<?php
require_once 'DevComponents.php';

use Firebase\JWT\JWT;

class DevTools extends DevComponents {
    
    private $contentType;
    private $cacheControl;
    private $devToken;
    
    private $payload;
    
    private $decodedPayload;
    private $secretKey;
    
    public function __construct(){
        parent::__construct();
        
        $this -> initComponents();
        $this -> setParameters();
        $this -> validateParamValues();
    }
    
    private function initComponents(){
        header("Content-type: text/json");
    }
    
    private function setParameters(){
        $this -> contentType = isset($_SERVER["CONTENT_TYPE"]) 
            ? $_SERVER["CONTENT_TYPE"] : NO_STRING;
        $this -> cacheControl = isset($_SERVER["HTTP_CACHE_CONTROL"]) 
            ? $_SERVER["HTTP_CACHE_CONTROL"] : NO_STRING;
        $this -> devToken = isset($_SERVER["HTTP_DEV_TOKEN"])
            ? $_SERVER["HTTP_DEV_TOKEN"] : NO_STRING;
        
        $this -> payload = file_get_contents("php://input");
    }
    
    private function validateParamValues(){
        $stops = [];
        
        $isDevTokenNotPresent = empty($this -> devToken);
        $isPayloadNotJSONString = !$this -> isJSON($this -> payload);
        
        $conditions = [
            [
                "code" => "E01",
                "condition" => empty($this -> contentType),
                "message" => "No Content-Type found"
            ],
            [
                "code" => "E02",
                "condition" => empty($this -> cacheControl),
                "message" => "No Cache-Control found"
            ],
            [
                "code" => "E03",
                "condition" => $_SERVER["REQUEST_METHOD"] != "POST",
                "message" => "Invalid Request Method: {$_SERVER["REQUEST_METHOD"]}"
            ],
            [
                "code" => "E04",
                "condition" => $this -> contentType != "application/x-www-form-urlencoded",
                "message" => "Invalid Content Type: {$this -> contentType}"
            ],
            [
                "code" => "E05",
                "condition" => $isDevTokenNotPresent,
                "message" => "No Dev Token Found"
            ],
            [
                "code" => "E06",
                "condition" => $isPayloadNotJSONString,
                "message" => "Invalid Payload String"
            ]
        ];

        
        if(!$isDevTokenNotPresent && !$isPayloadNotJSONString){
            $this -> decodedPayload = json_decode($this -> payload);
            
            if(isset($this -> decodedPayload -> purpose)){
                switch($this -> decodedPayload -> purpose){
                    case "authentication":{
                        $decodedToken = base64_decode($this -> devToken);
                        
                        if($this -> isJSON($decodedToken)){
                            $decodedToken = json_decode($decodedToken);
                            $devCredential = $this -> getDevCred($decodedToken -> devKey);
                            
                            $this -> devCredValidation($conditions, $devCredential, $decodedToken);
                        }
                        break;
                    }
                    default:{
                        try{
                            $decodedToken = base64_decode($this -> devToken);
                            
                            if($this -> isJSON($decodedToken)){
                                $decodedToken = json_decode($decodedToken);
                                          
                                $jwtDecodedObject = JWT::decode($decodedToken -> payload, $decodedToken -> secretKey, ["HS256"]);                      
                                
                                $currentTimestamp = strtotime(date("Y-m-d H:i:s"));
                                $payloadTimestamp = strtotime($jwtDecodedObject -> payloadDate);
                               
                                $conditions[] = [
                                    "code" => "E11",
                                    "condition" => $payloadTimestamp < $currentTimestamp,
                                    "message" => "Payload is Expired **{$jwtDecodedObject -> payloadDate}**"
                                ];
                                
                                $devCredential = $this -> getDevCred($jwtDecodedObject -> devKey);
                                $this -> devCredValidation($conditions, $devCredential, $jwtDecodedObject);
                            }
                        }catch(Exception $ex){
                            $conditions[] = [
                                "code" => "E12",
                                "condition" => true,
                                "message" => "JWT Payload Problem Occurred. [Please check the secret key used on the request]"
                            ];
                        }
                        break;
                    }
                }
            }else{
                $conditions[] = [
                    "code" => "E07",
                    "condition" => true,
                    "message" => "'purpose' was not specified"
                ];
            }
        }
        
        foreach($conditions as $verification){
            if($verification["condition"]){
                unset($verification["condition"]);
                $stops[] = $verification;
            }
        }
        
        if(count($stops) != 0){
            die(json_encode($stops));
        }else{
            if(isset($jwtDecodedObject))
                $this -> updateDevCredExecution($jwtDecodedObject -> devKey);
        }
    }
    
    private function devCredValidation(&$conditions, $devCredential, $decodedToken){
        if(!empty($devCredential)){
            $this -> secretKey = $devCredential["dev_secret_key"];
            
            if(isset($devCredential["dev_ip_addresses"])){
                $conditions[] = [
                    "code" => "E08",
                    "condition" => !in_array($_SERVER["REMOTE_ADDR"], explode(",", $devCredential["dev_ip_addresses"])),
                    "message" => "Invalid IP Address: [{$_SERVER["REMOTE_ADDR"]}]"
                ];
            }
            
            if(isset($devCredential["dev_passcode"])){
                $conditions[] = [
                    "code" => "E09",
                    "condition" => !password_verify($decodedToken -> passcode.VAL_STR_AUTH_HASH_SECRET_KEY, $devCredential["dev_passcode"]),
                    "message" => "Incorrect Passcode"
                ];
            }
            
            if(isset($devCredential["dev_expiration"])){
                $credExpiration = strtotime($devCredential["dev_expiration"]);
                $currentDate = strtotime(date("Y-m-d"));
                
                $credExpStr = date("Y-m-d", $credExpiration);
                
                $conditions[] = [
                    "code" => "E13",
                    "condition" => $credExpiration < $currentDate,
                    "message" => "Dev Credential Expired **{$credExpStr}**"
                ];
            }
        }else{
            $conditions[] = [
                "code" => "E10",
                "condition" => true,
                "message" => "Dev Credential [{$decodedToken -> devKey}] doesn't exist"
            ];
        }
    }
    
    private function myPurpose(){
        if(isset($this -> decodedPayload -> cstring) && !empty($this -> decodedPayload -> cstring)){
            try{
                $starttime = microtime(true);
                
                $result = $this -> getResults($this -> decodedPayload -> cstring);
                $rowCount = count($result);
                
                $endtime = microtime(true);
                                
                echo json_encode([
                    "totalNumRows" => $rowCount,
                    "elapsedTime" => $endtime - $starttime,
                    "result" => $result
                ]);
            }catch(Exception $ex){
                echo json_encode([
                    "code" => "MYERR01",
                    "message" => "My-Exception found: {$ex->getMessage()}"
                ]);
            }
        }else{
            echo json_encode([
                "code" => "MYERR02",
                "message" => "cstring is missing or not having a value."
            ]);
        }
    }
    
    private function authPurpose(){
        $decodedToken = json_decode(base64_decode($this -> devToken));
        
        $time = new DateTime(date("Y-m-d H:i:s"));
        // adds 1 minutes to the current time that will be included in the payload
        $time->add(new DateInterval("PT1M")); 
                
        echo json_encode([
            "payload" => JWT::encode([
                "devKey" => $decodedToken -> devKey,
                "passcode" => $decodedToken -> passcode,
                "payloadDate" => $time -> format('Y-m-d H:i:s')
            ], $this -> secretKey),
            "secretKey" => "<put-your-secret-key-right-here>"
        ]);
    }
    
    private function settingsPurpose(){
        if(isset($this -> decodedPayload -> cstring) && !empty($this -> decodedPayload -> cstring)){
            $settingsCmd = $this -> decodedPayload -> cstring;
            
            if(is_object($settingsCmd)){
                $devSettings = $this -> getDevSetting($settingsCmd -> control);
                
                if($this -> isLoopData($devSettings)){            
                    if($devSettings["dev_setting_enabled"]){
                        $nval = json_encode([
                            "enabled" => $settingsCmd -> enabled,
                            "exceptIpAddress" => $settingsCmd -> exceptIpAddress
                        ]);
                        
                        $this -> updateDevSetting($devSettings["dev_setting_id"], $nval);
                       
                        $remarks = [
                            "status" => "UPDATED",
                            "settings" => $devSettings["dev_setting_id"],
                            "update" => ($settingsCmd -> enabled ? "enabled" : "disabled")
                        ];
                        
                        if($settingsCmd -> enabled && strlen($settingsCmd -> exceptIpAddress) != NO_COUNT){
                            $remarks["exceptions"] = "IP Address(es): {$settingsCmd -> exceptIpAddress}";
                        }
                        
                        echo json_encode($remarks);
                    }else{
                        echo json_encode([
                            "code" => "STWRN01",
                            "message" => "[{$devSettings["dev_setting_id"]}]  can't be modified"
                        ]);
                    }
                }else{
                    echo json_encode([
                        "code" => "STWRN02",
                        "message" => "[{$settingsCmd -> control}]  doesn't exist"
                    ]);
                }
            }else{
                echo json_encode([
                    "code" => "STWRN03",
                    "message" => "The inputted settings command doesn't make an object out of it."
                ]);
            }
            
        }else{
            echo json_encode([
                "code" => "STERR01",
                "message" => "cstring is missing or not having a value."
            ]);
        }
    }
    
    public function listen(){
        switch($this -> decodedPayload -> purpose){
            case "authentication":{
                $this -> authPurpose();     
                break;
            }
            case "my":{
                $this -> myPurpose();
                break;
            }
            case "settings":{
                $this -> settingsPurpose();
                break;
            }
            default:{
                echo json_encode([
                    "code" => "W01",
                    "message" => "Unknown Purpose: {$this -> decodedPayload -> purpose}"
                ]);
                break;
            }
        }
    }
}

$devTools = new DevTools();
$devTools -> listen();