// TODO
// Peer mssages
// Routing
// Video files being fully sent
// Use Deno static serving for static

import { serveDir } from "jsr:@std/http/file-server"

// deno-lint-ignore-file prefer-const no-explicit-any
function serveFile(filename: string) {
    // console.log(filename)
    const responseText = Deno.readFileSync("../" + filename);
    // console.log(responseText)
    const response = new Response(responseText);

    if (filename.endsWith('.js')) {
        response.headers.set('content-type', 'application/javascript')
    }
    return response;
}

function hashIdToNumber(id: string, range:number) {
    let number = 0;
    let hash = 0x811c9dc5
    for (let char of id) {
      if (char !== '0' && char !== '-') {
        hash ^=  char.charCodeAt(0);
        hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);

      }
    
    }
    return (hash >>> 0) % range;
}

const colors = [
    160, 196, 202, 208, 214, 220, 226, 190, 154, 118, 82, 46, 47, 48, 49,
    51, 45, 44, 43, 42, 41, 40, 39, 33, 27, 21, 57, 93, 129, 165, 201,
  ];

const resetCode = "\x1b[0m";
function colorID(id) {
    const colorCode = `\x1b[38;5;${colors[hashIdToNumber(id, colors.length)]}m`
    return `${colorCode}${id.substring(0,5)}${resetCode}`
}

function pingHandler(m: any) {
    console.log(colorID(m.peer_id), "pong handler", m);
    return '{"type":"pong"}'
}

interface HelloMessage {
    type: string
    user_id: string
    user_name: string
    peer_id: string
    peer_name: string
    known_users: string[]
}

const userPeers: Map<string, Set<string>> = new Map();
const peerSockets: Map<string, WebSocket> = new Map();
const socketPeers: Map<WebSocket, string> = new Map();

function helloHandler(m: HelloMessage, socket: WebSocket) {
    console.log(`Received hello from peer ${colorID(m.peer_id)}:${m.peer_name}, user ${colorID(m.user_id)}:${m.user_name}`);


    if (!userPeers.has(m.user_id)) {
        userPeers.set(m.user_id, new Set());
    }
    userPeers.get(m.user_id)?.add(m.peer_id);
    peerSockets.set(m.peer_id, socket);
    socketPeers.set(socket, m.peer_id);


    for (const knownUserID of m.known_users) {
        console.log(`Adding user ${knownUserID} from peer ${colorID(m.peer_id)}`);
        if (!userPeers.get(knownUserID)) {
            userPeers.set(knownUserID, new Set());
        }

        userPeers.get(knownUserID)?.add(m.peer_id);
    }

    let returnValue: any = {};
    for (let key of userPeers.keys()) {
        let peers = userPeers.get(key);
        if (!peers) {
            continue;
        }
        returnValue[key] = [...peers.keys()];
    }


    // console.log(returnValue);

    return JSON.stringify({ type: 'hello', userPeers: returnValue });
}

interface InnerMessage {
    type: string
    user_id: string
}

interface PeerMessage {
    type: string
    from: string
    from_username: string
    from_peername: string
    to: string
    message: InnerMessage
}

function peerMessageHandler(m: PeerMessage, _socket: WebSocket) {

    console.log(`Peer message type ${m.message.type} from ${colorID(m.from)}:${m.from_peername}:${m.from_username} to ${colorID(m.to)}`)

    let toPeer = peerSockets.get(m.to);
    if (!toPeer) {
        console.log(`Couln't find peer ${m.to}`)
        return null;
    }

    if (toPeer.readyState !== WebSocket.OPEN) {
        console.log("Peer socket is not open:", toPeer);
        return null;
    }

    let messageToSend = JSON.stringify(m);
    // console.log("ws->", toPeer, messageToSend);
    toPeer.send(messageToSend)
    return null;
}

const messageDispatch: Map<string, (m: any, socket: WebSocket) => string | null> = new Map();

function connectWebsocket(request: Request) {
    if (request.headers.get("upgrade") != "websocket") {
        return new Response(null, { status: 501 });
    }

    const { socket, response } = Deno.upgradeWebSocket(request);
    socket.addEventListener("open", () => {
        console.log("a client connected!");
    });
    socket.addEventListener("message", (event) => {
        // console.log(event);
        let message: any;
        try {
            message = JSON.parse(event.data);
        } catch (e) {
            console.error("socket.message: ", e);
            return null;
        }

        const dispatchHandler = messageDispatch.get(message?.type)
        if (!dispatchHandler) {
            console.log("Got message I don't understand: ", event.data);
            return;
        }


        const response = dispatchHandler(message, socket);
        // console.log(response);
        if (response) {
            socket.send(response);
        }

    });

    socket.addEventListener("close", (event) => {

    });

    return response;

}

function handler(request: Request, info: any) {

    // console.log(info.remoteAddr);

    const url = new URL(request.url);
    if (url.pathname === "/") {
        return serveFile("/static/index.html")
    }

    if (url.pathname === "/ws") {
        return connectWebsocket(request);
    }


    if (url.pathname === "/sw.js") {
        return serveFile("static/sw.js")
    }

    if (url.pathname === "/robots).txt") {
        return serveFile("static/robots.txt")
    }

    if (url.pathname === "/favicon.ico") {
        return serveFile("static/favicon.ico")
    }


    if (url.pathname.includes("/static/")) {
        return serveDir(request, { fsRoot: "../" });
    }

    return serveFile("/static/index.html")
}

function main() {

    messageDispatch.set('ping', pingHandler);
    messageDispatch.set('hello', helloHandler);
    messageDispatch.set('peer_message', peerMessageHandler);

    Deno.serve({
        port: 6789,
        cert: Deno.readTextFileSync("/etc/letsencrypt/live/ddlion.net/fullchain.pem"),
        key: Deno.readTextFileSync("/etc/letsencrypt/live/ddlion.net/privkey.pem"),
    }, handler);
}

main();