<?php

namespace Plainwork\Http;

use Plainwork\Http\Request;
use Plainwork\Http\Uri;
use Plainwork\Http\Router;
use Plainwork\Core\Helper;

class RequestFactory
{
    public $uri = Uri::class;
    public $router = Router::class;
    private static string $passParamPrefix = '-';
    private static string $tempParamSuffix = '-';

    public function fromGlobals(string $prefix = '', string $rootPath = ''): Request
    {
        $ret = new Request($prefix);
        $ret->setLevelPrefix(static::$passParamPrefix);

        $requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET';
        $ret = $ret->withRequestMethod($requestMethod);

    // headers
        foreach ($_SERVER as $name => $value) {
            if (substr($name, 0, 5) == 'HTTP_') {
                // $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
                $name = substr($name, strlen('HTTP_'));
                $ret = $ret->withHeader($name, $value);
            }
        }

    // query params
        $params = [];
        foreach ($_GET as $k => $v) {
            if (strlen($prefix)) {
                if (substr($k, 0, strlen($prefix)) === $prefix) {
                    $k = substr($k, strlen($prefix));
                } else {
                    continue;
                }
            }
            $params[$k] = $v;
        }
        if ($params) {
            $params = array_map([$this, 'cleanInput'], $params);
            $ret = $ret->withQueryParams($params);
        }

    // post params
        $params = [];
        foreach ($_POST as $k => $v) {
            if (strlen($prefix)) {
                if (substr($k, 0, strlen($prefix)) === $prefix) {
                    $k = substr($k, strlen($prefix));
                } else {
                    continue;
                }
            }

            $params[$k] = $v;
        }
        if ($params) {
            $params = array_map([$this, 'cleanInput'], $params);
            $ret = $ret->withPostParams($params);
        }

    // body
        $body = file_get_contents('php://input');
        $ret = $ret->withBody($body);

    // json params
        if (strlen($body)) {
            $parsedBody = json_decode($body, true);
            if ((null === $parsedBody) && (JSON_ERROR_NONE !== json_last_error())) {
                // Ensure subsequent calls receive error instance.
                // $this->parsed_json = false;

                $error = [
                    'json_error_code'    => json_last_error(),
                    'json_error_message' => json_last_error_msg(),
                ];

                $parsedBody = [];
                // return new WP_Error( 'rest_invalid_json', __( 'Invalid JSON body passed.' ), $error_data );
            }
            $ret = $ret->withParsedBody($parsedBody);
        }

    // determine and set path
        $pathParam = $this->uri->getPathParam();
        $path = $ret->getQueryParam($pathParam) ?? '';
        $path = $this->uri->normalizePath($path);

    // if root path is supplied then allow only within this path
        if (strlen($rootPath)) {
            if (strlen($path)) {
                if (($rootPath . '/') !== substr($path, 0, strlen($rootPath) + 1)) {
                    $path = '';
                }
            }
            if (!strlen($path)) {
                $path = $rootPath;
            }
        }

    // resolve shortcuts
        while ($shortcutTo = $this->router->isShortcut($path)) {
            $path = $shortcutTo;
        }

    // don't need route as param anymore
        $ret = $ret->withRequestedPath($path);
        $ret = $ret->withPath($path);
        $ret = $ret->withQueryParam($pathParam, null);

    // find route pattern
        list($routePattern, $routeParams) = $this->router->parsePath($path);
        if (null === $routePattern) {
            return $ret;
        }

// echo "<br>PATH '$path' RESOLVE AS '$routePattern'<br>";
        $ret = $ret->withRoutePattern($routePattern);
        $ret = $ret->withRouteParams($routeParams);

    // with route we can now filter params
        $params = $ret->getQueryParams();
        foreach (array_keys($params) as $k) {
        // is parent's param?
            list($levelUp, $parentParamName) = $this->resolveParentLevelOfParam($k);
            if ($levelUp > 0) {
                $routePattern2 = join('/', array_slice(explode('/', $routePattern), 0, -$levelUp));
                $v = $this->router->filterParam($routePattern2, $parentParamName, $params[$k]);
            } else {
                $v = $this->router->filterParam($routePattern, $k, $params[$k]);
            }

            if ($v !== $params[$k]) {
                $ret = $ret->withQueryParam($k, $v);
            }
        }

        return $ret;
    }

    private function resolveParentLevelOfParam(string $paramName): array
    {
        $ret = 0;

        $prefixLen = strlen(self::$passParamPrefix);
        while (self::$passParamPrefix === substr($paramName, 0, $prefixLen)) {
            $ret++;
            $paramName = substr($paramName, $prefixLen);
        }

        return [$ret, $paramName];
    }

    public function fromOrParent(Request $request, string $path, array $params)
    {
        $request_ = $this->from($request, $path, $params);
        $path = $request_->getPath();
        while ($shortcutTo = $this->router->isShortcut($path)) {
            $path = $shortcutTo;
        }

    // if has no this path then go to parent
        while (!$this->router->hasPath($path)) {
            $request_ = $this->from($request_, '..', $params);
            $path = $request_->getPath();
        }

        return $request_;
    }


    public function from(Request $request, string $path, array $params = []): Request
    {
        $path = $request->resolvePath($path);
        $currentPath = $request->getPath();

        // if ($currentPath === $path) {
            // foreach ($params as $k => $v) {
                // $request = $request->withQueryParam($k, $v);
            // }

            // return $request;
        // }

        $ret = $request->withPath($path);

        $currentParams = $request->getQueryParams();
        $passParams = $this->passParams($currentPath, $path, $currentParams);

        foreach ($passParams as $k => $v) {
            if (!array_key_exists($k, $params)) {
            // if (!isset($params[$k]) ){
                $params[$k] = $v;
            }
        }
        $ret = $ret->withQueryParams($params);

    // find route pattern
        list($routePattern, $routeParams) = $this->router->parsePath($path);
        $ret = $ret->withRoutePattern($routePattern);
        $ret = $ret->withRouteParams($routeParams);

        return $ret;
    }

    private function passParams(string $fromPath, string $toPath, array $params)
    {
        $ret = [];

    /* keep temp params? */
        $keepTemp = false;
        if (array_key_exists(static::$tempParamSuffix, $params) && (true === $params[static::$tempParamSuffix])) {
            $keepTemp = true;
            unset($params[static::$tempParamSuffix]);
        }

        if (!$keepTemp) {
            $ks = array_keys($params);
            foreach ($ks as $k) {
                if ($this->isParamTemporary($k)) {
                    // echo "UNSET '$k'<br>";
                    unset($params[$k]);
                }
            }
        }

    // permanent params?
        $permanentParams = [];
        $ks = array_keys($params);
        foreach ($ks as $k) {
            if ($this->isParamPermanent($k)) {
                $permanentParams[$k] = $params[$k];
                unset($params[$k]);
            }
        }

        $stepsTo = static::determineStepsToOtherPath($fromPath, $toPath);

        if (null === $stepsTo) {
        // probably can have a common parent
            $commonParent = static::determineCommonParent($fromPath, $toPath);

            if ($commonParent) {
                // echo "'$fromSlug' : '$toSlug'  = '$commonParent'<br>";
                $commonParentParams = $this->passParams($fromPath, $commonParent, $params);
                $ret = $this->passParams($commonParent, $toPath, $commonParentParams);
            }
        } elseif (0 === $stepsTo) {
            $ret = $params;
    // pass to child
        } elseif ($stepsTo > 0) {
            foreach ($params as $k => $v) {
                $k2 = str_repeat(static::$passParamPrefix, $stepsTo) . $k;
                $ret[$k2] = $v;
            }
    // pass to parent
        } elseif ($stepsTo < 0) {
            foreach ($params as $k => $v) {
                $prefix = str_repeat(static::$passParamPrefix, -$stepsTo);
                if (substr($k, 0, strlen($prefix)) === $prefix) {
                    $k2 = substr($k, strlen($prefix));
                    $ret[$k2] = $v;
                }
            }
        }

        $ret = array_merge($ret, $permanentParams);

        return $ret;
    }

    private static function determineCommonParent(string $fromPath, string $toPath): string
    {
    // the same one
        if ($fromPath === $toPath) {
            return $fromPath;
        }

        $fromArray = explode('/', $fromPath);
        $toArray = explode('/', $toPath);

        $ret = '';

        $len = min(count($fromArray), count($toArray));
        $matchLen = 0;
        for ($i = 0; $i < $len; $i++) {
            if ($fromArray[$i] === $toArray[$i]) {
                $matchLen = $i + 1;
            } else {
                break;
            }
        }

        if (!$matchLen) {
            return $ret;
        }

        $ret = array_slice($fromArray, 0, $matchLen);
        $ret = join('/', $ret);

        return $ret;
    }

    private static function determineStepsToOtherPath(string $fromPath, string $toPath): ?int
    {
    // the same one
        if ($fromPath === $toPath) {
            return 0;
        }

        $fromArray = explode('/', $fromPath);
        $toArray = explode('/', $toPath);

        $countFrom = count($fromArray);
        $countTo = count($toArray);

    // to is child
        if ($countTo > $countFrom) {
            $ok = true;
            for ($i = 0; $i < $countFrom; $i++) {
                if ($fromArray[$i] !== $toArray[$i]) {
                    $ok = false;
                    break;
                }
            }

            return $ok ? ($countTo - $countFrom) : null;
        }

    // to is parent
        if ($countTo < $countFrom) {
            $ok = true;
            for ($i = 0; $i < $countTo; $i++) {
                if ($fromArray[$i] !== $toArray[$i]) {
                    $ok = false;
                    break;
                }
            }

            return $ok ? -($countFrom - $countTo) : null;
        }

        return null;
    }

    private function isParamTemporary(string $paramName): bool
    {
        if (substr($paramName, -strlen(static::$tempParamSuffix)) === static::$tempParamSuffix) {
            $ret = true;
        } else {
            $ret = false;
        }

        return $ret;
    }

    private function isParamPermanent(string $paramName): bool
    {
        $suffix = '*';

        if (substr($paramName, -strlen($suffix)) === $suffix) {
            $ret = true;
        } else {
            $ret = false;
        }

        return $ret;
    }

    private function cleanInput($v)
    {
        if (is_array($v)) {
            return array_map([$this, 'cleanInput'], $v);
        }

        $needStrip = false;
        if (version_compare(PHP_VERSION, '5.4', '<') && get_magic_quotes_gpc()) {
            $needStrip = true;
        } elseif (defined('WPINC')) {
            $needStrip = true;
        }

        if ($needStrip) {
            $v = stripslashes($v);
        }

        // standardize newlines
        if (false !== strpos($v, "\r")) {
            $v = str_replace(["\r\n", "\r", "\r\n\n"], PHP_EOL, $v);
        }

        $v = Helper::removeInvisibleCharacters($v);

        return $v;
    }
}
