package ch.hevs.isi.web;

import ch.hevs.isi.core.BooleanDataPoint;
import ch.hevs.isi.core.DataPoint;
import ch.hevs.isi.core.DataPointListener;
import ch.hevs.isi.core.FloatDataPoint;
import ch.hevs.isi.utils.Utility;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;

public class WebConnector implements DataPointListener {
    private static WebConnector mySelf = null;  // Singleton
    public WebSocketServer wss = null;  // Websocket server

    /**
     * Private constructor
     * Subscribe to the update of DataPoints
     */
    private WebConnector (){

        // Subscribe to the update of DataPoints
        DataPointListener.subscribeUpdate(this);
    }

    /**
     * Get the singleton instance
     * create it if it does not exist
     * @return the singleton instance
     */
    public static WebConnector  getMySelf(){
        if (mySelf == null){
            mySelf = new WebConnector();
        }
        return mySelf;
    }

    /**
     * Initialize webConnector by initializing the websocket server
     *  - define all the callbacks for the websocket server (onOpen, onClose, onMessage, onError, onStart)
     *  - start the websocket server
     *  - wait for the first connection
     * @param host url of the host (localhost)
     * @param port port for the websocket server (8888)
     */
    public void initialize(String host, int port){
        InetSocketAddress address = new InetSocketAddress(host, port);
        /**
         * Define all the callbacks for the websocket server
         * - onOpen (send a message to the client)
         * - onClose (stop the websocket server)
         * - onMessage (update the dataPoint)
         * - onError (print the error)
         * - onStart (print a message)
         */
        wss = new WebSocketServer(address) {
            @Override
            public void onOpen(WebSocket conn, ClientHandshake handshake) {
                conn.send("Hello Minecraft World 5 !");
                broadcast( "new connection: " + handshake.getResourceDescriptor() ); //This method sends a message to all clients connected
                System.out.println("New connection from " + conn.getRemoteSocketAddress().getAddress().getHostAddress());
                System.out.println("Handshake message: " + handshake.getResourceDescriptor());
            }

            @Override
            public void onClose(WebSocket conn, int code, String reason, boolean remote) {
                System.out.println("closed " + conn.getRemoteSocketAddress() + " with exit code " + code + " additional info: " + reason);
                try {
                    stop(500);
                    System.exit(0);
                } catch (InterruptedException e) {
                    System.out.println("Error: " + e.getMessage());
                    throw new RuntimeException(e);
                }
            }

            @Override
            public void onMessage(WebSocket conn, String message) {
                Utility.pDebug("received message from "	+ conn.getRemoteSocketAddress() + ": " + message);
                updateDataPoint(message);
            }

            @Override
            public void onError(WebSocket conn, Exception ex) {
                try {
                    System.err.println("an error occurred on connection " + conn.getRemoteSocketAddress()  + ":" + ex);
                } catch (Exception e) {
                    System.out.println("Error: " + e.getMessage());
                }
            }

            @Override
            public void onStart() {
                System.out.println("Websocket server started");
            }
        };

        wss.start();
    }

    /**
     * Push the dataPoint to the web
     * use the websocket server to broadcast the dataPoint
     * @param dp dataPoint to push
     */
    private void pushToWeb(DataPoint dp){
        wss.broadcast(dp.toString());
        Utility.pDebug(dp.toString() + " -> Web");
    }

    /**
     * Update the dataPoint with the message received from the web
     * @param message
     */
    private void updateDataPoint(String message){

        // Split the message to get the label and the value
        String label = message.split("=")[0];
        Utility.pDebug("Label: " + label);

        String value = message.split("=")[1];
        Utility.pDebug("Value: " + value);

        // Check if the value is a boolean or a float and update the dataPoint
        if( (value.equals("true")) || value.equals("false") ) {
            new BooleanDataPoint(label, true).setValue(Boolean.parseBoolean(value));
        } else {
            new FloatDataPoint(label, true).setValue(Float.parseFloat(value));
        }

    }

    /**
     * Callback for the update of a dataPoint
     * @param dp dataPoint updated
     */
    @Override
    public void onNewValue(DataPoint dp) {
        pushToWeb(dp);
    }
}
