package org.expeditee.warp;

import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.server.WebSocketUpgradeHandler;
import org.eclipse.jetty.websocket.api.Session;
    
import org.eclipse.jetty.websocket.api.exceptions.WebSocketException;
import org.eclipse.jetty.websocket.api.Callback;
    
    
import java.awt.*;
import java.io.IOException;
import java.net.URI;
    
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Base64;
import java.util.concurrent.atomic.AtomicBoolean;

import java.nio.ByteBuffer;
import java.nio.file.Paths;

import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.AbstractHandler;

import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.ResourceFactory;

public class WarpSocket implements Session.Listener.AutoDemanding
{

    /**
     * Tiny JSON helper for { "k":"v", "n":123 } style payloads.
     * This is intentionally primitive to keep the prototype dependency-light.
     */
    public static class JsonLite {
        static String getString(String json, String key) {
            String pat = "\"" + key + "\"";
            int i = json.indexOf(pat);
            if (i < 0) return null;
            int colon = json.indexOf(':', i + pat.length());
            if (colon < 0) return null;
            int q1 = json.indexOf('"', colon + 1);
            if (q1 < 0) return null;
            int q2 = json.indexOf('"', q1 + 1);
            if (q2 < 0) return null;
            return json.substring(q1 + 1, q2);
        }

        static int getInt(String json, String key) {
            String pat = "\"" + key + "\"";
            int i = json.indexOf(pat);
            if (i < 0) return 0;
            int colon = json.indexOf(':', i + pat.length());
            if (colon < 0) return 0;
            int start = colon + 1;
            while (start < json.length() && Character.isWhitespace(json.charAt(start))) start++;
            int end = start;
            while (end < json.length() && "-0123456789".indexOf(json.charAt(end)) >= 0) end++;
            return Integer.parseInt(json.substring(start, end));
        }
    }

    

    /**
     * WebSocket endpoint. Requires a connect(token) before allowing warp.
     */
    private final String expectedToken;
    private final AtomicBoolean authed = new AtomicBoolean(false);
    private final Robot robot;
    private volatile Session session;
    
    WarpSocket(String expectedToken) {
	this.expectedToken = expectedToken;
	try {
	    this.robot = new Robot();
	} catch (AWTException e) {
	    throw new RuntimeException("Cannot create java.awt.Robot. Are you running headless?", e);
	}
    }
    
    @Override
    public void onWebSocketOpen(Session session) {
	this.session = session;
    }
    
    @Override
    public void onWebSocketText(String message) {
	System.out.println("onWebSocketTest() - message = " + message);
	
	try {
	    handleMessage(message);
	} catch (Exception e) {
	    throw new WebSocketException("Bad message: " + e.getMessage(), e);
	}
    }
    
    @Override
    public void onWebSocketClose(int statusCode, String reason) {
	// no-op
    }

    @Override
    public void onWebSocketError(Throwable cause) {
	System.err.println("onWebSocketError() - " + cause);
            // no-op
    }
    
    @Override
    public void onWebSocketBinary(ByteBuffer payload, Callback callback) {
	// We don't accept binary in this prototype.
	//callback.succeeded();
	callback.succeed();
    }
    
    private void handleMessage(String message) throws IOException {
	// Minimal parsing for prototype. Swap to Jackson/Gson once you're happy.
	String type = JsonLite.getString(message, "type");
	
	if ("connect".equals(type)) {
	    String token = JsonLite.getString(message, "token");
	    if (expectedToken.equals(token)) {
		authed.set(true);
		send("{\"type\":\"connect\",\"status\":\"ok\"}");
	    } else {
		send("{\"type\":\"error\",\"msg\":\"bad token\"}");
		close();
	    }
	    return;
	}
	
	if (!authed.get()) {
	    send("{\"type\":\"error\",\"msg\":\"not authed\"}");
	    close();
	    return;
	}
	
	if ("warp".equals(type)) {
	    int x = JsonLite.getInt(message, "x");
	    int y = JsonLite.getInt(message, "y");
	    robot.mouseMove(x, y);
	    send("{\"type\":\"warp\",\"status\":\"ok\"}");
	    return;
	}
	
	send("{\"type\":\"error\",\"msg\":\"unknown type\"}");
        }
    
    private void send(String text) throws IOException {
	Session s = this.session;
	if (s != null && s.isOpen()) {
	    s.sendText(text, null);
	}
    }
    
    private void close() {
	try {
	    Session s = this.session;
	    if (s != null && s.isOpen()) {
		s.close();
	    }
	}
	catch (Exception ignored) {
	}
    }
}
