<?php /** @noinspection PhpIllegalPsrClassPathInspection */

namespace Kicken\Chat;


use Exception;

class HTTPClient extends StreamSocketClient implements IHTTPRouter {
    private string $mHeadersBuffer = '';
    private array $mHeaders = [];
    private string $mRequestBody = '';
    private bool $mGotHeaders = false;
    private bool $mCloseAfterWrite = false;

    public function __construct($resource){
        parent::__construct($resource);
    }

    public function nowReadable() : void{
        parent::nowReadable();

        $this->dataAvailable();
    }

    public function dataAvailable() : void{
        if (!$this->mGotHeaders){
            $data = $this->readAll();
            $pos = strpos($data, "\r\n\r\n");
            if (false !== $pos){
                $this->mGotHeaders = true;
                $pos += 4;
                $this->mHeadersBuffer .= substr($data, 0, $pos);
                $this->mRequestBody .= substr($data, $pos);

                if ($this->processHeaders()){
                    $this->doRouting();
                }
            } else {
                $this->mHeadersBuffer .= $data;
            }
        } else {
            $this->mRequestBody .= $this->readAll();
            $this->doRouting();
        }
    }

    private function doRouting() : void{
        try {
            $this->route($this->mHeaders['REQUEST_URI']);
        } catch (Exception $e){
            $this->sendResponse(500, $e->getMessage());
        }
    }

    protected function processHeaders() : bool{
        $curHeader = [];
        $allHeaders = [];
        $headers = rtrim($this->mHeadersBuffer);

        foreach (explode("\r\n", $headers) as $l){
            if (preg_match('#^([a-z]+)\s+(.*)\s+HTTP/1.[01]$#i', $l, $matches)){
                if (str_contains($matches[2], '?')){
                    [$uri, $qs] = explode('?', $matches[2]);
                } else {
                    $uri = '/' . ltrim($matches[2], '/');
                    $qs = '';
                }

                $allHeaders[] = [
                    'REQUEST_METHOD',
                    $matches[1]
                ];
                $allHeaders[] = [
                    'REQUEST_URI',
                    $uri
                ];
                $allHeaders[] = [
                    'QUERY_STRING',
                    $qs
                ];
            } else if ($l[0] == ' ' || $l[0] == "\t"){
                $curHeader[1] .= ltrim($l);
            } else {
                if ($curHeader){
                    $allHeaders[] = $curHeader;
                }

                $curHeader = explode(':', $l, 2);
                $curHeader[0] = rtrim($curHeader[0]);
                $curHeader[1] = ltrim($curHeader[1]);
            }
        }
        if ($curHeader){
            $allHeaders[] = $curHeader;
        }

        $this->mHeaders = [];
        foreach ($allHeaders as $h){
            $h[0] = strtoupper($h[0]);
            $h[0] = str_replace('-', '_', $h[0]);
            $this->mHeaders[$h[0]] = $h[1];
        }

        return isset($this->mHeaders['REQUEST_METHOD']) && isset($this->mHeaders['REQUEST_URI']);
    }

    protected function getCodeText(int $code) : string{
        return match ($code) {
            200 => 'Ok',
            400 => 'Bad Request',
            404 => 'Not Found',
            403 => 'Forbidden',
            405 => 'Method not allowed',
            415 => 'Unsupported media type',
            500 => 'Internal Server Error',
            default => 'Unknown',
        };

    }

    protected function sendResponse(int $code, string $body = null, array $headers = []) : void{
        $response = sprintf("HTTP/1.0 %d %s\r\n", $code, $this->getCodeText($code));
        $defaultHeaders = [
            'EXPIRES' => gmdate(DATE_RFC1123),
            'PRAGMA' => 'no-cache',
            'CACHE_CONTROL' => 'no-cache',
            'LAST_MODIFIED' => gmdate(DATE_RFC1123),
            'DATE' => gmdate(DATE_RFC1123),
            'SERVER' => 'phpchatd',
            'CONTENT_LENGTH' => strlen($body),
            'CONTENT_TYPE' => 'text/html'
        ];

        $headers = array_merge($defaultHeaders, $headers);

        foreach ($headers as $h => $valueList){
            if ($valueList !== null){
                $h = ucfirst(strtolower($h));
                $h = str_replace('_', '-', $h);

                foreach ((array)$valueList as $v){
                    $response .= "$h: $v\r\n";
                }
            }
        }

        $response .= "\r\n";
        $response .= $body;

        $this->mCloseAfterWrite = true;
        $this->writeSocket($response);
    }

    public function writeSocket(string $data) : void{
        parent::writeSocket($data);
        if ($this->mCloseAfterWrite && !$this->hasPendingWrites()){
            $this->close();
        }
    }

    public function route(string $uri) : void{
        $this->sendResponse(404);
    }

    protected function getRequestHeaders() : array{
        return $this->mHeaders;
    }

    protected function getRequestBody() : string{
        return $this->mRequestBody;
    }

    protected function getQueryVars() : array{
        parse_str($this->mHeaders['QUERY_STRING'], $GET);

        return $GET;
    }
}