<?php
namespace app\modules\hub\essentials;

use app\modules\hub\models\db\IstSession;
use app\modules\hub\models\db\IstSessionPeers;
use app\modules\hub\models\db\IstPeers;
use app\modules\hub\models\db\IstHub;

/**
 * Session 
 * 
 * 
 * Table mapping for tables
 * 
 *  A = ist_session
 *  B = ist_session_peers
 *  C = ist_hub_link
 *  D = ist_hub
 *  E = ist_peers
 *  F = ist_hub_type
 * 
 * @author William Escudero <wescu001@fiu.edu>
 * @version 1.0.0
 * 
 */
class Session{

    const SID_LEN = 6;
    
    private $session_fields = [
        'A.status',
        'A.sid',
        'A.start_datetime',
        'A.end_datetime',
        'A.created_on'
    ];

    private $session_peers_fields = [
        'E.id',
        'E.pid',
        'E.addr',
        'E.status',
        'B.peer_type',
        'B.peer_state',
        'B.timestamp'
    ];


    /**
     * Set peer online
     */
    public function setPeerOnline(String $pid){
        $this->updateData(['pid' => $pid], 'E', ['status' => 'online']);
    }

    /**
     * Change peer's address
     */
    public function setPeerAddr(String $pid, String $new_addr){
        $this->updateData(['pid' => $pid], 'E', ['addr' => $new_addr]);
    }

    /**
     * Update session status
     */
    public function setSessionStatus(String $session_id, String $new_status){
        $this->updateData(['sid' => $sid], 'A', ['status' => $new_status]);
    }

    /**
     * Get session peers by session id
     */
    public function getSessionPeersBySessionId(String $session_id){
        return $this->getSessionPeers($this->session_peers_fields, ['A.sid' => $session_id]);
    }

    /**
     * Get session data by domain
     */
    public function getSessionByDomain(String $domain){
        $session = $this->getSession($this->session_fields, ['D.domain' => $domain]);

        return is_array($session) ? $session[0] : null;
    }

    /**
     * Get Hub id by domain
     */
    public function startSession(String $domain, String $pi_id){

        $new_session = new IstSession();
        $new_session_peer = new IstSessionPeers();

        // Get hub id by domain
        if (($hub = IstHub::find()->where(['domain' => $domain])->one()) == null)
            return false;
        
        // Get peer id by pid
        if (($peer = IstPeers::find()->where(['pid' => $pi_id])->one()) == null){
            return false;
        }
            
        // Create new random session id
        $new_sid = $this->genSid();
        
        $transaction = \Yii::$app->db->beginTransaction();
        try{

            // Create new session record
            $new_session->hub_id = $hub['id'];
            $new_session->sid = $new_sid;
            $new_session->status = 'started';
            $new_session->start_datetime = @date("Y-m-d H:i:s");
            $new_session->created_on = $new_session->start_datetime;

            // Attempt to save new session
            if (!$new_session->save()){
                $transaction->rollBack();
                print_r($new_session->getErrors());
                echo "cant save new session";exit;
                return false;
            }

            // Create new session peer as initiator
            $new_session_peer->session_id = $new_session->id;
            $new_session_peer->peer_id = $peer->id;
            $new_session_peer->peer_type = 'initiator';
            $new_session_peer->peer_state = "active";
            $new_session_peer->timestamp = @date("Y-m-d H:i:s");
            $new_session_peer->created_on = $new_session_peer->timestamp;

            if (!$new_session_peer->save()){
                $transaction->rollBack();
                echo "cant save new session peer";exit;
                return false;
            }

            // Commit transaction
            $transaction->commit();


        }
        catch(\Exception $e){
            $transaction->rollBack();
            print_r($e);exit;
            echo "exception";exit;
            return false;
        }

        return $new_sid;
    }

    /**
     * Generate random session id
     */
    private function genSid(){
        $sid = "";
        $rnd_sample = hash('sha512', (random_bytes(Session::SID_LEN)));

        // Pick SID_LEN characters for id
        for ($i = 0; $i < Session::SID_LEN; $i++){
            $sid .= $rnd_sample[rand(0, (strlen($rnd_sample)-1))];
        }

        return $sid;
    }
    
    /**
     * Get session data from db
     */
    private function getSession(Array $data, Array $criteria){

        $sql = "
            SELECT 
                {{data}}
            FROM
            ist_session A
            LEFT JOIN ist_hub D ON D.id = A.hub_id
            WHERE
                {{where}}";

        return $this->getDataFromDbWithCriteria($sql, $data, $criteria);
    }


    /**
     * Get session peers from db
     */
    private function getSessionPeers(Array $data, Array $criteria){

        $sql = "
            SELECT 
                {{data}}
            FROM
            ist_session_peers B
            LEFT JOIN ist_peers E ON E.id = B.peer_id
            LEFT JOIN ist_session A ON A.id = B.session_id
            WHERE
                {{where}}";
            
        return $this->getDataFromDbWithCriteria($sql, $data, $criteria);
    }


    /**
     * Get data from database with criteria
     */
    private function getDataFromDbWithCriteria(String $sql, Array $data, Array $criteria){
        $_data = $_criteria = "";
        
        // Iterate over data to generate {{data}}
        foreach($data as $val){
            $_data .= "$val,";
        }
        // Iterate over criteria to generate {{where}}
        foreach($criteria as $key => $val){
            $_criteria .= "$key='$val' AND ";
        }

        // Remove trailing ',' from _data and 'AND' from criteria
        $_data = (trim($_data, ","));
        $_criteria = (substr($_criteria, 0, -4));

        // Replace data and criteria in query
        $sql = (str_replace("{{data}}", $_data, $sql));
        $sql = (str_replace("{{where}}", $_criteria, $sql));

        return (($record = \Yii::$app->db->createCommand($sql)->queryAll()) == false ? false : $record );
    }


    /**
     * Update data 
     * 
     * This method performs updates passively, meaning that if it finds an updatable
     * column it will update it otherwise it will skip it.
     */
    private function updateData(Array $criteria, String $table, Array $new_data){

        // Updateable fields
        $uf = [
            "A" => [
                "ar" => ($table == "A" ? IstSession::find()->where($criteria)->one() : null),
                "columns" => [
                    'status',
                    'end_datetime'
                ]
            ],
            "B" => [
                "ar" => ($table == "B" ? IstSessionPeers::find()->where($criteria)->one() : null),
                "columns" => [
                    'peer_state',
                ]
            ],
            "E" => [
                "ar" => ($table == "E" ? IstPeers::find()->where($criteria)->one(): null),
                "columns" => [
                    'addr',
                    'status'
                ]
            ]
        ];

        // Update if column is updatable
        foreach($new_data as $column => $new_value){
            if (in_array($column, $uf[$table]['columns']) && $uf[$table]['ar'] != null){
                $uf[$table]['ar']->$column = $new_value;
            }
        }

        return ($uf[$table]['ar']->save() == false ? false : true);
    }
}