<?php

namespace Mnv\Core;

use Closure;
use Smarty;
use SmartyException;
use RuntimeException;
use Mnv\Core\Singleton\SingletonTrait;

/**
 * Class Application
 * custom class Smarty
 *
 * @package System
 */
final class Design extends Smarty
{

    use SingletonTrait;

    /** @var bool */
    private $smartySecurity = false;

    /** @var string */
    private $defaultTemplateDir;

    /** @var array */
    private $smartyModifiers = [];

    /** @var array */
    private $smartyFunctions = [];

    /**
     * @var array
     */
    private $allowedPhpFunctions = [
        'escape',
        'cat',
        'count',
        'in_array',
        'nl2br',
        'str_replace',
        'reset',
        'floor',
        'round',
        'ceil',
        'max',
        'min',
        'number_format',
        'print_r',
        'var_dump',
        'printa',
        'file_exists',
        'stristr',
        'strtotime',
        'empty',
        'urlencode',
        'intval',
        'isset',
        'sizeof',
        'is_array',
        'array_intersect',
        'time',
        'array',
        'base64_encode',
        'implode',
        'explode',
        'preg_replace',
        'preg_match',
        'key',
        'json_encode',
        'json_decode',
        'is_file',
        'date',
        'strip_tags',
        'preg_quote'
    ];

    /**
     * Design constructor.
     * @throws SmartyException
     */
    public function __construct()
    {
        parent::__construct();

        /**
         * @var  $compile_check
         * При каждом вызове РНР-приложения Smarty проверяет, изменился или нет текущий шаблон с момента последней компиляции.
         * Если шаблон изменился, он перекомпилируется.
         * В случае, если шаблон еще не был скомпилирован, его компиляция производится с игнорированием значения этого параметра.
         * По умолчанию эта переменная установлена в true.
         * В момент, когда приложение начнет работать в реальных условиях (шаблоны больше не будут изменяться),
         * этап проверки компиляции становится ненужным.
         * В этом случае проверьте, чтобы переменная $compile_check была установлена в "false" для достижения максимальной производительности.
         * Учтите, что если вы присвоите этой переменной значение "false", и файл шаблона будет изменен, вы *НЕ* увидите изменений в выводе шаблона до тех пор,
         * пока шаблон не будет перекомпилирован.
         * Если caching и compile_check активированы, файлы кэша будут регенерированы при обновлении связанных с ним шаблонов или конфигурационных файлов
         */
        $this->compile_check   = Smarty::COMPILECHECK_ON;
        $this->error_reporting = E_ALL & ~E_NOTICE;

//        $allowedPhpFunctions = ['escape', 'cat', 'count', 'in_array', 'nl2br', 'str_replace', 'reset', 'floor', 'round', 'ceil', 'max', 'min', 'number_format', 'print_r', 'var_dump', 'printa', 'file_exists', 'stristr', 'strtotime', 'empty', 'urlencode', 'intval', 'isset', 'sizeof', 'is_array', 'array_intersect', 'time', 'array', 'base64_encode', 'implode', 'explode', 'preg_replace', 'preg_match', 'key', 'json_encode', 'json_decode', 'is_file', 'date', 'strip_tags'];

        if ($this->smartySecurity) {
            $this->enableSecurity();
            $this->security_policy->php_modifiers = $this->allowedPhpFunctions;
            $this->security_policy->php_functions = $this->allowedPhpFunctions;
            $this->security_policy->secure_dir = array(
                $this->defaultTemplateDir() . '/templates/',
                GLOBAL_ROOT . 'admin/templates',
                GLOBAL_ROOT . 'app',
            );
        }

        $this->setConfigDir(GLOBAL_ROOT . '/includes/smarty_config');
        $this->setCompileDir($this->tmpPath() . 'compile');
        $this->setCompileId('main' . SITE_LANG_POSTFIX.  '-' . Config::getValue('theme'));
        $this->setTemplateDir($this->defaultTemplateDir());

        // Создаем папку для скомпилированных шаблонов текущей темы
        if (!is_dir($this->getCompileDir()) && !mkdir($concurrentDirectory = $this->getCompileDir(), 0777) && !is_dir($concurrentDirectory)) {
            throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory));
        }

        $this->setCacheDir($this->tmpPath() . 'cache');

        $this->addPluginsDir(GLOBAL_ROOT . '/includes/smarty_plugins');
        $this->default_modifiers = array('escape');

        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'in_array',         array($this, 'smarty_modifier_in_array'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'unset_key',        array($this, 'smarty_modifier_unset_key'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'sizeof',           array($this, 'smarty_modifier_sizeof'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'to_json',          array($this, 'smarty_modifier_to_json'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'upper',            array($this, 'smarty_modifier_upper'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'plural',           array($this, 'smarty_modifier_plural'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'first',		    array($this, 'smarty_modifier_first'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'array_first',	    array($this, 'smarty_modifier_array_first'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'element',		    array($this, 'fetch_from_array_modifier'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'remove_element',	array($this, 'fetch_from_array_remove_element_modifier'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'format_time',      array($this, 'smarty_modifier_format_time'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'fsize_format',     array($this, 'smarty_modifier_fsize_format'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'string_format',    array($this, 'smarty_modifier_string_format'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'format_phone',     array($this, 'smarty_modifier_format_phone'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'strip_phone',      array($this, 'smarty_modifier_strip_phone'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'page_break', 		array($this, 'smarty_modifier_page_break'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'break', 		    array($this, 'smarty_modifier_break'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'substr', 		    array($this, 'smarty_modifier_substr'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'preg_quote', 		array($this, 'smarty_modifier_preg_quote'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'strtotime', 		array($this, 'smarty_modifier_strtotime'));
        $this->registerPlugin(Smarty::PLUGIN_MODIFIER, 'print_r', 		    array($this, 'smarty_modifier_print_r'));

        $this->registerPlugin(Smarty::PLUGIN_FUNCTION, 'image_file_name_exists',   array($this, 'image_file_name_exists'));

        try {
            $this->registerPlugin(Smarty::PLUGIN_BLOCK, 'nocache',  array($this, 'smarty_block_nocache'), false, '');
        } catch (SmartyException $e) {
            print $e->getMessage();
        }

    }
    /** @return Design */
    public function design(): Design
    {
        return $this;
    }

    /** @return string */
    public function tmpPath(): string
    {
        return sprintf('%s/temp/smarty/', GLOBAL_ROOT);
    }

    public function defaultTemplateDir(): string
    {
        return sprintf('%s/themes/' . Config::getValue('theme'), GLOBAL_ROOT);
    }

    public function defaultAdminTemplateDir(): string
    {
        return sprintf('%s/admin/templates', GLOBAL_ROOT);
    }

    /**
     * @param int $num
     * @param string|null $time
     */
    public function caching(int $num, ?string $time): void
    {
        $this->caching = $num;
        if (!empty($time)) {
            $this->cache_lifetime = $time;
        }
    }

    /** Установка директории файлов шаблона(отображения) */
    public function setTemplatesDir($dir): void
    {
        $dir = rtrim($dir, '/') . '/';
        if (!is_string($dir)) {
            throw new RuntimeException("Param \$dir must be string");
        }

        $this->defaultTemplateDir = $dir;
        $this->setSmartyTemplatesDir($dir);
    }

    public function setSmartyTemplatesDir($dir): void
    {
        $this->setTemplateDir($dir);
    }

    /** ************************        ***************************** */

    /** не удалять никогда смотреть SmartyCustom */
    public function smarty_block_nocache($param, $content, &$smarty)
    {
        return $content;
    }

    /** ************************   MODIFIER   ***************************** */

    public function smarty_modifier_in_array($needle, $haystack)
    {
        if (!empty($haystack) && !is_array($haystack)) {
            $haystack = array($haystack);
        }

        if (is_array($haystack)) {
            return in_array($needle, $haystack);
        }

        return false;
    }


    public function smarty_modifier_substr($string, $start, $length)
    {
        if (is_null($string)) {
            return null;
        }

        $start = (int)$start;
        $start = $start <= strlen($string) && $start >= -strlen($string) ? $start : 0;
        $length = $length ? (int)$length : null;

        return substr($string, $start, $length);
    }

    public function smarty_modifier_preg_quote($string): string
    {
        return preg_quote($string, '/');
    }

    public function smarty_modifier_strtotime($string): string
    {
        return strtotime($string);
    }

    function smarty_modifier_upper($string)
    {
        return strtoupper($string);
    }

    public function smarty_modifier_print_r($string)
    {
        return print_r($string, true);
    }

    function smarty_modifier_unset_key(array $array, $key)
    {
        unset($array[$key]);
        return $array;
    }




    /**
     * @param array|null $params
     * @param $key
     * @param $value
     * @param $as
     * @return false|mixed|null
     */
    public function fetch_from_array_modifier(?array $params, $key, $value, $as = null)
    {
        if (!is_array($params)) {
            return false;
        }

        $result = null;
        foreach ($params as $param) {
            if (isset($param[$key])) {
                if ($param[$key] !== $value) {
                    continue;
                }
                if (!empty($as)) {
                    $result = $param[$as];
                } else {
                    $result = $param;
                }
            }
        }

        return $result;
    }

    /**
     * @param array|null $array
     * @param $value
     * @return false|mixed|null
     */
    public function fetch_from_array_remove_element_modifier(?array $array, $value)
    {
        if (!is_array($array)) {
            return false;
        }

        unset($array[array_search($value, $array, true)]);
        return implode(',', $array);
    }

    /**
     * @param array $params
     * @return false|mixed
     */
    public function smarty_modifier_first(array $params = [])
    {
        if (!is_array($params)) {
            return false;
        }

        return reset($params);
    }


    public function smarty_modifier_array_first($array, $key,  callable $callback = null, $default = null)
    {
        if (is_null($callback)) {
            if (empty($array)) {
                return $default instanceof Closure ? $default() : $default;
            }

//            print_r($array);
            foreach ($array as $item) {
                return $item[$key];
            }
        }

        foreach ($array as $key => $value) {
            if ($callback($value, $key)) {
                return $value;
            }
        }

        return $default instanceof Closure ? $default() : $default;
    }


    /**
     * @param $string
     * @param string $format
     * @return false|string|void
     */
    public function smarty_modifier_format_time($string, $format = 'r')
    {
        if ($string !== '') {
            return date($format, $string);
        }

        return;
    }

    /**
     * @param $size
     * @param string $format
     * @param int $precision
     * @param string $dec_point
     * @param string $thousands_sep
     * @return string
     */
    public function smarty_modifier_fsize_format($size, $format = '', $precision = 2, $dec_point = ".", $thousands_sep = ","): string
    {
        $format = strtoupper($format);

        static $sizes = array();

        if (!count($sizes)) {
            $b = 1024;
            $sizes["B"]        =    1;
            $sizes["KB"]    =    $sizes["B"]  * $b;
            $sizes["MB"]    =    $sizes["KB"] * $b;
            $sizes["GB"]    =    $sizes["MB"] * $b;
            $sizes["TB"]    =    $sizes["GB"] * $b;

            $sizes = array_reverse($sizes,true);
        }

        //~ get "human" filesize
        foreach($sizes AS $unit => $bytes) {
            if($size > $bytes || $unit === $format) {
                //~ return formatted size
                return number_format($size / $bytes, $precision, $dec_point, $thousands_sep)." ".$unit;
            } //~ end if
        } //~ end foreach

        return '';
    }


    /**
     * @param $string
     * @param $format
     * @return string
     */
    public function smarty_modifier_string_format($string, $format): string
    {
        return sprintf($format, $string);
    }

    /**
     * @param $phone
     * @return array|string|string[]|null
     */
    public function smarty_modifier_format_phone($phone)
    {
        return \Mnv\Core\Utilities\Mask::init()->phone($phone);
    }

    /**
     * @param $phone
     * @return array|string|string[]|null
     */
    public function smarty_modifier_format_phone_old($phone)
    {
        $phone = preg_replace('/[^0-9]/', '', $phone);
        return preg_replace('/([0-9]{3})([0-9]{2})([0-9]{3})([0-9]{2})([0-9]{2})/', '+$1 ($2) $3-$4-$5', $phone);
    }

    /**
     * @param $phone
     * @return string
     */
    public function smarty_modifier_strip_phone($phone): string
    {
        $phone = preg_replace('/[^0-9]/', '', $phone);
        return '+' . $phone;
    }

    /**
     * @param $number
     * @param $singular
     * @param $plural1
     * @param null $plural2
     * @return mixed
     */
    public function smarty_modifier_plural($number, $singular, $plural1, $plural2 = null)
    {
        $number = abs($number);
        if(!empty($plural2)) {
            $p1 = $number%10;
            $p2 = $number%100;
            if ($number === 0) {
                return $plural1;
            }
            if ($p1 === 1 && !($p2 >= 11 && $p2 <= 19)) {
                return $singular;
            }

            if ($p1 >= 2 && $p1 <= 4 && !($p2 >= 11 && $p2 <= 19)) {
                return $plural2;
            }

            return $plural1;
        }

        if ($number === 1) {
            return $singular;
        }

        return $plural1;
    }

    /**
     * @param $params
     * @return string|null
     */
    public function image_file_name_exists($params): ?string
    {
        $filePath   = fetch_getParam('filePath', $params);
        $class      = fetch_getParam('class', $params);
        $style      = fetch_getParam('style', $params);

        $fileRoot = GLOBAL_ROOT . $filePath;
        if (file_exists($fileRoot)) {
            return '<img src="'. GLOBAL_URL. $filePath . '" class="'.$class.'" style="' . $style . '"/>' ;
        }

        return null;
    }

    /**
     * @param $content
     * @param null $key
     * @return array|false|mixed|string|string[]
     */
    public function smarty_modifier_page_break($content, $key = NULL)
    {
        if (empty($content)) {
            return false;
        }

        if (stripos($content, '{PAGEBREAK}') !== false) {
//            $contentNotParse = $content;

            if (!is_null($key)) {
                $content = explode('{PAGEBREAK}', $content);
//                print_r($content);
//                return !empty($content[$key]) ? str_replace('{PAGEBREAK}', '', $contentNotParse) : '';
                return !empty($content[$key]) ? $content[$key] : '';
            }

            return str_replace('{PAGEBREAK}', '', $content);

        }

        return $content;

    }

    public function smarty_modifier_break($content)
    {
        if (empty($content)) {
            return false;
        }

        if (stripos($content, '{PAGEBREAK}') !== false) {
            $content = explode('{PAGEBREAK}', $content);
            return str_replace('{PAGEBREAK}', '', $content);
        }

        return $content;

    }

    /**
     * Counts all elements in an array, or something in an object
     *
     * @param mixed $value
     *
     * @return int
     */
    function smarty_modifier_sizeof($value)
    {
        if (is_array($value) || (is_object($value) && $value instanceof \Countable)) {
            return count($value);
        }

        return 0;
    }

    function smarty_modifier_to_json($data)
    {
        if (function_exists('json_encode')) {
            return json_encode($data);
        }

        if (is_null($data)) {
            $content = 'null';

        } elseif ($data === false) {
            $content = 'false';

        } elseif ($data === true) {
            $content = 'true';

        } elseif (is_array($data)) {
            $result = array();
            $akeys = array_keys($data);
            $diff = array_diff($akeys, range(0, sizeof($akeys) - 1));
            $is_list = empty($diff);

            if ($is_list) {
                foreach ($data as $v) {
                    $result[] = smarty_modifier_to_json($v);
                }
                $content = '[' . join(',', $result) . ']';
            } else {
                foreach ($data as $k => $v) {
                    $result[] = smarty_modifier_to_json($k) . ':' . smarty_modifier_to_json($v);
                }
                $content = '{' . join(',', $result) . '}';
            }
        } else {
            $content = empty($data) ? "''" : (is_string($data) ? "'" . fn_js_escape($data) . "'" : $data);
        }

        return $content;
    }

}