import SBC.GpioInput;
import SBC.GpioOutput;
import SBC.NanopiDuo2;
import SIP.SIP_Request;
import SIP.SIP_Response;
import SIP.jSIPClient;
import SIP.javaSipEvents;
import Webpage.WebServer;
import code.common;
import org.pmw.tinylog.Logger;

import java.io.File;
import java.text.MessageFormat;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;

import static code.common.currentDir;

public class Main {
    private static jSIPClient client;
    private static WebServer webserver;
    private static GpioInput callButton;
    private static GpioInput hangupButton;
    private static GpioOutput pilotLight;
    private static GpioOutput networkLight;
    private static GpioOutput callLight;
    private static GpioOutput Buzzer;
    private static Timer timer;
    private static TimerTask callLightTask;
    private static TimerTask BuzzerTask;
    private static SIP_Request incomingRequest;
    private static SIP_Request oncallRequest;
    private static SIP_Response incomingResponse;
    private static SIP_Response oncallResponse;

    public static void main(String[] args) {
        common.ExtractProperties(currentDir,"config.properties", false);
        Properties config = common.LoadProperties(currentDir,"config.properties");

        // Timer Section
        timer =  new Timer();
        callLightTask = new TimerTask() {
            @Override
            public void run() {
                if (callLight!=null && callLight.isInitialized()) callLight.BlinkON(100);
            }
        };
        BuzzerTask = new TimerTask() {
            @Override
            public void run() {
                if (Buzzer!=null && Buzzer.isInitialized()) Buzzer.BlinkON(500);
            }
        };

        // SIP Section
        client = new jSIPClient(config);
        client.SetJavaSipEvent(new javaSipEvents() {
            @Override
            public void Registering(SIP_Request req) {
                Logger.info("Registering to SIP Server, Request: {}",req);
                Buzzer_Off();
                callLight_Registering();
            }

            @Override
            public void RegisterSuccesful(SIP_Response resp) {
                Logger.info("Registered to SIP Server, Response: {}",resp);
                Buzzer_Off();
                callLight_Idle();
            }

            @Override
            public void RegisterFailed(SIP_Response resp) {
                Logger.info("Failed to register to SIP Server, Response: {}",resp);
                Buzzer_Off();
            }

            @Override
            public void IncomingCall(SIP_Request req, SIP_Response resp) {
                Logger.info("Incoming Call, Request: {}, Response: {}",req,resp);
                callLight_IncomingCall();
                Buzzer_IncomingCall();
                incomingRequest = req;
                incomingResponse = resp;
                oncallRequest = null;
                oncallResponse = null;
                //client.AcceptIncomingCall(req);
            }

            @Override
            public void RemoteHangUp(SIP_Request req) {
                Logger.info("Remote Hangup, Request: {}",req);
                callLight_Idle();
                Buzzer_Off();

                client.HangUp();
                incomingRequest = null;
                incomingResponse = null;
                oncallRequest = null;
                oncallResponse = null;

            }

            @Override
            public void Ringing(SIP_Response resp) {
                Logger.info("Ringing, Response: {}",resp);
            }

            @Override
            public void CalleePickup(SIP_Response resp) {
                Logger.info("Callee Pickup, Response: {}",resp);
            }
        });
        ReconnectSIP();


        // Web Server Section
        webserver = new WebServer(config);
        webserver.Start();

        // GPIO Section
        callButton = new GpioInput(NanopiDuo2.Pin12.gpionumber, true);
        if (callButton.isInitialized()){
            callButton.setOnLongPress(vv->{
                Logger.info("Call Button Long Pressed");
                if (incomingRequest!=null && incomingResponse!=null){
                    client.AcceptIncomingCall(incomingRequest);
                    callLight_OnCall();
                    Buzzer_OnCall();
                    oncallRequest = incomingRequest;
                    oncallResponse = incomingResponse;
                    incomingRequest = null;
                    incomingResponse = null;
                }
            });
            callButton.setOnShortPress(vv->{
                Logger.info("Call Button Short Pressed");
                if (incomingRequest!=null && incomingResponse!=null){
                    client.AcceptIncomingCall(incomingRequest);
                    callLight_OnCall();
                    Buzzer_OnCall();
                    oncallRequest = incomingRequest;
                    oncallResponse = incomingResponse;
                    incomingRequest = null;
                    incomingResponse = null;
                }
            });
        }
        hangupButton = new GpioInput(NanopiDuo2.Pin14.gpionumber, true);
        if (hangupButton.isInitialized()){
            hangupButton.setOnShortPress(vv->{
                Logger.info("Hangup Button Short Pressed");
                if (oncallRequest!=null || oncallResponse!=null){
                    client.HangUp();
                    oncallResponse = null;
                    oncallRequest = null;
                } else if (incomingRequest!=null || incomingResponse!=null){
                    client.RejectIncomingCall(incomingRequest);
                    incomingRequest = null;
                    incomingResponse = null;
                }
                callLight_Idle();
                Buzzer_Off();
            });
            hangupButton.setOnLongPress(vv->{
                Logger.info("Hangup Button Long Pressed");
                if (oncallRequest!=null || oncallResponse!=null){
                    client.HangUp();
                    oncallResponse = null;
                    oncallRequest = null;
                } else if (incomingRequest!=null || incomingResponse!=null){
                    client.RejectIncomingCall(incomingRequest);
                    incomingRequest = null;
                    incomingResponse = null;
                }
                callLight_Idle();
                Buzzer_Off();
            });
        }
        pilotLight = new GpioOutput(NanopiDuo2.Pin16.gpionumber, true);
        if (pilotLight.isInitialized()){
            timer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    if (pilotLight!=null && pilotLight.isInitialized()) pilotLight.BlinkON(100);
                }
            }, 0, 1000);
        }
        networkLight = new GpioOutput(NanopiDuo2.Pin18.gpionumber, true);
        callLight = new GpioOutput(NanopiDuo2.Pin20.gpionumber, true);
        Buzzer = new GpioOutput(NanopiDuo2.Pin22.gpionumber, true);

        // Shutdown Hook
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            Logger.info("Shutting down SIPIntercom");
            if (client!=null) client.Disconnect();
            if (webserver!=null) webserver.Stop();
            if (callButton!=null && callButton.isInitialized()) callButton.Close();
            if (hangupButton!=null && hangupButton.isInitialized()) hangupButton.Close();
            if (pilotLight!=null && pilotLight.isInitialized()) pilotLight.Close();
            if (networkLight!=null && networkLight.isInitialized()) networkLight.Close();
            if (callLight!=null && callLight.isInitialized()) callLight.Close();
            if (timer!=null) timer.cancel();
        }));
    }

    @SuppressWarnings("unused")
    private static void CallTest(String extension){
        String callnumber = MessageFormat.format("sip:{0}@{1}", extension, client.getServerAddress());
        if (client.Call(callnumber)){
            Logger.info("Call to {} is successful",callnumber);
            try{
                Thread.sleep(30*1000);
            } catch (InterruptedException ignored) {
            }
            client.HangUp();
        }
        else{
            Logger.error("Call to {} is failed",callnumber);
        }
    }

    @SuppressWarnings("unused")
    private static void StreamTest(String extension, String filename){
        File ff = new File(currentDir, filename);
        if (ff.isFile()){
            String callnumber = MessageFormat.format("sip:{0}@{1}", extension, client.getServerAddress());
            if (client.StreamFile(callnumber, ff.getAbsolutePath())){
                Logger.info("Stream to {} is successful",callnumber);
                try{
                    Thread.sleep(30*1000);
                } catch (InterruptedException ignored) {
                }
                client.HangUp();
            }
            else{
                Logger.error("Stream to {} is failed",callnumber);
            }
        } else Logger.error("File not found: {}",ff.getAbsolutePath());
    }

    private static void ReconnectSIP(){
        if (client.Connect()){
            Logger.info("Connected to SIP Server");
        }
        else{
            Logger.error("Failed to connect to SIP Server");
            try {
                Thread.sleep(20*1000);
            } catch (InterruptedException ignored) {
            }
            ReconnectSIP();
        }
    }

    /**
     * Registering state, call light is blinking with 500ms interval
     */
    private static void callLight_Registering(){
        if (callLightTask!=null) callLightTask.cancel();
        if (timer!=null){
            timer.purge();
            timer.scheduleAtFixedRate(callLightTask, 0, 500);
        }
    }

    /**
     * Idle state, call light is blinking with interval 3000ms
     */
    private static void callLight_Idle(){
        if (callLightTask!=null) callLightTask.cancel();
        if (timer!=null){
            timer.purge();
            timer.scheduleAtFixedRate(callLightTask, 0, 3000);
        }
    }

    /**
     * Incoming call, call light is blinking with 1000ms interval
     */
    private static void callLight_IncomingCall(){
        if (callLightTask!=null) callLightTask.cancel();
        if (timer!=null){
            timer.purge();
            timer.scheduleAtFixedRate(callLightTask, 0, 1000);
        }
    }

    /**
     * On call, call light is on
     */
    private static void callLight_OnCall(){
        if (callLightTask!=null) callLightTask.cancel();
        if (timer!=null) timer.purge();
        callLight.SetValue(true);
    }

    /**
     * Incoming call, buzzer is beeping with 1000ms interval
     */
    private static void Buzzer_IncomingCall(){
        if (BuzzerTask!=null) BuzzerTask.cancel();
        if (timer!=null){
            timer.purge();
            timer.scheduleAtFixedRate(BuzzerTask, 0, 1000);
        }
    }

    /**
     * On-call, buzzer is off
     */
    private static void Buzzer_OnCall(){
        if (BuzzerTask!=null) BuzzerTask.cancel();
        if (timer!=null) timer.purge();
        Buzzer.SetValue(false);
    }

    private static void Buzzer_Off(){
        if (BuzzerTask!=null) BuzzerTask.cancel();
        if (timer!=null) timer.purge();
        Buzzer.SetValue(false);
    }
}