<?php
namespace Wpupgrader\Libraries\WordPress\Plugins\Core\Hooks;

use Wpupgrader\Libraries\WordPress\Plugins\Core\PluginBaseInterface;
use Wpupgrader\Libraries\WordPress\Plugins\Core\Utilities;

/**
 * Class Autoloader must be extended by your Project Implementation and be PSR compliant
 * This class requires an Instance of PluginBaseInterface
 *
 * @author Wpupgrader, Patrick Weijtenburg
 */
abstract class Autoloader
{
    /** @var array $loaderMappings A list of protected Autoloader methods for each Folder in /Hooks/ */
    protected $loaderMappings = [
        'registerAction'    => 'Actions/',
        'registerFilter'    => 'Filters/',
        'registerShortcode' => 'Shortcodes/',
    ];

    /** @var string $loaderInvoker The method on the Hook class that is invoked by Wordpress */
    protected $loaderInvoker = 'invoke';

    /** @var \Wpupgrader\Libraries\WordPress\Plugins\Core\PluginBaseInterface $plugin */
    protected $plugin;

    /**
     * Auto-register Wordpress Action- and Filter Hooks from Files if $hooksPath is provided
     *
     * @param PluginBaseInterface $plugin
     * @param string $hooksPath
     * @param string $loaderInvoker
     * @param array $loaderMappings
     */
    public function __construct(PluginBaseInterface &$plugin, $hooksPath = null, $loaderInvoker = null, array $loaderMappings = null)
    {
        $this->plugin = $plugin;

        if (\strlen($loaderInvoker)) {
            $this->loaderInvoker = (string)$loaderInvoker;
        }
        if (!empty($loaderMappings)) {
            $this->loaderMappings = (array)$loaderMappings;
        }
        if (\strlen($hooksPath)) {
            $this->registerWpHooksFolder($hooksPath);
        }
    }

    /**
     * Auto-register Wordpress Action- and Filter Hooks from Files if $hooksPath is provided
     *
     * @param PluginBaseInterface $plugin
     * @param string $hooksPath
     * @param string $loaderInvoker
     * @param array $loaderMappings
     * @return static
     */
    static public function Factory(PluginBaseInterface &$plugin, $hooksPath = null, $loaderInvoker = null, array $loaderMappings = null)
    {
        return new static($plugin, $hooksPath, $loaderInvoker, $loaderMappings);
    }

    /**
     * Retrieve the Namespace of the current Autoloader class
     *
     * @return bool|string
     */
    static public function loaderNamespace()
    {
        return \substr(static::class, 0, \strrpos(static::class, '\\'));
    }

    /**
     * Register Wordpress Action- and Filter Hooks from Files (e.g. Actions/init.php, Actions/wp_head.php, Filters/plugins_api.php)
     * Mappings can be configured by overwriting $this->loaderMappings of an abstract Hooks\Autoloader
     *
     * @param string $hooksPath (e.g. src/YourProject/Hooks)
     * @param bool $unregister When TRUE this will unregister the hooks, instead of registering them.
     */
    public function registerWpHooksFolder($hooksPath, $unregister = false)
    {
        foreach ($this->loaderMappings as $autoLoader => $folder) {
            $path = \rtrim($hooksPath, '/').'/'.\trim($folder, '/');
            if (\is_dir($path)) {
                foreach (\glob($path.'/*.php') as $filePath) {
                    $this->$autoLoader($filePath, $unregister);
                }
            }
        }
    }

    /**
     * Unregister Wordpress Action- and Filter Hooks from Files
     *
     * @param string $hooksPath (e.g. src/YourProject/Hooks)
     */
    public function unregisterWpHooksFolder($hooksPath)
    {
        $this->registerWpHooksFolder($hooksPath, true);
    }

    /**
     * Registers a Wordpress Action Hook from $filePath (e.g. ../Actions/init.php)
     * ---
     * Action hooks (or actions) are triggered when something takes place, such as loading a page, a user
     * logging in, or a custom action that you define in your theme or plugin.
     *
     * @param string $filePath
     * @param bool $unregister When TRUE this will unregister the hooks, instead of registering them.
     */
    protected function registerAction($filePath, $unregister = false)
    {
        $actionName = \basename($filePath, '.php');
        $__PSR__className = static::loaderNamespace().'\\'.\basename(\dirname($filePath)).'\\'.$actionName;

        $priority = 10;
        $arguments = 10; // since we don't know at this point how many parameters each plugin expects, we make sure they will be provided with all of them (it's unlikely any developer will specify more than 99 parameters in a function)

        if (\preg_match('~^(.+?)[_-](\d+)(?:[_-](\d+))?$~', $actionName, $match)) {
            $actionName = (string)$match[1];
            $priority = (int)$match[2];

            if (isset($match[3])) {
                $arguments = (int)$match[3];
            }
        }

        // Temp Fix: ACF filters use / so these are stored as ___ and then replaced with /
        $actionName = \str_replace('___', '/', $actionName);

        if ($unregister) {
            \remove_action($actionName, [$__PSR__className, $this->loaderInvoker], $priority);
        }
        else {
            \add_action($actionName, [$__PSR__className, $this->loaderInvoker], $priority, $arguments);
        }
    }

    /**
     * Registers a Wordpress Filter Hook from $filePath (e.g. ../Filters/plugins_api.php)
     * ---
     * Filter hooks, or filters, control how something happens or change something that's already being output.
     * You could use a filter to output metadata in a specific format, to override text output by your plugin,
     * or to prevent something from being displayed at all.
     *
     * @param string $filePath
     * @param bool $unregister When TRUE this will unregister the hooks, instead of registering them.
     */
    protected function registerFilter($filePath, $unregister = false)
    {
        $filterName = \basename($filePath, '.php');
        $__PSR__className = static::loaderNamespace().'\\'.\basename(\dirname($filePath)).'\\'.$filterName;

        $priority = 10;
        $arguments = 10; // since we don't know at this point how many parameters each plugin expects, we make sure they will be provided with all of them (it's unlikely any developer will specify more than 99 parameters in a function)

        if (\preg_match('~^(.+?)[_-](\d+)(?:[_-](\d+))?$~', $filterName, $match)) {
            $filterName = (string)$match[1];
            $priority = (int)$match[2];

            if (isset($match[3])) {
                $arguments = (int)$match[3];
            }
        }

        // Temp Fix: ACF filters use / so these are stored as ___ and then replaced with /
        $filterName = \str_replace('___', '/', $filterName);

        if ($unregister) {
            \remove_filter($filterName, [$__PSR__className, $this->loaderInvoker], $priority);
        }
        else {
            \add_filter($filterName, [$__PSR__className, $this->loaderInvoker], $priority, $arguments);
        }
    }

    /**
     * Registers a Wordpress Shortcode Hook from $filePath (e.g. ../Shortcodes/settings.php)
     *
     * @param string $filePath
     * @param bool $unregister When TRUE this will unregister the hooks, instead of registering them.
     */
    protected function registerShortcode($filePath, $unregister = false)
    {
        $shortcodeName = \basename($filePath, '.php');
        $__PSR__className = static::loaderNamespace().'\\'.\basename(\dirname($filePath)).'\\'.$shortcodeName;

        $shortcodeTag = Utilities::tableizer($this->plugin->getConfig()->get('prefix').'_'.$shortcodeName);

        if ($unregister) {
            \remove_shortcode($shortcodeTag);
        }
        else {
            \add_shortcode($shortcodeTag, [$__PSR__className, $this->loaderInvoker]);
        }
    }
}