package com.example.fitbot.util.server;

import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;

public class WebServer implements Runnable {

    private ServerSocket serverSocket;
    protected IWebServerHandler eventHandler = (input) -> {
    }; // No-op.

    private Thread thread;
    private final AtomicBoolean forceClose = new AtomicBoolean(false);

    /**
     * Constructor for creating a new WebSocket server.
     */
    private WebServer() {

    }

    /**
     * Function for creating a new WebSocket server given the provided port.
     *
     * @return A WebSocket connection, or null if something went wrong.
     */
    public static WebServer createServer() {
        try {
            WebServer server = new WebServer();
            server.serverSocket = new ServerSocket();
            server.serverSocket.bind(new InetSocketAddress(3445));
            server.serverSocket.setSoTimeout(0);

            Log.i("WebServer", "Server created: " + server.serverSocket.getLocalSocketAddress() + ", " + server.serverSocket.getLocalPort());

            server.thread = new Thread(server);
            server.thread.start();

            return server;
        } catch (IOException error) {
            String cause = error.getMessage() == null ? "Unknown reason" : error.getMessage();
            Log.e("WebServer", cause);
            return null;
        }
    }

    @Override
    public void run() {
        // Listen for new connections until the socket closes.
        while (this.isConnected() && !this.forceClose.get()) {
            try {
                // Find a new connection
                Socket newSocket = this.serverSocket.accept();
                InputStream streamIn = newSocket.getInputStream();
                OutputStream streamOut = newSocket.getOutputStream();

                // Read the incoming data
                BufferedReader reader = new BufferedReader(new InputStreamReader(streamIn));
                StringBuilder builder = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null)
                    builder.append(line).append("\n");

                // Send generic message back
                streamOut.write("HTTP/1.1 200 OK\n".getBytes());
                streamOut.write("Content-Type: text/html\n".getBytes());
                streamOut.write("Connection: close\n".getBytes());

                streamIn.close(); // Closes the reader, stream and socket connection.
                streamOut.close();
                newSocket.close();

                String[] data = builder.toString().split("\n\n");

                if (data.length > 1) { // Check if the data is valid.
                    this.eventHandler.onReceive(data[1]);
                }

            } catch (IOException error) {
                String reason = error.getMessage() == null ? "Unknown reason" : error.getMessage();
                Log.e("WebServerConnectionHandler", "Error listening to Socket connections: " + reason);
                break;
            }
        }
    }

    /**
     * Method for stopping the WebSocket server.
     */
    public void stop() {
        try {
            this.serverSocket.close();
            this.forceClose.set(true);
        } catch (IOException error) {
            String cause = error.getMessage() == null ? "Unknown reason" : error.getMessage();
            Log.e("WebServer", cause);
        }
    }

    /**
     * Method for setting the event handler for this WebSocket server.
     *
     * @param handler The handler to use. This handler will parse all events
     *                that occur in this WebSocket connection. The events are the followed:
     *                - onMessageReceived(Socket, String)
     *                - onConnected(Socket)
     *                - onDisconnected(Socket)
     *                - onError(Socket, String)
     */
    public void setEventHandler(IWebServerHandler handler) {
        this.eventHandler = handler;
    }

    /**
     * Method for getting the ServerSocket connection
     *
     * @return The ServerSocket connection.
     */
    public ServerSocket getSocket() {
        return this.serverSocket;
    }

    /**
     * Method for checking whether this WebSocket connection is connected.
     *
     * @return The connection status of the WebSocket.
     */
    public boolean isConnected() {
        return !this.serverSocket.isClosed();
    }
}
