<?php declare(strict_types=1);

namespace Plainwork\Http;

use Plainwork\Core\App;

class Uri
{
    public $app = App::class;
    private $pathParam = '_';
    private $paramPrefix = '';
    private $pathSeparator = '-';
    private $passParamPrefix = '-';
    private array $hrefFuncs = [];
    private array $assetFuncs = [];

    public function setParamPrefix(string $prefix): self
    {
        $this->paramPrefix = $prefix;

        return $this;
    }

    public function getPathParam(): string
    {
        return $this->pathParam;
    }

    public function getParamPrefix(): string
    {
        return $this->paramPrefix;
    }

    public function normalizePath(string $path): string
    {
        if ('/' !== $this->pathSeparator) {
            $path = str_replace($this->pathSeparator, '/', $path);
        }

        return $path;
    }

    public function to(string $path, array $params = [], string $context = ''): string
    {
        $pathParam = $this->getPathParam();

        if ('/' !== $this->pathSeparator) {
            $pathValue = str_replace('/', $this->pathSeparator, $path);
        } else {
            $pathValue = $path;
        }
        $params[$pathParam] = $pathValue;

        $ks = array_keys($params);
        foreach ($ks as $k) {
            if (is_object($params[$k])) {
                $params[$k] = (string) $params[$k];
            }
        }

        $paramPrefix = $this->getParamPrefix();
        if ($paramPrefix) {
            $ks = array_keys($params);
            foreach ($ks as $k) {
                $k2 = $paramPrefix . $k;
                $params[$k2] = $params[$k];
                unset($params[$k]);
            }
        }

        return $this->href($params, $context);
    }

    public function asset(string $to): string
    {
        $func = [$this, 'defaultAssetFunc'];

        $funcs = $this->assetFuncs;
        usort($funcs, fn($a, $b) => ($a[1] - $b[1]));
        $funcs = array_column($funcs, 0);

        foreach ($funcs as $decorator) {
            $func = fn(string $to) => $decorator($to, $func);
        }

        return $func($to);
    }

    public function defaultAssetFunc(string $to): string
    {
        $ret = $to;
        $ret = 'app/' . $ret;

        $ver = $this->app->version();
        $ver = str_replace('.', '', $ver);

        $pos = strpos($ret, '?');
        $glue = (false === $pos) ? '?' : '&';
        $ret .= $glue . 'pwver=' . $ver;

        return $ret;
    }

    private function href(array $params, string $context): string
    {
        uksort($params, [$this, 'sortParams']);

        $func = [$this, 'defaultHrefFunc'];
        foreach ($this->hrefFuncs as $decorator) {
            $func = fn(array $params, string $context) => $decorator($params, $context, $func);
        }

        return $func($params, $context);
    }

    public function defaultHrefFunc(array $params, string $context): string
    {
        $ret = '';
        if ('system' === $context) {
            $ret = $this->currentUrl();
            $ret = $this->baseUrl($ret);
        }

        if ($params) {
            $ret .= (false === strpos($ret, '?')) ? '?' : '&';
            $ret .= http_build_query($params);
        }

        return $ret;
    }

    public function addHrefFunc(callable $func): self
    {
        $this->hrefFuncs[] = $func;

        return $this;
    }

    public function addAssetFunc(callable $func, int $order = 5): self
    {
        $this->assetFuncs[] = [$func, $order];

        return $this;
    }

    private function sortParams($a, $b, bool $stripPrefix = true)
    {
        if ($stripPrefix) {
            $paramPrefix = $this->getParamPrefix();
            if ($paramPrefix) {
                $a = substr($a, strlen($paramPrefix));
                $b = substr($b, strlen($paramPrefix));
            }
        }

        if ((substr($a, 0, 1) === $this->passParamPrefix) && (substr($b, 0, 1) === $this->passParamPrefix)) {
            return $this->sortParams(substr($a, 1), substr($b, 1), false);
        }
        if ((substr($a, 0, 1) === $this->passParamPrefix) && (substr($b, 0, 1) !== $this->passParamPrefix)) {
            return 1;
        }
        if ((substr($b, 0, 1) === $this->passParamPrefix) && (substr($a, 0, 1) !== $this->passParamPrefix)) {
            return -1;
        }

        return strcmp($a, $b);
    }

    private function currentUrl(): string
    {
        // $noPort = defined( 'HC3_URI_NO_PORT' ) && HC3_URI_NO_PORT ? true : false;
        $noPort = false;

        $ret = 'http';
        if (isset($_SERVER['HTTPS']) && ('on' == $_SERVER['HTTPS'])) {
            $ret .= 's';
        }
        $ret .= '://';

        $host = '';
        if (isset($_SERVER['HTTP_HOST']) && (80 != $_SERVER['SERVER_PORT'])) {
            if (false === strpos($_SERVER['HTTP_HOST'], ':')) {
                $host .= $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'];
            } else {
                $host .= $_SERVER['HTTP_HOST'];
            }
        } else {
            $host = $_SERVER['HTTP_HOST'];
        }

        if ($noPort) {
            $pos = strpos($host, ':');
            if (false !== $pos) {
                $host = substr($host, 0, $pos);
            }
        }

        $ret .= $host;

        if (!empty($_SERVER['REQUEST_URI'])) {
            $ret .= $_SERVER['REQUEST_URI'];
        } else {
            $ret .= $_SERVER['SCRIPT_NAME'];
        }

        $ret = urldecode($ret);

        return $ret;
    }

    private function baseUrl(string $url): string
    {
        $parsedUrl = parse_url($url);
        $ret = $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . $parsedUrl['path'];

        return $ret;

        // preg_match("/(.+)\?.*$/", $url, $ma);
        // if (isset($ma[1])) {
            // $url = $ma[1];
        // }

        // return $url;
    }
}
