import {Server} from "http";
import WebSocket, {WebSocketServer} from "ws";

type BrowserClient = {
  isAlive: boolean; 
  type: 'browser';
  appId: string;
  ip: string|undefined;
}; 

type CompilerClient = {
  isAlive: boolean; 
  type: 'compiler';
  ip: string|undefined;
};

type PhoneClient = { 
  isAlive: boolean; 
  type: 'phone';
  ip: string|undefined;
};

type CliClient = {
  isAlive: boolean;
  type: 'cliui';
  ip: string|undefined;
};

type UnknownClient = {
  isAlive: boolean;
  type: 'unknown';
  ip: string|undefined;
};

type Client = BrowserClient | CompilerClient | PhoneClient | CliClient | UnknownClient;
export type Room = {};

const _rooms: Room[] = [];
export const rooms = new Proxy(_rooms, {
  set: () => {
    throw new Error("rooms is immutable");
  }
})

const aliveClients: Map<WebSocket, Client> = new Map();
let wss: WebSocketServer; 

export function sendLog(message: string) {
  if (!wss) {
    console.error("Not ready to send messages");
  } else {
    let sent = false;
    for (let ws of wss.clients) {
      const ref = aliveClients.get(ws);
      if (ref && ref.type === 'cliui' && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({type: "log", message}));
        sent = true;
      } 
    }

    if (!sent) {
      console.error("Failed to send log: ", message);
    }
  }
}

export function sendCompileRequest(appid: string) {
  if (!wss) {
    console.error("Not ready to send compilation requests");
  } else {
    let sent = false;
    for (let ws of wss.clients) {
      const ref = aliveClients.get(ws); 
      if (ref && ref.type === 'compiler' && ws.readyState === WebSocket.OPEN) {
        ws.send(`{"type": "prepareCodepush", "appId": "${appid}"}`);
        sent = true;
        break;
      }
    }

    if (!sent) {
      console.error("Failed to send compilation request for ", appid);
    }
  }
}

export function sendHotReloadRequest() {
  if (!wss) {
    console.error("Not ready to send hotreload requests");
  } else {
    let sent = false;
    for (let ws of wss.clients) {
      const ref = aliveClients.get(ws); 
      if (ref && ref.type === 'browser' && ws.readyState === WebSocket.OPEN) {
        ws.send(`{"type": "webSdkCompileDone", "appId": "none"}`);
        sent = true;
        break;
      }
    }

    if (!sent) {
      console.error("Failed to send compilation request");
    }
  }
}

export function sendLiveUpdateRequest(appid: string) {
  if (!wss) {
    console.error("Not ready to send liveupdate requests");
  } else {
    let sent = false;
    for (let ws of wss.clients) {
      const ref = aliveClients.get(ws); 
      if (ref && ref.type === 'compiler' && ws.readyState === WebSocket.OPEN) {
        ws.send(`{"type": "liveUpdate", "appId": "${appid}"}`);
        sent = true;
        break;
      }
    }

    if (!sent) {
      console.error("Failed to send compilation request for ", appid);
    }
  }
}

export function createSocketServer(server: Server) {
  wss = new WebSocketServer({ server });
  wss.on('connection', (ws, req) => {
    console.log('Websocket connection opened');

    const ref = aliveClients.get(ws);
    // console.log("Websocket connection: Ref: ", ref);
    if (!ref) {
      aliveClients.set(ws, {isAlive: true, type: 'unknown', ip: req.socket.remoteAddress});
    } else {
      ref.isAlive = true;
    }
     
    ws.on('pong', () => {
      const ref = aliveClients.get(ws);
      if (!ref) {
        console.log("Got a pong from a client that is not registered! Something is broken");
      } else {
        // console.log("Pong from: ", ref);
        ref.isAlive = true;
        // console.log("After pong: ", ref);
      }
    });
    
    ws.on('message', (message, isBinary) => {
      const msg = JSON.parse(message.toString());
      console.log("Received message: ", msg);
      let ref;
      switch (msg.type) {
        case "registerCompiler":
          console.log("registering compiler");
          ref = aliveClients.get(ws);
          if (ref) {
            ref.type = "compiler";
            ws.send(`{"status": "registered"}`);
          } else {
            ws.send(`{"status": "failed"}`);
          }
          break;
        case "compilationFailed": 
        case "compilationDone": 
          console.log("Broadcasting compilationDone/Failed message");
          for (let client of wss.clients) {
            if (client !== ws) {
              const ref = aliveClients.get(client);
              console.log("sending to: ", ref);
              if (ref && 
                  ref.type !== "compiler" && 
                  client.readyState === WebSocket.OPEN) {
                client.send(message)
              }
            }
          }
          break;
        case "mobileLog": 
          const mobmessage = message.toString();
          console.log("Broadcasting mobileLog/Failed message", mobmessage);
          for (let client of wss.clients) {
            const ref = aliveClients.get(client);
            console.log("sending to: ", ref);
            if (ref && 
                client.readyState === WebSocket.OPEN) {
              client.send(JSON.stringify({type:"log", message: mobmessage}));
            }
          }
          break;
        case "register":
          console.log("Registering client");
          ref = aliveClients.get(ws);
          if (ref) {
            ref.type = msg.kind;
            if (msg.appId) {
              (ref as BrowserClient).appId = msg.appId;
            }
            ws.send(`{"status": "registered"}`);
          } else {
            ws.send(`{"status": "failed"}`);
          }
          break;
        default:
          console.log("Echoing back message");
          ws.send(message);
      }
    });

    ws.on('close', () => {
      console.log('WebSocket connection closed');
      aliveClients.delete(ws);
    });

    ws.on('error', console.error);
  });

  const interval = setInterval(() => {
    // console.log("Socket clients maintenance");
    const wsClients: Client[] = [];
    const sockets = [];
    for (let [ws, ref] of aliveClients.entries()) {
      wsClients.push(ref);
      sockets.push(ws);
    }
    const clientSnapshot = JSON.stringify({type: 'status', clients: wsClients});
    // console.log("---", JSON.stringify(wsClients, null, 2));
    // console.log("Clients: ", wsClients.length, wss.clients.size);
    
    for (let i = 0; i < sockets.length; ++i) {
      const client = wsClients[i];
      const ws = sockets[i];
      if (!client.isAlive || (ws.readyState != WebSocket.OPEN)) {
        aliveClients.delete(ws);
        console.log("Terminating websocket", aliveClients.size)
        ws.close();
        return ws.terminate();
      } else {
        if (client.type === 'cliui') {
          // console.log('>>>', JSON.stringify(wsClients, null, 2));
          ws.send(clientSnapshot);
        }

        client.isAlive = false;
        ws.ping();
      }
    }
    // console.log("-----");
  }, 5000)

  wss.on('close', () => clearInterval(interval));
}


