<?php defined('BASEPATH') or exit('No direct script access allowed');

// load the MX core module class
require_once __DIR__ .'/Modules.php';

/**
 * Modular Extensions - HMVC
 *
 * Adapted from the CodeIgniter Core Classes
 * @link    http://codeigniter.com
 *
 * Description:
 * This library extends the CodeIgniter router class.
 *
 * Install this file as application/third_party/MX/Router.php
 *
 * @copyright   Copyright (c) 2015 Wiredesignz
 * @version     5.5
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 **/
class MX_Router extends CI_Router
{
    public $module;
    private $located = 0;

    /**
     * [fetch_module description]
     *
     * @method fetch_module
     *
     * @return [type]       [description]
     */
    public function fetch_module()
    {
        return $this->module;
    }

    /**
     * [_set_request description]
     *
     * @method _set_request
     *
     * @param  array        $segments [description]
     */
    protected function _set_request($segments = [])
    {
        if ($this->translate_uri_dashes === true) {
            foreach (range(0, 2) as $v) {
                isset($segments[$v]) && $segments[$v] = str_replace('-', '_', $segments[$v]);
            }
        }

        $segments = $this->locate($segments);

        if ($this->located == -1) {
            $this->_set_404override_controller();
            return;
        }

        if (empty($segments)) {
            $this->_set_default_controller();
            return;
        }

        $this->set_class($segments[0]);

        if (isset($segments[1])) {
            $this->set_method($segments[1]);
        } else {
            $segments[1] = 'index';
        }

        array_unshift($segments, null);
        unset($segments[0]);
        $this->uri->rsegments = $segments;
    }

    /**
     * [_set_404override_controller description]
     *
     * @method _set_404override_controller
     */
    protected function _set_404override_controller()
    {
        $this->_set_module_path($this->routes['404_override']);
    }

    /**
     * [_set_default_controller description]
     *
     * @method _set_default_controller
     */
    protected function _set_default_controller()
    {
        if (empty($this->directory)) {
            // set the default controller module path
            $this->_set_module_path($this->default_controller);
        }

        parent::_set_default_controller();

        if (empty($this->class)) {
            $this->_set_404override_controller();
        }
    }

    /**
     * [Locate the controller]
     *
     * @method locate
     *
     * @param  [type] $segments [description]
     *
     * @return [type]           [description]
     */
    public function locate($segments)
    {
        // Clear var $this->directory before search controller in function locate() of the Router class.
        // Solve the problem of trying to load a "root" controller using Modules::run('controller/method') after loading a module controller
        // with Modules::run('module/controller/method')
        $this->directory = null;
        $this->located = 0;
        $ext = $this->config->item('controller_suffix').EXT;

        /* use module route if available */
        if (isset($segments[0]) && $routes = Modules::parse_routes($segments[0], implode('/', $segments))) {
            $segments = $routes;
        }

        // Backward function
        // Before PHP 7.1.0, list() only worked on numerical arrays and assumes the numerical indices start at 0.
        if (version_compare(phpversion(), '7.1', '<')) {
            // php version isn't high enough
            // get the segments array elements
            list($module, $directory, $controller) = array_pad($segments, 3, null);
        } else {
            [$module, $directory, $controller] = array_pad($segments, 3, null);
        }

        // check modules
        foreach (Modules::$locations as $location => $offset) {
            // module exists?
            if (is_dir($source = $location.$module.'/controllers/')) {
                $this->module = $module;
                $this->directory = $offset.$module.'/controllers/';

                // module sub-controller exists?
                if ($directory) {
                    // module sub-directory exists?
                    if (is_dir($source.$directory.'/')) {
                        $source .= $directory.'/';
                        $this->directory .= $directory.'/';

                        // module sub-directory controller exists?
                        if ($controller) {
                            if (is_file($source.ucfirst($controller).$ext)) {
                                $this->located = 3;
                                return array_slice($segments, 2);
                            }
                            $this->located = -1;
                        }
                    } elseif (is_file($source.ucfirst($directory).$ext)) {
                        $this->located = 2;
                        return array_slice($segments, 1);
                    } else {
                        $this->located = -1;
                    }
                }

                // module controller exists?
                if (is_file($source.ucfirst($module).$ext)) {
                    $this->located = 1;
                    return $segments;
                }
            }
        }

        if (! empty($this->directory)) {
            return;
        }

        // application sub-directory controller exists?
        if ($directory) {
            if (is_file(APPPATH.'controllers/'.$module.'/'.ucfirst($directory).$ext)) {
                $this->directory = $module.'/';
                return array_slice($segments, 1);
            }

            // application sub-sub-directory controller exists?
            if ($controller && is_file(APPPATH . 'controllers/' . $module . '/' . $directory . '/' . ucfirst($controller) . $ext)) {
                $this->directory = $module.'/'.$directory.'/';
                return array_slice($segments, 2);
            }
        }

        // application controllers sub-directory exists?
        if (is_dir(APPPATH.'controllers/'.$module.'/')) {
            $this->directory = $module.'/';
            return array_slice($segments, 1);
        }

        // application controller exists?
        if (is_file(APPPATH.'controllers/'.ucfirst($module).$ext)) {
            return $segments;
        }
        $this->located = -1;
    }

    /**
     * [set module path]
     *
     * @method _set_module_path
     *
     * @param  [type]  &$_route [description]
     */
    protected function _set_module_path(&$_route)
    {
        if (! empty($_route)) {
            // Are module/directory/controller/method segments being specified?
            $sgs = sscanf($_route, '%[^/]/%[^/]/%[^/]/%s', $module, $directory, $class, $method);

            // set the module/controller directory location if found
            if ($this->locate([$module, $directory, $class])) {
                //reset to class/method
                switch ($sgs) {
                    case 1: $_route = $module.'/index';
                        break;
                    case 2: $_route = ($this->located < 2) ? $module.'/'.$directory : $directory.'/index';
                        break;
                    case 3: $_route = ($this->located == 2) ? $directory.'/'.$class : $class.'/index';
                        break;
                    case 4: $_route = ($this->located == 3) ? $class.'/'.$method : $method.'/index';
                        break;
                }
            }
        }
    }

    /**
     * [set_class description]
     *
     * @method set_class
     *
     * @param  [type]    $class [description]
     */
    public function set_class($class)
    {
        $suffix = $this->config->item('controller_suffix');
        // Fixing Error Message: strpos(): Non-string needles will be interpreted as strings in the future.
        // Use an explicit chr() call to preserve the current behavior.
        if ($suffix && strpos($class, $suffix) === false) {
            $class .= $suffix;
        }
        parent::set_class($class);
    }
}
