<?php

/**
 * @package   Gantry5
 * @author    RocketTheme http://www.rockettheme.com
 * @copyright Copyright (C) 2007 - 2021 RocketTheme, LLC
 * @license   Dual License: MIT or GNU/GPLv2 and later
 *
 * http://opensource.org/licenses/MIT
 * http://www.gnu.org/licenses/gpl-2.0.html
 *
 * Gantry Framework code that extends GPL code is considered GNU/GPLv2 and later
 */

namespace Gantry\Framework\Base;

use Gantry\Component\Config\Config;
use Gantry\Component\System\Messages;
use Gantry\Debugger;
use Gantry\Framework\Document;
use Gantry\Framework\Menu;
use Gantry\Framework\Outlines;
use Gantry\Framework\Page;
use Gantry\Framework\Platform;
use Gantry\Framework\Positions;
use Gantry\Framework\Request;
use Gantry\Framework\Services\ConfigServiceProvider;
use Gantry\Framework\Services\StreamsServiceProvider;
use Gantry\Framework\Site;
use Gantry\Framework\Translator;
use Gantry5\Loader;
use RocketTheme\Toolbox\DI\Container;
use RocketTheme\Toolbox\Event\Event;
use RocketTheme\Toolbox\Event\EventDispatcher;

/**
 * Class Gantry
 * @package Gantry\Framework\Base
 */
abstract class Gantry extends Container
{
    /** @var static|null */
    protected static $instance;

    /** @var mixed */
    protected $wrapper;

    /**
     * @return static
     */
    public static function instance()
    {
        $instance = self::$instance;
        if (null === $instance) {
            $instance = static::restart();

            if (!defined('GANTRY5_DEBUG')) {
                define('GANTRY5_DEBUG', $instance->debug());
            }
        }

        return $instance;
    }

    /**
     * @return static
     */
    public static function restart()
    {
        self::$instance = static::init();

        return self::$instance;
    }

    /**
     * Returns true if debug mode has been enabled.
     *
     * @return boolean
     */
    public function debug()
    {
        /** @var Config $global */
        $global = $this['global'];

        return $global->get('debug', false);
    }

    /**
     * Returns true if we are in administration.
     *
     * @return boolean
     */
    public function admin()
    {
        return defined('GANTRYADMIN_PATH');
    }


    /**
     * @return string
     */
    public function siteUrl()
    {
        /** @var Document $document */
        $document = $this['document'];

        return $document::siteUrl();
    }

    /**
     * @param string $location
     * @return array
     */
    public function styles($location = 'head')
    {
        /** @var Document $document */
        $document = $this['document'];

        return $document::getStyles($location);
    }

    /**
     * @param string $location
     * @return array
     */
    public function scripts($location = 'head')
    {
        /** @var Document $document */
        $document = $this['document'];

        return $document::getScripts($location);
    }

    /**
     * Load Javascript framework / extension in platform independent way.
     *
     * @param string $framework
     * @return bool
     */
    public function load($framework)
    {
        /** @var Document $document */
        $document = $this['document'];

        return $document::addFramework($framework);
    }

    /**
     * Lock the variable against modification and return the value.
     *
     * @param string $id
     * @return mixed
     */
    public function lock($id)
    {
        $value = $this[$id];

        try {
            // Create a dummy service.
            $this[$id] = static function () use ($value) {
                return $value;
            };
        } catch (\RuntimeException $e) {
            // Services are already locked, so ignore the error.
        }

        // Lock the service and return value.
        return $this[$id];
    }

    /**
     * Fires an event with optional parameters.
     *
     * @param  string $eventName
     * @param  Event  $event
     * @return Event
     */
    public function fireEvent($eventName, Event $event = null)
    {
        /** @var EventDispatcher $events */
        $events = $this['events'];

        /** @var Event $event */
        $event = $events->dispatch($eventName, $event);

        return $event;
    }

    /**
     * @param string $path
     * @return string
     */
    public function route($path)
    {
        $routes = $this->offsetGet('routes');
        $route = isset($routes[$path]) ? $routes[$path] : $routes[1];

        if (!$route) {
            return $this->offsetGet('base_url');
        }

        $path = implode('/', array_filter(func_get_args(), static function($var) { return isset($var) && $var !== ''; }));

        // rawurlencode() the whole path, but keep the slashes.
        $path = preg_replace(['|%2F|', '|%25|'], ['/', '%'], rawurlencode($path));

        return preg_replace('|/+|', '/', '/' . $this->offsetGet('base_url') . sprintf($route, $path));
    }

    /**
     * @param string $action
     * @param string|null $id
     * @return bool
     */
    public function authorize($action, $id = null)
    {
        /** @var Platform $platform */
        $platform = $this['platform'];

        return $platform->authorize($action, $id);
    }

    /**
     * @param mixed|null $value
     * @return mixed|null
     */
    public function wrapper($value = null)
    {
        if ($value !== null) {
            $this->wrapper = $value;
        }

        return $this->wrapper;
    }

    /**
     * @return static
     */
    protected static function init()
    {
        $instance = new static();

        if (\GANTRY_DEBUGGER) {
            $instance['debugger'] = Debugger::instance();
        }

        $instance['loader'] = Loader::get();

        $instance->register(new ConfigServiceProvider);
        $instance->register(new StreamsServiceProvider);

        $instance['request'] = static function () {
            return new Request();
        };

        $instance['events'] = static function () {
            return new EventDispatcher();
        };

        $instance['platform'] = static function ($c) {
            return new Platform($c);
        };

        $instance['translator'] = static function () {
            return new Translator();
        };

        $instance['site'] = static function () {
            return new Site();
        };

        $instance['menu'] = static function () {
            return new Menu();
        };

        $instance['messages'] = static function () {
            return new Messages();
        };

        $instance['page'] = static function ($c) {
            return new Page($c);
        };

        $instance['document'] = static function () {
            return new Document();
        };

        // Make sure that nobody modifies the original collection by making it a factory.
        $instance['outlines'] = $instance->factory(static function ($c) {
            static $collection;
            if (!$collection) {
                $collection = (new Outlines($c))->load();
            }

            return $collection->copy();
        });

        // @deprecated 5.3
        $instance['configurations'] = $instance->factory(static function ($c) {
            if (\GANTRY_DEBUGGER) {
                Debugger::addMessage('Depredated call: gantry.configurations');
            }

            static $collection;
            if (!$collection) {
                $collection = (new Outlines($c))->load();
            }

            return $collection->copy();
        });

        $instance['positions'] = $instance->factory(static function ($c) {
            static $collection;
            if (!$collection) {
                $collection = (new Positions($c))->load();
            }

            return $collection->copy();
        });

        $instance['global'] = static function (Gantry $c) {
            $data = $c->loadGlobal() + [
                    'debug' => false,
                    'production' => true,
                    'use_media_folder' => false,
                    'asset_timestamps' => true,
                    'asset_timestamps_period' => 7,
                    'compile_yaml' => true,
                    'compile_twig' => true,
                    'offline_message'  => ''
                ];

            return new Config($data);
        };

        return $instance;
    }

    /**
     * Unicode-safe version of PHP’s pathinfo() function.
     *
     * @link  https://www.php.net/manual/en/function.pathinfo.php
     *
     * @param string $path
     * @param int|null $flags
     * @return array|string
     */
    public static function pathinfo($path, $flags = null)
    {
        $path = str_replace(['%2F', '%5C'], ['/', '\\'], rawurlencode($path));

        if (null === $flags) {
            $info = pathinfo($path);
        } else {
            $info = pathinfo($path, (int)$flags);
        }

        if (is_array($info)) {
            return array_map('rawurldecode', $info);
        }

        return rawurldecode($info);
    }

    /**
     * Unicode-safe version of the PHP basename() function.
     *
     * @link  https://www.php.net/manual/en/function.basename.php
     *
     * @param string $path
     * @param string $suffix
     * @return string
     */
    public static function basename($path, $suffix = '')
    {
        return rawurldecode(basename(str_replace(['%2F', '%5C'], '/', rawurlencode($path)), $suffix));
    }

    /**
     * Check if Gantry is compatible with your theme / extension.
     *
     * This function can be used to make sure that user has installed Gantry version
     * that has been tested to work with your extension. All existing functions should
     * be backwards compatible, but each release can add some new functionality, which
     * you may want to use.
     *
     * <code>
     * if ($gantry->isCompatible('5.0.1')) {
     *      // You can do it in the new way.
     * } else {
     *     // Revert to the old way to display an error message.
     * }
     * </code>
     *
     * @param string $version Minimum required version.
     *
     * @return boolean Yes, if it is safe to use Gantry Framework.
     */
    public function isCompatible($version)
    {
        // If requested version is smaller than 5.0-rc, it's not compatible.
        if (version_compare($version, '5.0-rc', '<')) {
            return false;
        }

        // Development version support.
        if ($version === '5.3' || $this->isDev()) {
            return true;
        }

        // Check if future version is needed.
        if (version_compare($version, GANTRY5_VERSION, '>')) {
            return false;
        }

        return true;
    }

    /**
     * Check if Gantry is running from a Git repository or is a CI build.
     *
     * Developers tend to do their work directly in the Git repositories instead of
     * creating and installing new builds after every change. This function can be
     * used to check the condition and make sure we do not break users repository
     * by replacing files during upgrade.
     *
     * @return boolean True if Git repository or CI build is detected.
     */
    public function isDev()
    {
        return '@version@' === GANTRY5_VERSION || strpos(GANTRY5_VERSION, 'dev-') === 0;
    }

    /**
     * @return array
     */
    protected function loadGlobal()
    {
        return [];
    }
}
