<?php

namespace Croogo\Core;

use Cake\Core\App;
use Cake\Core\Configure;
use Cake\Event\Event;
use Cake\Event\EventManager;
use Cake\Http\ServerRequest;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;

/**
 * Croogo
 *
 * @package  Croogo.Croogo.Lib
 * @version  1.0
 * @author   Fahad Ibnay Heylaal <contact@fahad19.com>
 * @license  http://www.opensource.org/licenses/mit-license.php The MIT License
 * @link     http://www.croogo.org
 */
class Croogo
{

    /**
     * Loads as a normal component from controller.
     *
     * @param string $controllerName Controller Name
     * @param mixed $componentName  Component name or array of Component and settings
     */
    public static function hookComponent($controllerName, $componentName)
    {
        if (is_string($componentName)) {
            $componentName = [$componentName];
        }
        self::hookControllerProperty($controllerName, '_appComponents', $componentName);
    }

    /**
     * Loads an API component to a controller during route setup.
     *
     * @param string $controllerName Controller Name
     * @param mixed $componentName  Component name or array of Component and settings
     */
    public static function hookApiComponent($controllerName, $componentName)
    {
        $defaults = [
            'priority' => 8,
        ];
        if (is_string($componentName)) {
            $component = [$componentName => $defaults];
        } else {
            $cName = key($componentName);
            $settings = Hash::merge($defaults, $componentName[$cName]);
            $component = [$cName => $settings];
        }

        self::hookControllerProperty($controllerName, '_apiComponents', $component);
    }

    /**
     * Attaches Behavior to a Table whenever loaded.
     *
     * @param string $tableName
     * @param string $behaviorName
     * @param array  $config
     */
    public static function hookBehavior($tableName, $behaviorName, $config = [])
    {
        self::hookTableProperty(
            App::className($tableName, 'Model/Table', 'Table'),
            'hookedBehaviors',
            [
                $behaviorName => $config
            ]
        );
    }

    /**
     * Loads as a normal helper via controller.
     *
     * @param string $controllerName
     * @param mixed $helperName Helper name or array of Helper and settings
     */
    public static function hookHelper($controllerName, $helperName)
    {
        if (is_string($helperName)) {
            $helperName = [$helperName];
        }
        self::hookViewBuilderOption($controllerName, 'helpers', $helperName);
    }

    /**
     * Shows plugin's admin_menu element in admin navigation under Extensions.
     *
     * @param string $pluginName
     */
    public static function hookAdminMenu($pluginName)
    {
        $pluginName = Inflector::underscore($pluginName);
        Configure::write('Admin.menus.' . $pluginName, 1);
    }

    /**
     * In admin panel for the provided action, the link will appear in table rows under 'Actions' column.
     *
     * @param string $action in the format ControllerName/action_name
     * @param string $title Link title
     * @param string $url
     */
    public static function hookAdminRowAction($action, $title, $url)
    {
        $action = base64_encode($action);
        $rowActions = Configure::read('Admin.rowActions');
        if (!is_array($rowActions)) {
            $rowActions = [];
        }
        if (!isset($rowActions[$action])) {
            $rowActions[$action] = [];
        }
        $rowActions[$action][$title] = $url;
        Configure::write('Admin.rowActions', $rowActions);
    }

    /**
     * Admin tab
     *
     * @param string $action  in the format ControllerName/action_name
     * @param string $title   Tab title
     * @param string $element element name, like plugin_name.element_name
     * @param array  $options array with options for the hook to take effect
     */
    public static function hookAdminTab($action, $title, $element, $options = [])
    {
        self::_hookAdminBlock('Admin.tabs', $action, $title, $element, $options);
    }

    /**
     * Admin box
     *
     * @param string $action  in the format ControllerName/action_name
     * @param string $title   Box title
     * @param string $element element name, like plugin_name.element_name
     * @param array  $options array with options for the hook to take effect
     */
    public static function hookAdminBox($action, $title, $element, $options = [])
    {
        self::_hookAdminBlock('Admin.boxes', $action, $title, $element, $options);
    }

    protected static function _hookAdminBlock($key, $action, $title, $element, $options = [])
    {
        $tabs = Configure::read($key);
        if (!is_array($tabs)) {
            $tabs = [];
        }
        if (!isset($tabs[$action])) {
            $tabs[$action] = [];
        }
        $tabs[$action][$title]['element'] = $element;
        $tabs[$action][$title]['options'] = $options;
        Configure::write($key, $tabs);
    }

    protected static function _getClassName($name, $type, $suffix)
    {
        if ($name !== '*') {
            return App::classname($name, $type, $suffix);
        }

        return '*';
    }

    /**
     * Hook table property
     *
     * Useful when tables need to be associated to another one, setting Behaviors, disabling cache, etc.
     *
     * @param string $tableName Table name (for e.g., Croogo/Nodes.Nodes)
     * @param string $property for e.g., actsAs
     * @param string|array $value
     */
    public static function hookTableProperty($tableName, $property, $value)
    {
        $configKeyPrefix = 'Hook.table_properties';

        $tableClass = self::_getClassName($tableName, 'Model/Table', 'Table');

        if ($tableClass) {
            self::_hookProperty($configKeyPrefix, $tableClass, $property, $value);
        }
    }

    /**
     * Hook controller property
     *
     * @param string $controllerName Controller name (for e.g., Croogo/Nodes.Nodes)
     * @param string $property for e.g., components
     * @param string|array $value
     */
    public static function hookControllerProperty($controllerName, $property, $value)
    {
        $configKeyPrefix = 'Hook.controller_properties';
        $controllerClass = self::_getClassName($controllerName, 'Controller', 'Controller');

        if ($controllerClass) {
            self::_hookProperty($configKeyPrefix, $controllerClass, $property, $value);
        }
    }

    /**
     * Hook controller property
     *
     * @param string $controllerName Controller name (for e.g., Nodes)
     * @param string $option for e.g., components
     * @param string|array $value
     */
    public static function hookViewBuilderOption($controllerName, $option, $value)
    {
        $configKeyPrefix = 'Hook.view_builder_options';
        $controllerClass = self::_getClassName($controllerName, 'Controller', 'Controller');

        if ($controllerClass) {
            self::_hookProperty($configKeyPrefix, $controllerClass, $option, $value);
        }
    }

    /**
     * Hook property
     *
     * @param string $configKeyPrefix
     * @param string $name
     * @param string $property
     * @param string $value
     */
    protected static function _hookProperty($configKeyPrefix, $name, $property, $value)
    {
        $propertyValue = Configure::read($configKeyPrefix . '.' . $name . '.' . $property);

        if (!is_array($propertyValue)) {
            $propertyValue = null;
        }

        if (is_array($value)) {
            if (is_array($propertyValue)) {
                $propertyValue = Hash::merge($propertyValue, $value);
            } else {
                $propertyValue = $value;
            }
        } else {
            $propertyValue = $value;
        }

        Configure::write($configKeyPrefix . '.' . $name . '.' . $property, $propertyValue);
    }

    public static function options($configKey, $object, $option = null)
    {
        if (is_string($object)) {
            $objectName = $object;
        } elseif ($object instanceof ServerRequest) {
            $pluginPath = $controller = null;
            $namespace = 'Controller';
            if (!empty($object->getParam('plugin'))) {
                $pluginPath = $object->getParam('plugin') . '.';
            }
            if (!empty($object->getParam('controller'))) {
                $controller = $object->getParam('controller');
            }
            if (!empty($object->getParam('prefix'))) {
                $prefixes = array_map(
                    'Cake\Utility\Inflector::camelize',
                    explode('/', $object->getParam('prefix'))
                );
                $namespace .= '/' . implode('/', $prefixes);
            }
            $objectName = App::className($pluginPath . $controller, $namespace, 'Controller');
        } elseif (is_object($object)) {
            $objectName = get_class($object);
        } else {
            return;
        }

        $options = Configure::read($configKey . '.' . $objectName);

        if (is_array(Configure::read($configKey . '.*'))) {
            $options = Hash::merge(Configure::read($configKey . '.*'), $options);
        }

        if ($option) {
            return $options[$option];
        }

        return $options;
    }

    /**
     * Applies properties set from hooks to an object in __construct()
     *
     * @param string $configKey
     */
    public static function applyHookProperties($configKey, $object = null)
    {
        if (empty($object)) {
            $object = self;
        }

        $hookProperties = self::options($configKey, $object);
        if (is_array($hookProperties)) {
            foreach ($hookProperties as $property => $value) {
                if (!$object->getProperty($property)) {
                    $object->setProperty($property, $value);
                } else {
                    $currentValues = $object->getProperty($property);
                    if (is_array($currentValues)) {
                        if (is_array($value)) {
                            $object->setProperty($property, Hash::merge($object->getProperty($property), $value));
                        } else {
                            $object->setProperty($property, $value);
                        }
                    } else {
                        $object->setProperty($property, $value);
                    }
                }
            }
        }
    }

    /**
     * Convenience method to dispatch event.
     *
     * Creates, dispatches, and returns a new Event object.
     *
     * @see Event::__construct()
     * @param string $name Name of the event
     * @param object $subject the object that this event applies to
     * @param mixed $data any value you wish to be transported with this event
     *
     * @return Event
     */
    public static function dispatchEvent($name, $subject = null, $data = null)
    {
        $event = new Event($name, $subject, $data);
        if ($subject) {
            $event = $subject->getEventManager()->dispatch($event);
        } else {
            $event = EventManager::instance()->dispatch($event);
        }

        return $event;
    }

    /**
     * Get URL relative to the app
     *
     * @param array|string $url
     *
     * @return array
     */
    public static function getRelativePath($url = '/')
    {
        if (is_array($url)) {
            $absoluteUrl = Router::url($url, true);
        } else {
            $absoluteUrl = Router::url('/' . $url, true);
        }
        $path = '/' . str_replace(Router::url('/', true), '', $absoluteUrl);

        return $path;
    }

    /**
     * Merge Configuration
     *
     * @param string $key Configure key
     * @param array $config New configuration to merge
     * @param return array Array of merged configurations
     *
     * @return array|mixed
     */
    public static function mergeConfig($key, $config, $encode = false)
    {
        $values = Configure::read($key);
        if ($encode) {
            foreach ($config as $k => $v) {
                $tmp[base64_encode($k)] = $v;
            }
            $config = $tmp;
        }
        $values = Hash::merge((array)$values, $config);
        Configure::write($key, $values);

        return $values;
    }

    public static function translateModel($model, $config)
    {
        Croogo::mergeConfig('Translate.models', [
            $model => $config
        ], true);
    }
}
