<?php

use Mnv\Core\Collections\Arr;
use Mnv\Core\Collections\Collection;
use Mnv\Core\Collections\Contracts\DeferringDisplayableValue;
use Mnv\Core\Collections\Contracts\Htmlable;
use Mnv\Core\Collections\HigherOrderTapProxy;
use Mnv\Core\Database\Database;
use Mnv\Core\DB;
use Mnv\Http\Handler\ResponseFactory;
use Mnv\Http\Request;
use Mnv\Http\Response;

if (! function_exists('request')) {
    /**
     * Получите экземпляр текущего запроса или элемент ввода из запроса.
     *
     * @param  array|string|null  $key
     * @param  mixed  $default
     * @return \Mnv\Http\Request|string|array
     */
    function request($key = null, $default = null)
    {
        $request = Request::capture();

        if (is_null($key)) {
            return $request;
        }

        if (is_array($key)) {
            return $request->only($key);
        }

        $value = $request->__get($key);

        return is_null($value) ? value($default) : $value;
    }
}

if ( ! function_exists('response')) {
    /**
     * Response construction helper / Помощник по построению ответа
     *
     * @param string|null $content
     * @param int $statusCode
     * @param array $headers
     * @return ResponseFactory|Response
     */
    function response(string $content = null, int $statusCode = 200, array $headers = [])
    {
        $factory = new ResponseFactory;

        if (func_num_args() === 0) {
            return $factory;
        }

        return $factory->make($content, $statusCode, $headers);
    }

}

if (!function_exists('redirect')) {
    /**
     * Переадресация заголовка
     *
     * Переадресация заголовка в двух вариантах
     * Для очень детального управления заголовками вы можете использовать вывод
     *
     * @param	string	$uri URL
     * @param	string	$method	Redirect method 'auto', 'location' or 'refresh'
     * @param	int	$code HTTP Response status code
     *
     * @return	void
     */
    function redirect($uri = '', $method = 'auto', $code = NULL) {
//        if ( ! preg_match('#^(\w+:)?//#i', $uri)) {
//            $uri = '/';
//        }

        if ($method === 'auto' && isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== FALSE) {
            $method = 'refresh';
        } elseif ($method !== 'refresh' && (empty($code) OR ! is_numeric($code))) {
            if (isset($_SERVER['SERVER_PROTOCOL'], $_SERVER['REQUEST_METHOD']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.1') {
                $code = ($_SERVER['REQUEST_METHOD'] !== 'GET') ? 303 : 307;
            } else {
                $code = 302;
            }
        }

        switch ($method) {
            case 'refresh':
                header('Refresh:0;url=' . $uri);
                break;
            default:
                header('Location: '.$uri, TRUE, $code);
                break;
        }
        exit;
    }
}



if (!function_exists('getRequestVar')) {

    /**
     * @param string $varName  / название переменной
     * @param string $defaultVal / значение по умолчанию
     * @param bool $noEscape    // true - можно получать массив / false массив запрещен
     * @param bool $checkCookie  / устанавливать значение в куки
     * @return array|bool|string
     */
    function getRequestVar($varName = '', $defaultVal = '', $noEscape = false, $checkCookie = false)
    {
        global $smarty;

        if ($varName) {
            if (isset($_POST[$varName])) {
                $varValue = $_POST[$varName];
            } elseif (isset($_GET[$varName])) {
                $varValue = $_GET[$varName];
            } elseif (isset($_COOKIE[$varName]) && $checkCookie) {
                $varValue = $_COOKIE[$varName];
            }
        } elseif (!empty($_POST)) {
            $varValue = $_POST;
        } elseif (!empty($_GET)) {
            $varValue = $_GET;
        } elseif (!empty($_COOKIE) && $checkCookie) {
            $varValue = $_COOKIE;
        }

        if (isset($varValue)) {
            $smarty->assign($varName, $varValue);
            return $noEscape ? $varValue : arrAddSlashes($varValue);
        }

        if (!empty($defaultVal)) {
            $smarty->assign($varName, $defaultVal);
            return $noEscape ? $defaultVal : arrAddSlashes($defaultVal);
        }

        return false;
    }
}


if (!function_exists('connect')) {

    /**
     * Создать подключение к базе.
     *
     * @param null $table
     * @return Database
     */
    function connect($table = null): Database
    {
        $db = DB::init()->connect();
        if (!empty($table)) $db->table($table);

        return $db;
    }
}


if (!function_exists('collect')) {

    /**
     * Создайте коллекцию из заданного значения.
     *
     * @param  mixed $value
     * @return Collection
     */

    function collect($value = null): Collection
    {
        return new Collection($value);
    }
}

if (! function_exists('data_fill')) {
    /**
     * Fill in data where it's missing.
     *
     * @param  mixed  $target
     * @param  string|array  $key
     * @param  mixed  $value
     * @return mixed
     */
    function data_fill(&$target, $key, $value)
    {
        return data_set($target, $key, $value, false);
    }
}

if (! function_exists('data_get')) {
    /**
     * Get an item from an array or object using "dot" notation.
     *
     * @param  mixed  $target
     * @param  string|array|int|null  $key
     * @param  mixed  $default
     * @return mixed
     */
    function data_get($target, $key, $default = null)
    {
        if (is_null($key)) {
            return $target;
        }

        $key = is_array($key) ? $key : explode('.', $key);

        foreach ($key as $i => $segment) {
            unset($key[$i]);

            if (is_null($segment)) {
                return $target;
            }

            if ($segment === '*') {
                if ($target instanceof Collection) {
                    $target = $target->all();
                } elseif (! is_array($target)) {
                    return value($default);
                }

                $result = [];

                foreach ($target as $item) {
                    $result[] = data_get($item, $key);
                }

                return in_array('*', $key) ? Arr::collapse($result) : $result;
            }

            if (Arr::accessible($target) && Arr::exists($target, $segment)) {
                $target = $target[$segment];
            } elseif (is_object($target) && isset($target->{$segment})) {
                $target = $target->{$segment};
            } else {
                return value($default);
            }
        }

        return $target;
    }
}

if (! function_exists('data_set')) {
    /**
     * Set an item on an array or object using dot notation.
     *
     * @param  mixed  $target
     * @param  string|array  $key
     * @param  mixed  $value
     * @param  bool  $overwrite
     * @return mixed
     */
    function data_set(&$target, $key, $value, $overwrite = true)
    {
        $segments = is_array($key) ? $key : explode('.', $key);

        if (($segment = array_shift($segments)) === '*') {
            if (! Arr::accessible($target)) {
                $target = [];
            }

            if ($segments) {
                foreach ($target as &$inner) {
                    data_set($inner, $segments, $value, $overwrite);
                }
            } elseif ($overwrite) {
                foreach ($target as &$inner) {
                    $inner = $value;
                }
            }
        } elseif (Arr::accessible($target)) {
            if ($segments) {
                if (! Arr::exists($target, $segment)) {
                    $target[$segment] = [];
                }

                data_set($target[$segment], $segments, $value, $overwrite);
            } elseif ($overwrite || ! Arr::exists($target, $segment)) {
                $target[$segment] = $value;
            }
        } elseif (is_object($target)) {
            if ($segments) {
                if (! isset($target->{$segment})) {
                    $target->{$segment} = [];
                }

                data_set($target->{$segment}, $segments, $value, $overwrite);
            } elseif ($overwrite || ! isset($target->{$segment})) {
                $target->{$segment} = $value;
            }
        } else {
            $target = [];

            if ($segments) {
                data_set($target[$segment], $segments, $value, $overwrite);
            } elseif ($overwrite) {
                $target[$segment] = $value;
            }
        }

        return $target;
    }
}

if (! function_exists('head')) {
    /**
     * Get the first element of an array. Useful for method chaining.
     *
     * @param  array  $array
     * @return mixed
     */
    function head($array)
    {
        return reset($array);
    }
}

if (! function_exists('last')) {
    /**
     * Get the last element from an array.
     *
     * @param  array  $array
     * @return mixed
     */
    function last($array)
    {
        return end($array);
    }
}

if (! function_exists('value')) {
    /**
     * Return the default value of the given value.
     *
     * @param  mixed  $value
     * @return mixed
     */
    function value($value, ...$args)
    {
        return $value instanceof Closure ? $value(...$args) : $value;
    }
}


if (! function_exists('e')) {
    /**
     * Экранируйте HTML-объекты в строке.
     *
     * @param  DeferringDisplayableValue|Htmlable|string|null  $value
     * @param  bool  $doubleEncode
     * @return string
     */
    function e($value, $doubleEncode = true)
    {
        if ($value instanceof DeferringDisplayableValue) {
            $value = $value->resolveDisplayableValue();
        }

        if ($value instanceof Htmlable) {
            return $value->toHtml();
        }

        return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8', $doubleEncode);
    }
}

if (! function_exists('class_basename')) {
    /**
     * Get the class "basename" of the given object / class.
     *
     * @param  string|object  $class
     * @return string
     */
    function class_basename($class)
    {
        $class = is_object($class) ? get_class($class) : $class;

        return basename(str_replace('\\', '/', $class));
    }
}

if (! function_exists('arrAddSlashes')) {
    /**
     * @param $var
     * @return array|string
     */
    function arrAddSlashes($var)
    {
        if (is_array($var)) {
            $var = array_map('arrAddSlashes', $var);
        } elseif (is_object($var)) {
            $vars = get_object_vars($var);
            foreach ($vars as $k => $v) {
                $var->{$k} = addslashes($v);
            }
        } else {
            $var = addslashes($var);
        }

        return $var;
    }
}

if (! function_exists('arrStripSlashes')) {
    /**
     * @param $var
     * @return array|string
     */
    function arrStripSlashes($var)
    {
        if (is_array($var)) {
            $var = array_map('arrStripSlashes', $var);
        } elseif (is_object($var)) {
            $vars = get_object_vars($var);
            foreach ($vars as $k => $v) {
                $var->{$k} = arrStripSlashes($v);
            }
        } else {
            $var = stripslashes($var);
        }
        return $var;
    }
}

// MODEL
if (! function_exists('class_uses_recursive')) {
    /**
     * Returns all traits used by a class, its parent classes and trait of their traits.
     *
     * @param  object|string  $class
     * @return array
     */
    function class_uses_recursive($class)
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        $results = [];

        foreach (array_reverse(class_parents($class)) + [$class => $class] as $class) {
            $results += trait_uses_recursive($class);
        }

        return array_unique($results);
    }
}

if (! function_exists('trait_uses_recursive')) {
    /**
     * Returns all traits used by a trait and its traits.
     *
     * @param  string  $trait
     * @return array
     */
    function trait_uses_recursive($trait)
    {
        $traits = class_uses($trait);

        foreach ($traits as $trait) {
            $traits += trait_uses_recursive($trait);
        }

        return $traits;
    }
}

if (! function_exists('tap')) {
    /**
     * Call the given Closure with the given value then return the value.
     *
     * @param  mixed  $value
     * @param  callable|null  $callback
     * @return mixed
     */
    function tap($value, $callback = null)
    {
        if (is_null($callback)) {
            return new HigherOrderTapProxy($value);
        }

        $callback($value);

        return $value;
    }
}

if (! function_exists('transform')) {
    /**
     * Transform the given value if it is present.
     *
     * @param  mixed  $value
     * @param  callable  $callback
     * @param  mixed  $default
     * @return mixed|null
     */
    function transform($value, callable $callback, $default = null)
    {
        if (filled($value)) {
            return $callback($value);
        }

        if (is_callable($default)) {
            return $default($value);
        }

        return $default;
    }
}
if (! function_exists('filled')) {
    /**
     * Determine if a value is "filled".
     *
     * @param  mixed  $value
     * @return bool
     */
    function filled($value)
    {
        return ! blank($value);
    }
}
if (! function_exists('blank')) {
    /**
     * Determine if the given value is "blank".
     *
     * @param  mixed  $value
     * @return bool
     */
    function blank($value)
    {
        if (is_null($value)) {
            return true;
        }

        if (is_string($value)) {
            return trim($value) === '';
        }

        if (is_numeric($value) || is_bool($value)) {
            return false;
        }

        if ($value instanceof Countable) {
            return count($value) === 0;
        }

        return empty($value);
    }
}

if (! function_exists('fs_file_get_contents')) {

    /**
     * @throws Exception
     */
    function fs_file_get_contents($file, $flags = 'r')
    {
        if (FS_READ_MODE == 'fopen') {
            if (!file_exists($file)) throw new Exception("Ошибка: файл $file не существует!");
            if (!filesize($file)) throw new Exception("Файл $file пустой!");
            $f = fopen($file, $flags);
            $content = fread($f, filesize($file));
            fclose($f);

            return $content;
        } else if (FS_READ_MODE == 'file_get_contents') {

            return file_get_contents($file);

        } else if (FS_READ_MODE == 'file_read') {

            return file_read($file);
        }

        return null;
    }
}

if (! function_exists('windows_os')) {
    /**
     * Determine whether the current environment is Windows based.
     *
     * @return bool
     */
    function windows_os()
    {
        return PHP_OS_FAMILY === 'Windows';
    }
}