<?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($data0$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(':'$l2);
                
$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 $codestring $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;
    }
}