<?php

namespace Fab;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Bootstrap
 *
 * @package    Fab
 * @subpackage Fab\Includes
 */
class Bootstrap {

    /**
     * Plugin name
     *
     * @var     string
     */
    public $name;

    /**
     * Plugin slug
     *
     * @var     string
     */
    public $slug;

    /**
     * Plugin version
     *
     * @var     string
     */
    public $version;

    /**
     * Plugin stage (true = production, false = development)
     *
     * @var     boolean
     */
    public $production;

    /**
     * Plugin namespace
     *
     * @var     string
     */
    public $namespace;

    /**
     * Lists of plugin apis
     *
     * @var     array
     */
    public $apis;

    /**
     * Lists of plugin controllers
     *
     * @var     array
     */
    public $controllers;

    /**
     * Lists of plugin features
     *
     * @var     array
     */
    public $features;

    /**
     * Lists of plugin models
     *
     * @var     array
     */
    public $models;

    /**
     * Lists of plugin modules
     *
     * @var     array
     */
    public $modules;

    /**
     * Plugin configuration
     *
     * @var     object
     */
    public $config;

    /**
     * Helper object for controller
     *
     * @access   public
     * @var      object    $Helper  Helper object for controller
     */
    public $Helper;

    /**
     * WordPress Helper
     *
     * @access   public
     * @var      object    $WP  WordPress Helper object
     */
    public $WP;

    /**
     * Ignored files
     * - These files are not loaded into the plugin.
     *
     * @var     array
     */
    public $ignored_files = array( '.', '..', '.DS_Store', 'index.php' );

    /**
     * Define the core functionality of the plugin.
     *
     * @return void
     */
    public function __construct() {
        // Get Config from config.json.
        $this->config = \Fab\Plugin\Config::getInstance()->getConfig();

        // Initiate Plugin.
        $this->name        = $this->config->name;
        $this->slug        = strtolower( $this->name );
        $this->slug        = str_replace( ' ', '-', $this->slug );
        $this->version     = $this->config->version;
        $this->production  = $this->config->production;
        $this->controllers = array();
        $this->apis        = array();
        $this->features    = array();
        $this->models      = array();
        $this->Helper      = \Fab\Plugin\Helper::getInstance();
    }

    /**
     * Load plugin
     *
     * @return void
     */
    public function load_plugin() {
        $this->load_models();
        $this->load_features();
        $plugin_path       = ( 'Fab' === $this->namespace ) ? 'src' : 'premium';
        $this->controllers = $this->load_hooks( $this->config->path['plugin_path'] . $plugin_path . '/Controller' ); // We use $this->config->path['plugin_path'] because activate hook is fired before plugins_loaded hook.
        $this->apis        = $this->load_hooks( $this->config->path['plugin_path'] . $plugin_path . '/Api' ); // We use $this->config->path['plugin_path'] because activate hook is fired before plugins_loaded hook.
    }

    /**
     * Load registered models
     *
     * @return  void
     */
    public function load_models() {
        $plugin_path = ( 'Fab' === $this->namespace ) ? 'src' : 'premium';
        $models      = $this->Helper->getDirFiles( $this->config->path['plugin_path'] . $plugin_path . '/Model' ); // We use $this->config->path['plugin_path'] because activate hook is fired before plugins_loaded hook.
        foreach ( $models as $model ) {
            if ( in_array( basename( $model ), $this->ignored_files, true ) ) {
                continue;
            }
            $name  = basename( $model, '.php' );
            $model = sprintf( '\\%s\\%s\\%s', $this->namespace, 'Model', $name );
            $model = new $model( $this );

            // Run model run method.
            if ( method_exists( $model, 'run' ) ) {
                $model->run();
            }
        }
    }

    /**
     * Load registered hooks in a controller
     *
     * @return  void
     * @pattern bridge
     */
    private function load_features() {
        $plugin_path = ( 'Fab' === $this->namespace ) ? 'src' : 'premium';
        $features    = $this->Helper->getDirFiles( $this->config->path['plugin_path'] . $plugin_path . '/Feature' ); // We use $this->config->path['plugin_path'] because activate hook is fired before plugins_loaded hook.
        foreach ( $features as $feature ) {
            if ( in_array( basename( $feature ), $this->ignored_files, true ) ) {
                continue;
            }
            $name                                 = basename( $feature, '.php' );
            $feature                              = sprintf( '\\%s\\%s\\%s', $this->namespace, 'Feature', $name );
            $feature                              = new $feature();
            $this->features[ $feature->getKey() ] = $feature;

            // Execute JIT.
            if ( is_subclass_of( $feature, 'Fab\Interfaces\JIT_Interface' ) && method_exists( $feature, 'jit' ) ) {
                $feature::jit();
            }

            // Run feature run method.
            if ( method_exists( $feature, 'run' ) ) {
                $feature->run();
            }
        }
        ksort( $this->features );
    }

    /**
     * Load registered hooks in a controller
     *
     * @param   string $dir   full path of plugin hooks directory (API, Controller).
     * @return  array
     */
    public function load_hooks( $dir ) {
        $controllers = $this->Helper->getDirFiles( $dir );
        foreach ( $controllers as $key => &$controller ) {
            // Skip if controller is in ignore list.
            if ( in_array( basename( $controller ), $this->ignored_files, true ) ) {
                unset( $controllers[ $key ] );
                continue;
            }

            // Get controller name.
            $name       = basename( $controller, '.php' );
            $controller = sprintf( '\\%s\\%s\\%s', $this->namespace, ucwords( basename( $dir ) ), $name );

            // Register JIT class.
            if ( is_subclass_of( $controller, 'Fab\Interfaces\JIT_Interface' ) && method_exists( $controller, 'jit' ) ) {
                $controller::jit();
                unset( $controllers[ $key ] );
            }

            // Initialize method that required an object to run.
            $required_object = array(
                'initialize' => 'Fab\Interfaces\Initiable_Interface',
                'run'        => 'Fab\Interfaces\Model_Interface',
            );
            foreach ( $required_object as $method => $interface ) {
                // If controller is an object, run method.
                if ( is_subclass_of( $controller, $interface ) && method_exists( $controller, $method ) ) {
                    // If controller is not an object, create new instance.
                    // We place this here because we wants to avoid creating new instance if possible.
                    if ( ! is_object( $controller ) ) {
                        $controller = new $controller();
                    }

                    // Run method.
                    $controller->$method();
                }
            }
        }

        return $controllers;
    }

    /**
     * Load Modules Hook
     *
     * @return array
     */
    public function load_modules_hooks() {
        $modules = $this->Helper->getDirFiles( $this->config->path['plugin_path'] . 'src/Helper/Module' ); // We use $this->config->path['plugin_path'] because activate hook is fired before plugins_loaded hook.
        foreach ( $modules as &$module ) {
            if ( in_array( basename( $module ), $this->ignored_files, true ) ) {
                continue;
            }
            $name   = basename( $module, '.php' );
            $module = '\\Fab\\Module\\' . $name;
            $module = new $module();

            if ( method_exists( $module, 'run' ) ) {
                $module->run();
            }
        }

        return $modules;
    }

    /**
     * Magic method to handle dynamic getter/setter calls
     *
     * @param string $method Method name being called.
     * @param array  $arguments Arguments passed to the method.
     * @return mixed
     */
    public function __call( $method, $arguments ) {
        // Check if method starts with 'get' or 'set'.
        $prefix = substr( $method, 0, 3 );

        if ( 'get' === $prefix || 'set' === $prefix ) {
            // Get the property name by removing prefix and converting to lowercase first char.
            $property = lcfirst( substr( $method, 3 ) );

            // Check if property exists.
            if ( property_exists( $this, $property ) ) {
                if ( 'get' === $prefix ) {
                    return $this->$property;
                } else {
                    $this->$property = $arguments[0];
                    return;
                }
            }
        }

        // If method not handled, throw exception.
        return new \WP_Error(
            'fab_method_not_found',
            // translators: %s: method name that was called but does not exist.
            esc_html( sprintf( __( 'Method %s does not exist.', 'floating-awesome-button' ), $method ) )
        );
    }

    /**
     * Run the plugin
     * - Load plugin model
     * - Load plugin API
     * - Load plugin controller
     *
     * @return  void
     */
    public function run() {
        add_action( 'plugins_loaded', array( $this, 'load_plugin' ) );
    }

    /**
     * Activate the plugin
     *
     * @return  void
     */
    public function activate() {
        $plugin_path = ( 'Fab' === $this->namespace ) ? 'src' : 'premium';
        $dir         = $this->config->path['plugin_path'] . $plugin_path . '/Controller'; // We use $this->config->path['plugin_path'] because activate hook is fired before plugins_loaded hook.
        $controllers = $this->Helper->getDirFiles( $dir );
        foreach ( $controllers as $key => &$controller ) {
            // Skip if controller is in ignore list.
            if ( in_array( basename( $controller ), $this->ignored_files, true ) ) {
                unset( $controllers[ $key ] );
                continue;
            }

            // Get controller name.
            $name       = basename( $controller, '.php' );
            $controller = sprintf( '\\%s\\%s\\%s', $this->namespace, ucwords( basename( $dir ) ), $name );

            // Register JIT class.
            if ( is_subclass_of( $controller, 'Fab\Interfaces\Activatable_Interface' ) && method_exists( $controller, 'activate' ) ) {
                $controller = isset( $this->controllers[ $name ] ) ? $this->controllers[ $name ] : new $controller();
                $controller->activate();
            }
        }
    }

    /**
     * Deactivate the plugin
     *
     * @return  void
     */
    public function deactivate() {
        $plugin_path = ( 'Fab' === $this->namespace ) ? 'src' : 'premium';
        $dir         = $this->config->path['plugin_path'] . $plugin_path . '/Controller'; // We use $this->config->path['plugin_path'] because activate hook is fired before plugins_loaded hook.
        $controllers = $this->Helper->getDirFiles( $dir );
        foreach ( $controllers as $key => &$controller ) {
            // Skip if controller is in ignore list.
            if ( in_array( basename( $controller ), $this->ignored_files, true ) ) {
                unset( $controllers[ $key ] );
                continue;
            }

            // Get controller name.
            $name       = basename( $controller, '.php' );
            $controller = sprintf( '\\%s\\%s\\%s', $this->namespace, ucwords( basename( $dir ) ), $name );

            // Register JIT class.
            if ( is_subclass_of( $controller, 'Fab\Interfaces\Deactivatable_Interface' ) && method_exists( $controller, 'deactivate' ) ) {
                $controller = isset( $this->controllers[ $name ] ) ? $this->controllers[ $name ] : new $controller();
                $controller->deactivate();
            }
        }
    }
}
