<?php

namespace MyApp;

require __DIR__.'/../../../../application/libraries/vendor/autoload.php';
require __DIR__.'/../../../../application/helpers/simple_crypt_helper.php';

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Chat implements MessageComponentInterface {
    
    private const MEMORY_USAGE_FOR_GC_COLLECTION  = 25; // percentage

    protected $clients;

    protected $memory_usage_before = null; // @debug

    protected $conn_number = 0; // @debug

    private $memory_watch_dog;

    public function __construct() 
    {
        $this->clients = new \SplObjectStorage;
        //echo "Server started.";
        $this->memory_watch_dog = new \stdClass();
        $this->memory_watch_dog->max_memory = $this->getMemoryLimit();
        $this->memory_watch_dog->recent_connections = 0;
        $this->memory_watch_dog->check_interval = 1;
        $this->memory_watch_dog->gb_collect_memory_usage = $this->memory_watch_dog->max_memory * (self::MEMORY_USAGE_FOR_GC_COLLECTION / 100);
        $this->memory_watch_dog->last_memory_usage = null;
        $this->memory_watch_dog->enabled = true;
        //return TRUE;
    }

    public function addClient(ConnectionInterface $conn,$id)
    {
        $this->clients->attach($conn,['user_id'=>$id]);
    }

    public function onOpen(ConnectionInterface $conn) 
    {
        $parms = $this->get_querystring($conn);
    	$this->addClient($conn,$parms->from);
    	
    	if ($this->memory_watch_dog->enabled) {
            // Record the created connection
            if ($this->memory_watch_dog->recent_connections >= PHP_INT_MAX) {
                $this->memory_watch_dog->recent_connections = 1;
            } else {
                $this->memory_watch_dog->recent_connections++;
            }

            $memory_usage = null;

            // Check if garbage collection is necessary
            if (
                $this->memory_watch_dog->recent_connections === 1 ||
                $this->memory_watch_dog->recent_connections % $this->memory_watch_dog->check_interval === 0
            ) {
                if ($this->memory_watch_dog->last_memory_usage !== null) {
                    // Get the memory usage difference
                    $memory_usage = memory_get_usage();
                    $memory_usage_diff = $this->memory_watch_dog->last_memory_usage;

                    if ($memory_usage_diff > 0) {
                        $gc_collect_memory_usage = $this->memory_watch_dog->max_memory * (self::MEMORY_USAGE_FOR_GC_COLLECTION / 100);
                        $this->memory_watch_dog->check_interval = ($gc_collect_memory_usage - ($gc_collect_memory_usage % $memory_usage_diff)) / $memory_usage_diff;

                        if ($this->memory_watch_dog->check_interval <= 0) {
                            $this->memory_watch_dog->check_interval = 1;
                        }

                        if ($memory_usage >= $gc_collect_memory_usage) {
                            gc_collect_cycles();
                        }
                    } else {
                        $this->memory_watch_dog->enabled = false;
                    }
                }

                // Save current memory usage
                $this->memory_watch_dog->last_memory_usage = ($memory_usage === null) ? memory_get_usage() : $memory_usage;
            }
        }

        $memory_usage_now = memory_get_usage(); // @debug
        $this->conn_number++; // @debug

        echo 'Connection ' . str_pad($this->conn_number, 3, '0', STR_PAD_LEFT) . ' | '; // @debug
        echo 'Usage is: ' . $memory_usage_now . ' bytes'; // @debug

        if ($this->memory_usage_before !== null) { // @debug
            echo ' (difference is: ' . (string)($memory_usage_now - $this->memory_usage_before) . ' bytes'; // @debug
        } // @debug

        echo ' | Clients count: ' . (string)$this->clients->count(); // @debug
        echo PHP_EOL; // @debug

        $this->memory_usage_before = $memory_usage_now; // @debug
    	
        echo "New connection! ({$parms->from})\n";
    }
    
    public function onClose(ConnectionInterface $conn)
    {
        $parms = $this->get_querystring($conn);
    	$this->removeClient($conn,$parms->from);
    }
  
    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        $parms = $this->get_querystring($conn);
    	echo "The following error occured : ". $e->getMessage();
	    $this->removeClient($conn);
  	}
  	
  	public function onMessage(ConnectionInterface $conn , $msg)
  	{
    	$data = $this->parseMessage($msg);
    	
        $json = json_decode($msg);
        $parms = $this->decode_sessid($json->sessid);
        
        $clients = $this->clients;
        
        $clients->rewind();
        while($clients->valid()) {
        $client   = $clients->getInfo();
        $object = $clients->current();

        if(isset($client['user_id']) && in_array($client['user_id'],$parms->to))
        {
            echo "send to:".$client['user_id']."|\n";
            $object->send(json_encode($data->msg)); 
        }
            $clients->next();
        }
  	}
  	
    private function get_querystring($conn)
    {
        return $this->decode_sessid($conn->httpRequest->getUri()->getQuery());
    }
    
    private function decode_sessid($sessid)
    {
        return json_decode(simple_crypt( $sessid, 'd' ));
    }
  	private function parseMessage($msg)
  	{
    	return json_decode($msg);
  	}
  	
    public function removeClient(ConnectionInterface $conn, $user_id)
    {
	    $clients = $this->clients;
        
        $clients->rewind();
        
        while($clients->valid()) 
        {
            $client   = $clients->getInfo();
            $object = $clients->current();
    	
    	    if(isset($client['user_id']) && $client['user_id'] == $user_id )
            {
                echo "close connection:".$client['user_id']."|\n";
                $this->clients->detach($object);
                break;
            }
            $clients->next();
        }
        return null;
    }
    
    private function getMemoryLimit() {
        ini_set('memory_limit', '500M');
        $ini_val = trim(ini_get('memory_limit'));
        $val = substr($ini_val, 0, -1);
        $unit = strtolower(substr($ini_val, -1));

        switch($unit)
        {
            case 'g':
                $val *= 1024 * 1024 * 1024;
                break;
            case 'm':
                $val *= 1024 * 1024;
                break;
            case 'k':
                $val *= 1024;
                break;
        }
        return $val;
    }
}