<?php /** @noinspection PhpIllegalPsrClassPathInspection */

namespace Kicken\Chat;


use InvalidArgumentException;

class ChatClient extends HTTPClient {
    private ChatServer $mChatServer;
    private ?ChatUser $mUser = null;
    private array $mHeaders;
    private string $mRequestBody;
    private array $mAllowedOrigins;
    /** @var callable */
    private $mPollingCb;

    public function __construct($resource, ChatServer $chatServer, array $allowedOrigins){
        parent::__construct($resource);
        $this->mChatServer = $chatServer;
        $this->mAllowedOrigins = $allowedOrigins;
    }

    public function route(string $uri) : void{
        $path = trim($uri, '/');
        $path = str_replace('/', '_', $path);

        $this->mHeaders = $this->getRequestHeaders();
        $this->mRequestBody = $this->getRequestBody();

        if (method_exists($this, $path)){
            $headers = [];
            if (isset($this->mHeaders['ORIGIN'])){
                $headers = $this->getCORSHeaders($path);
            }

            if ($this->mHeaders['REQUEST_METHOD'] === 'OPTIONS'){
                $this->sendResponse(200, 'CORS Response', $headers);
            } else {
                $this->$path($headers);
            }
        } else {
            parent::route($uri);
        }
    }

    private function getCORSHeaders(string $path) : ?array{
        $methodList = [
            'messages' => ['GET', 'POST'],
            'userlist' => ['GET', 'REPORT'],
            'register' => ['POST'],
            'chat' => ['GET']
        ];

        $headers = [];
        if (!isset($methodList[$path])){
            $this->sendResponse(405, 'Method not allowed');

            return null;
        } else {
            foreach ($methodList[$path] as $m){
                $headers['ACCESS_CONTROL_ALLOW_METHODS'][] = $m;
            }
        }

        if (isset($this->mHeaders['ORIGIN'])){
            $origin = $this->mHeaders['ORIGIN'];
            foreach ($this->mAllowedOrigins as $o){
                if (strcasecmp($origin, $o) == 0 || $o == '*'){
                    $headers['ACCESS_CONTROL_ALLOW_ORIGIN'] = $o;
                    break;
                }
            }
        }

        $headers['ACCESS_CONTROL_ALLOW_HEADERS'] = 'x-requested-with,content-type';

        return $headers;
    }

    private function getPostedJson(){
        if (!$this->isPostRequest()){
            $this->sendResponse(405, 'POST method only.');

            return false;
        } else if (!$this->isJsonRequest()){
            $this->sendResponse(415, 'Request entity must be a JSON string');

            return false;
        }

        $json = json_decode($this->mRequestBody);

        return $json;
    }

    public function register(array $additionalHeaders) : void{
        $json = $this->getPostedJson();
        if ($json === false){
            return;
        }
        try {
            $nick = trim($json->nick);
            $uid = $this->mChatServer->registerNick($nick);
            $this->sendJsonResponse(200, ['uid' => $uid, 'nick' => $nick]);
        } catch (InvalidArgumentException $e){
            $this->sendJsonResponse(400, [
                'error' => $e->getMessage(),
                'code' => $e->getCode()
            ], $additionalHeaders);
        }
    }

    public function messages(array $additionalHeaders) : void{
        $GET = $this->getQueryVars();
        if (!isset($GET['uid'])){
            $this->sendResponse(400, 'Missing uid parameter', $additionalHeaders);

            return;
        } else if (($this->mUser = $this->mChatServer->getUserByUID($GET['uid'])) === null){
            $this->sendResponse(404, 'User not found', $additionalHeaders);

            return;
        }

        $this->mUser->setActiveRequest($this);
        if ($this->mHeaders['REQUEST_METHOD'] === 'GET'){
            //Polling for new messages
            if (!$this->mPollingCb && $this->isConnected()){
                $this->mPollingCb = function() use ($additionalHeaders){
                    $messages = $this->mUser->getNewMessages();
                    $this->sendJsonResponse(200, [
                        'messages' => $messages
                    ], $additionalHeaders);
                };

                $this->mChatServer->onNewMessage($this->mPollingCb);
            }
        } else if ($this->mHeaders['REQUEST_METHOD'] === 'POST'){
            //Posting a new message
            $json = $this->getPostedJson();
            if ($json === false){
                return;
            }

            if (!isset($json->type) || !isset($json->text)){
                $this->sendResponse(400, 'Invalid JSON object format.  Requires type and text properties.', $additionalHeaders);
            }

            $json->timestamp = $this->now();
            $json->uid = $this->mUser->getUid();
            $newId = $this->mChatServer->saveMessage($json);
            //echo "Saved new message as {$newId}\r\n";
            $messages = $this->mUser->getNewMessages();
            $this->sendJsonResponse(200, [
                'messageId' => $newId,
                'timestamp' => $json->timestamp,
                'messages' => $messages
            ], $additionalHeaders);
        }
    }

    private function now() : int{
        return time();
    }

    public function isPostRequest() : bool{
        return $this->mHeaders['REQUEST_METHOD'] == 'POST';
    }

    public function isJsonRequest() : bool{
        return str_contains($this->mHeaders['CONTENT_TYPE'], 'text/json');
    }

    public function close() : void{
        parent::close();
        if ($this->mPollingCb){
            $this->mChatServer->cancelOnNewMessage($this->mPollingCb);
            $this->mPollingCb = null;
        }

        if ($this->mUser && $this->mUser->getActiveRequest() === $this){
            $this->mUser->setActiveRequest();
        }
    }

    public function chat(array $additionalHeaders) : void{
        $this->sendResponse(200, file_get_contents('chat.html'), $additionalHeaders);
    }

    /** @noinspection PhpUnused */
    public function userlist(array $additionalHeaders) : void{
        $uidMap = [];
        foreach ($this->mChatServer->getUsers() as $u){
            $uidMap[$u->getUid()] = $u->getNick();
        }

        $this->sendJsonResponse(200, [
            'uidmap' => $uidMap
        ], $additionalHeaders);
    }

    public function sendJsonResponse(int $code, array $arr, array $headers = []) : void{
        $json = json_encode($arr);
        $headers = array_merge($headers, [
            'CONTENT_TYPE' => 'text/json',
            'CONTENT_LENGTH' => strlen($json)
        ]);

        $this->sendResponse($code, $json, $headers);
    }
}