<?php /** @noinspection PhpIllegalPsrClassPathInspection */

namespace Kicken\Chat;


use 
InvalidArgumentException;
use 
RuntimeException;
use 
stdClass;

class 
ChatServer extends StreamSocketServer {
    private array 
$mMessages = [];
    
/** @var ChatUser[] */
    
private array $mUsers = [];
    
/** @var callable[] */
    
private array $mNewMessageListeners = [];

    public function 
getUsers() : array{
        return 
$this->mUsers;
    }

    public function 
getUserByUID(string $uid) : ?ChatUser{
        return 
$this->mUsers[$uid] ?? null;
    }

    public function 
cancelOnNewMessage(callable $cb) : void{
        foreach (
$this->mNewMessageListeners as $k => $cb2){
            if (
$cb === $cb2){
                unset(
$this->mNewMessageListeners[$k]);
            }
        }
    }

    public function 
onNewMessage(callable $cb) : void{
        
$this->mNewMessageListeners[] = $cb;
    }

    public function 
registerNick(string $nick) : string{
        if (
strlen($nick) == 0){
            throw new 
InvalidArgumentException('Nickname cannot be blank'ERR_NO_NICK);
        }

        
$inuse null;
        foreach (
$this->mUsers as $u){
            if (
$u->getNick() === $nick){
                
$inuse $u;
            }
        }

        if (
$inuse && !$inuse->isActive()){
            return 
$inuse->getUid();
        } else if (
$inuse){
            throw new 
InvalidArgumentException('Nickname already in use'ERR_NICK_TAKEN);
        } else {
            
$try 0;
            do {
                
$uid uniqid('u');
            } while (isset(
$this->mUsers[$uid]) && ++$try 25);

            if (isset(
$this->mUsers[$uid])){
                throw new 
RuntimeException('Unable to generate new unique id'ERR_SYSTEM);
            }

            
$user = new ChatUser($uid$this);
            
$user->setNick($nick);
            
$this->mUsers[$uid] = $user;

            return 
$uid;
        }
    }

    private function 
generateNewMessageId(stdClass $json) : string{
        if (!isset(
$json->timestamp)){
            throw new 
InvalidArgumentException('No timestamp on message object'ERR_NO_TIMESTAMP);
        }

        if (!isset(
$this->mMessages[$json->timestamp])){
            
$this->mMessages[$json->timestamp] = [];
        }

        
$idx count($this->mMessages[$json->timestamp]);

        return 
sprintf("%d:%d"$json->timestamp$idx);
    }

    public function 
saveMessage(stdClass $json) : string{
        
$newId $this->generateNewMessageId($json);
        [
$ts$idx] = explode(':'$newId2);

        
$json->messageId $newId;
        
$this->mMessages[$ts][$idx] = $json;

        
$this->fireOnNewMessage($newId);

        return 
$newId;
    }

    private function 
fireOnNewMessage(string $id) : void{
        foreach (
$this->mNewMessageListeners as $cb){
            
$cb($id);
        }
    }

    public function 
getNewestMessageId(){
        
$last end($this->mMessages);
        
$last end($last);

        return 
$last->messageId;
    }

    public function 
getMessagesAfter(?string $id) : array{
        
$output = [];
        [
$ts$idx] = explode(':'$id ?: '0:0'2);
        
$idx intval($idx);

        
$newerTs array_filter(array_keys($this->mMessages), function($k) use ($ts){
            return 
$k >= $ts;
        });

        foreach (
$newerTs as $k){
            
$list $this->mMessages[$k];

            if (
$k == $ts && isset($list[$idx 1])){
                
$slice array_slice($list$idx 1);
                
$output array_merge($output$slice);
            } else if (
$k $ts){
                
$output array_merge($output$list);
            }
        }

        return 
$output;
    }
}