<?php
/**
 * app/classes/plugin.php
 *
 * This class is used as a foundation for all plugins to build from.
 *
 * @version 3.0
 * @author  Joey Kimsey <Joey@thetempusproject.com>
 * @link    https://TheTempusProject.com
 * @license https://opensource.org/licenses/MIT [MIT LICENSE]
 */
namespace TheTempusProject\Classes;

use TheTempusProject\Houdini\Classes\Navigation;
use TheTempusProject\Houdini\Classes\Filters;
use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Classes\Database;

class Plugin {
    public $required_models = [];
    public $models = [];
    public $errors = [];

    // Global Properties
    public $module;
    public $initialized = false;
    public static $installer;
    public static $db;
    public static $installFlags = PLUGIN_INSTALL_FLAGS;
    public static $pluginFolders = [];
    public static $pluginsAvailable = [];
    public static $pluginsActive = [];

    // Basic Required Info
    public $pluginName = 'Default Plugin Name';
    public $pluginAuthor = 'TheTempusProject';
    public $pluginWebsite = 'https://TheTempusProject.com';
    public $pluginVersion = 0.0;
    public $pluginDescription = 'The Default Plugin Description';

    // Front-end Properties
    public $admin_links = [];
    public $main_links = [];
    public $footer_links = [];
    public $filters = [];

    // Install Related
    public $configName = '';
    public $configMatrix = [];
    public $resourceMatrix = [];
    public $preferenceMatrix = [];
    public $permissionMatrix = [];

    const PLUGIN_FLAG_MAP = [
        'preferences_installed' => 'installPreferences',
        'permissions_installed' => 'installPermissions',
        'configs_installed' => 'installConfigs',
        'models_installed' => 'installModels',
        'resources_installed' => 'installResources',
    ];

    public function __construct( $load = false ) {
        if ( true === $this->initialized && false == $load ) {
            return;
        }
        self::$db = Database::getInstance();
        if ( ! isset( self::$installer ) ) {
            self::$installer = new Installer();
        }
        if ( ! isset( $this->module ) ) {
            $this->module = self::$installer->getModule( getClassName( $this ) );
        }
        if ( true == $load ) {
            if ( $this->checkEnabled() ) {
                $this->loadAdminNav();
                $this->loadMainNav();
                $this->loadFooterNav();
                $this->loadFilters();
            }
            $this->initialized = true;
        }
    }

    public function install( $options ) {
        Debug::log( 'Installing Plugin: ' . $this->pluginName );
        $module_data = [];
        $errors = [];

        foreach ( self::PLUGIN_FLAG_MAP as $flag_name => $function_name ) {
            if ( empty( $options[$flag_name] ) ) {
                $module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
                continue;
            }

            if ( 'installModels' != $function_name ) {
                $result = $this->$function_name( $options );
            } else {
                $model_options = $this->convertPluginOptionsToModelOptions( $options );
                $result = $this->$function_name( $model_options );
            }

            if ( empty( $result ) ) {
                $errors[] = ['errorInfo' => get_class($this) . " Failed to execute $flag_name properly."];
                $module_data[ $flag_name ] = INSTALL_STATUS_FAIL;
                continue;
            }

            if ( 'installResources' === $function_name ) {
                $module_data[ $flag_name ] = $result;
                continue;
            }

            $module_data[ $flag_name ] = INSTALL_STATUS_SUCCESS;
            continue;
        }

        return [ $module_data, $errors ];
    }

    public function uninstall( $options ) {
        Debug::log( 'Uninstalling Plugin: ' . $this->pluginName );
        $module_data = [];
        $errors = [];

        foreach ( self::PLUGIN_FLAG_MAP as $flag_name => $function_name ) {
            $function_name = 'un' . $function_name;
            if ( empty( $options[$flag_name] ) ) {
                $module_data[ $flag_name ] = INSTALL_STATUS_SKIPPED;
                continue;
            }

            if ( 'installModels' != $function_name ) {
                $result = $this->$function_name( $options );
            } else {
                $model_options = $this->convertPluginOptionsToModelOptions( $options );
                $result = $this->$function_name( $model_options );
            }

            if ( empty( $result ) ) {
                $errors[] = ['errorInfo' => get_class($this) . " Failed to execute $flag_name properly."];
                $module_data[ $flag_name ] = INSTALL_STATUS_FAIL;
                continue;
            }

            if ( 'uninstallResources' === $function_name ) {
                $module_data[ $flag_name ] = $result;
                continue;
            }

            $module_data[ $flag_name ] = INSTALL_STATUS_UNINSTALLED;
            continue;
        }

        return $errors;
    }

    public function installModels( $options ) {
        $class = get_class($this);
        $nameArray =  explode( '\\', $class );
        $name = array_pop( $nameArray );
        $directory = PLUGIN_DIRECTORY . lcfirst($name) . DIRECTORY_SEPARATOR . 'models' . DIRECTORY_SEPARATOR;
        if ( ! file_exists( $directory ) ) {
            Debug::log( 'models directory is empty' );
            return true;
        }
        $models = self::$installer->getModelList( $directory );
        $error = false;
        foreach ( $models as $model ) {
            $result = self::$installer->installModel( $model, $options, true, false );
            
            if ( $result === false ) {
                $error = true;
                continue;
            }
        }
        if ( $error ) {
            return false;
        } else {
            return true;
        }
    }

    public function uninstallModels( $options ) {
        $class = get_class($this);
        $nameArray =  explode( '\\', $class );
        $name = array_pop( $nameArray );
        $directory = PLUGIN_DIRECTORY . lcfirst($name) . DIRECTORY_SEPARATOR . 'models' . DIRECTORY_SEPARATOR;
        if ( ! file_exists( $directory ) ) {
            Debug::log( 'models directory is empty' );
            return true;
        }
        $models = self::$installer->getModelList( $directory );
        $error = false;
        foreach ( $models as $model ) {
            $result = self::$installer->uninstallModel( $model, $options, true, false );
            
            if ( $result === false ) {
                $error = true;
                continue;
            }
        }
        if ( $error ) {
            return false;
        } else {
            return true;
        }
    }

    public function installPermissions( $options = '' ) {
        if ( empty( $this->permissionMatrix ) ) {
            Debug::log( 'permissionMatrix is empty' );
            return true;
        }
        $perms = new Permissions();
        foreach ( $this->permissionMatrix as $name => $details ) {
            $perms->add( $name, $details );
        }
        return $perms->save( true );
    }

    public function uninstallPermissions( $options = '' ) {
        if ( empty( $this->permissionMatrix ) ) {
            Debug::log( 'permissionMatrix is empty' );
            return true;
        }
        $perms = new Permissions();
        foreach ( $this->permissionMatrix as $name => $details ) {
            $perms->remove( $name, true );
        }
        return true;
    }

    public function installConfigs( $options = '' ) {
        if ( empty( $this->configMatrix ) || empty( $this->configName )) {
            Debug::log( 'configMatrix is empty' );
            return true;
        }
        $config = new Config( CONFIG_JSON );
        // should have some sort of DELTA functionality and safeguards
        $config->addCategory( $this->configName );
        foreach ( $this->configMatrix as $name => $details ) {
            $config->add( $this->configName, $name, $details );
        }
        return $config->save();
    }

    public function uninstallConfigs( $options = '' ) {
        if ( empty( $this->configName ) ) {
            return true;
        }
        $config = new Config( CONFIG_JSON );
        return $config->removeCategory( $this->configName, true, true );
    }

    public function installPreferences( $options = '' ) {
        $prefs = new Preferences();
        if ( empty( $this->preferenceMatrix ) ) {
            Debug::log( 'preferenceMatrix is empty' );
            return true;
        }
        foreach ( $this->preferenceMatrix as $name => $details ) {
            $prefs->add( $name, $details );
        }
        return $prefs->save( true );
    }

    public function uninstallPreferences( $options = '' ) {
        if ( empty( $this->preferenceMatrix ) ) {
            Debug::log( 'preferenceMatrix is empty' );
            return true;
        }
        $prefs = new Preferences();
        foreach ( $this->preferenceMatrix as $name => $details ) {
            $prefs->remove( $name, true );
        }
        return $prefs->save( true );
    }

    public function installResources( $options = '' ) {
        if ( empty( $this->resourceMatrix ) ) {
            Debug::log( 'resourceMatrix is empty' );
            return true;
        }
        $ids = [];
        foreach( $this->resourceMatrix as $tableName => $entries ) {
            foreach ( $entries as $entry ) {
                foreach ( $entry as $key => $value ) {
                    if ( '{time}' == $value ) {
                        $entry[$key] = time();
                    }
                }
                self::$db->insert( $tableName, $entry );
                $id = self::$db->lastId();
                if ( $id ) {
                    $ids[] = $id;
                }
            }
        }
        return $ids;
    }

    public function uninstallResources( $options = '' ) {
        if ( empty( $this->resourceMatrix ) ) {
            Debug::log( 'resourceMatrix is empty' );
            return true;
        }
        $ids = $this->module['resources_installed'];
        $data = [];
        foreach( $this->resourceMatrix as $tableName => $entries ) {
            foreach ($ids as $id) {
                $data[] = self::$db->delete( $tableName, [ 'ID', '=', $id ] );
            }
        }
        return $data;
    }

    /**
     * Loaders
     */
    public function loadAdminNav() {
        if ( !empty( $this->admin_links ) ) {
            foreach( $this->admin_links as $key => $link ) {
                Navigation::addLink( App::ADMIN_MENU_NAME, $link );
            }
        }
    }

    public function loadMainNav() {
        if ( !empty( $this->main_links ) ) {
            foreach( $this->main_links as $key => $link ) {
                Navigation::addLink( App::MAIN_MENU_NAME, $link );
            }
        }
    }

    public function loadFooterNav() {
        if ( !empty( $this->contact_footer_links ) ) {
            foreach( $this->contact_footer_links as $key => $link ) {
                Navigation::addLink( App::CONTACT_FOOTER_MENU_NAME, $link );
            }
        }
        if ( !empty( $this->info_footer_links ) ) {
            foreach( $this->info_footer_links as $key => $link ) {
                Navigation::addLink( App::INFO_FOOTER_MENU_NAME, $link );
            }
        }
    }

    public function loadFilters() {
        if ( ! empty( $this->filters ) ) {
            foreach( $this->filters as $filter ) {
                Filters::add( $filter['name'], $filter['find'], $filter['replace'], $filter['enabled'] );
            }
        }
    }

    public function convertPluginOptionsToModelOptions( $options ) {
        $data = [];
        foreach (self::PLUGIN_FLAG_MAP as $pluginValue => $modelValue) {
            if ( isset( $options[$pluginValue] ) ) {
                $data[$modelValue] = $options[$pluginValue];
            }
        }
        return $data;
    }

    public static function getPluginDirectories( $forceRefresh = false ) {
        if ( !empty( self::$pluginFolders ) && true !== $forceRefresh ) {
            return self::$pluginFolders;
        }
        $pluginFolders = [];
        if  ( ! PLUGINS_ENABLED && true !== $forceRefresh ) {
            Debug::warn('Plugins disabled');
            return $pluginFolders;
        }

        if ( ! file_exists( PLUGIN_DIRECTORY ) ) {
            Debug::warn("Plugins folder is missing: $dir");
            return $pluginFolders;
        }

        // get a list of all plugins in the plugin directory
        $pluginDirectories = scandir( PLUGIN_DIRECTORY );
        array_shift( $pluginDirectories ); // remove the .
        array_shift( $pluginDirectories ); // remove the ..

        foreach ( $pluginDirectories as $key => $pluginName ) {
            $pluginDirectory = PLUGIN_DIRECTORY . $pluginName;
            if ( is_file( $pluginDirectory ) ) {
                continue; // skip any files in the main plugin directory if they exist
            }

            // get a list of all directories in this plugin directory
            $pluginFolders[ $pluginName ] = [];
            $pluginDirectory .= DIRECTORY_SEPARATOR;
            $pluginDirectoryArray = scandir( $pluginDirectory );
            array_shift( $pluginDirectoryArray ); // remove the .
            array_shift( $pluginDirectoryArray ); // remove the ..

            // loop over each sub-directory insider plugin directory
            foreach ( $pluginDirectoryArray as $key => $file ) {
                $currentFolder = $pluginDirectory . $file . DIRECTORY_SEPARATOR;
                switch ( $file ) {
                    case 'controllers':
                    case 'config':
                    case 'models':
                    case 'views':
                    case 'templates':
                        break;
                    case 'forms.php':
                        $currentFolder = rtrim( $currentFolder, DIRECTORY_SEPARATOR );
                        break;
                    case 'plugin.php':
                        $currentFolder = rtrim( $currentFolder, DIRECTORY_SEPARATOR );
                        break;
                    default:
                        continue 2; // break if we aren't looking for whatever we found
                }
                $pluginFolders[ $pluginName ][] = [ $currentFolder => $file ];
            }
        }

        self::$pluginFolders = $pluginFolders;
        return self::$pluginFolders;
    }

    public static function getActivePlugins( $forceRefresh = false ) {
        if ( ! empty( self::$pluginsActive ) && true !== $forceRefresh ) {
            return self::$pluginsActive;
        }
        if ( ! isset( self::$installer ) ) {
            self::$installer = new Installer();
        }
        $out = [];
        $plugins = self::$installer->getAvailablePlugins( $forceRefresh );

        if ( ! empty( $plugins ) ) {
            foreach ( $plugins as $plugin ) {
                if ( !isset( $plugin->class_object )) {
                    continue;
                }
                $installedPlugin = $plugin->class_object->module;
                if ( !isset( $installedPlugin['enabled'] ) || !$installedPlugin['enabled'] ) {
                    continue;
                }
                $out[] = [
                    $plugin->name => $installedPlugin['class'],
                ];
            }
        }
        self::$pluginsActive = $out;
        return self::$pluginsActive;
    }

    public static function enable( $name, $save = true ) {
        if ( ! isset( self::$installer ) ) {
            self::$installer = new Installer();
        }
        $module = self::$installer->getModule( $name );
        if ( empty( $module ) ) {
            Debug::warn( "plugin not found: $name" );
            return false;
        }
        if ( ! isset( $module['enabled'] ) ) {
            Debug::error( "plugin enabled not set: $name" );
            return false;
        }
        $module['enabled'] = true;
        return self::$installer->setModule( $name, $module, $save );
    }

    public static function disable( $name, $save = true ) {
        if ( ! isset( self::$installer ) ) {
            self::$installer = new Installer();
        }
        $module = self::$installer->getModule( $name );
        if ( empty($module) ) {
            Debug::warn( "plugin not found: $name" );
            return false;
        }
        if ( ! isset( $module['enabled'] ) ) {
            Debug::error( "plugin not enabled: $name" );
            return false;
        }
        $module['enabled'] = false;
        return self::$installer->setModule( $name, $module, $save );
    }

    public function checkEnabled() {
        $name = ucfirst( strtolower( $this->pluginName ) );
        if ( isset( $this->module['enabled'] ) ) {
            return $this->module['enabled'];
        }
        Debug::warn( "install not found: {$this->pluginName}" );
        return false;
    }
}
