import { Server, Socket } from "socket.io";
import { handleEvent } from "../handleEvent";
import { EditorState, MissionMap, missionStoreModel } from "../../storage/missionMap";
import * as Y from "yjs";
import MissionDocHelper from "../../helpers/missionDocHelper";
import randomColor from "randomcolor";
import { SocketMap } from "../../storage/socketMap";

export default (io: Server, socket: Socket) => {
  socket.on(
    "mission:join",
    handleEvent(
      { type: "read", section: "operation", module: "mission" },
      async (missionId: string, clientLastUpdate: missionStoreModel) => {
        socket.rooms.forEach((room) => {
          if (room !== socket.id && room != "home") {
            socket.leave(room);
          }
        });

        try {
          socket.join(missionId);

          if (!MissionMap.exists(missionId)) {
            await MissionDocHelper.populateDoc(missionId);
          }

          if (clientLastUpdate && clientLastUpdate.timestamp) {
            if (MissionDocHelper.docTimestamp(missionId) > clientLastUpdate.timestamp) {
              socket.emit("package-sync", {
                update: MissionDocHelper.docAsUpdate(missionId),
                timestamp: MissionDocHelper.docTimestamp(missionId),
                source: "server",
              });
            } else {
              socket.emit("sync-get-missing-updates", {
                stateVector: MissionDocHelper.docAsStateVector(missionId),
                timestamp: MissionDocHelper.docTimestamp(missionId),
                source: "server",
              });
            }
          } else {
            socket.emit("package-sync", {
              update: MissionDocHelper.docAsUpdate(missionId),
              timestamp: MissionDocHelper.docTimestamp(missionId),
              source: "server",
            });
          }

          socket.emit("package-init-awareness", {
            update: MissionDocHelper.getAwarenessEditors(missionId),
            type: "editors",
          });
          socket.emit("package-init-awareness", {
            update: MissionDocHelper.getAwarenessState(missionId),
            type: "state",
          });

          let newEditor = {
            socketId: socket.id,
            data: {
              username: SocketMap.read(socket.id).username,
              firstname: SocketMap.read(socket.id).firstname,
              lastname: SocketMap.read(socket.id).lastname,
              color: randomColor({ format: "hex", luminosity: "dark" }),
            },
          };
          MissionMap.updateAwareness(missionId, {
            action: "update",
            socketId: newEditor.socketId,
            store: "editors",
            update: newEditor.data,
          });
          socket.to(missionId).emit("package-sync-awareness", {
            update: newEditor.data,
            socketId: socket.id,
            action: "update",
            type: "editors",
          });

          return {
            type: "status-mission:join",
            answer: { status: "success" },
          };
        } catch (error) {
          return { type: "status-join:join", answer: { status: "failed", msg: error.message } };
        }
      },
      socket
    )
  );

  socket.on(
    "mission:sync-client-updates",
    handleEvent(
      { type: "create", section: "operation", module: "mission" },
      async (data: { update: Array<number>; timestamp: number }) => {
        const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
        try {
          MissionDocHelper.saveDoc(socketRooms[0], new Uint8Array(data.update));

          return {
            type: "package-sync",
            answer: {
              update: data.update,
              timestamp: data.timestamp,
              source: socket.id,
            },
            room: socketRooms[0],
          };
        } catch (error) {
          socket.emit("package-sync", {
            update: MissionDocHelper.docAsUpdate(socketRooms[0]),
            timestamp: MissionDocHelper.docTimestamp(socketRooms[0]),
            source: "server",
            error: true,
          });
          return { type: "status-mission:sync-client-updates", answer: { status: "failed", msg: error.message } };
        }
      },
      socket
    )
  );

  socket.on(
    "mission:sync-get-missing-updates",
    handleEvent(
      { type: "create", section: "operation", module: "mission" },
      async (data: any) => {
        const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
        try {
          const mission = MissionMap.read(socketRooms[0]);
          const missingUpdates = Y.encodeStateAsUpdate(mission.doc, data.stateVector);

          return {
            type: "package-sync",
            answer: {
              update: missingUpdates,
              timestamp: MissionDocHelper.docTimestamp(socketRooms[0]),
              source: "server",
            },
          };
        } catch (error) {
          return { type: "status-mission:sync-get-missing-updates", answer: { status: "failed", msg: error.message } };
        }
      },
      socket
    )
  );

  socket.on(
    "mission:sync-client-awareness",
    handleEvent(
      { type: "create", section: "operation", module: "mission" },
      async (data: EditorState) => {
        const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
        try {
          MissionMap.updateAwareness(socketRooms[0], {
            action: "update",
            store: "state",
            update: data,
            socketId: socket.id,
          });

          return {
            type: "package-sync-awareness",
            answer: {
              type: "state",
              action: "update",
              update: data,
              socketId: socket.id,
            },
            room: socketRooms[0],
          };
        } catch (error) {
          socket.emit("package-sync-awareness", {
            update: MissionDocHelper.docAsUpdate(socketRooms[0]),
            source: "server",
            error: true,
          });
          return { type: "status-mission:sync-client-updates", answer: { status: "failed", msg: error.message } };
        }
      },
      socket
    )
  );

  socket.on(
    "mission:leave",
    handleEvent(
      { type: "read", section: "operation", module: "mission" },
      async (data: any) => {
        const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home");
        socket.leave(socketRooms[0]);

        const clients = io.sockets.adapter.rooms.get(socketRooms[0]);
        if (!clients || clients.size == 0) {
          MissionMap.delete(socketRooms[0]);
        }

        MissionMap.updateAwareness(socketRooms[0], {
          action: "remove",
          store: "editors",
          update: undefined,
          socketId: socket.id,
        });
        io.to(socketRooms[0]).emit("package-sync-awareness", {
          type: "editors",
          update: undefined,
          action: "remove",
          socketId: socket.id,
        });

        MissionMap.updateAwareness(socketRooms[0], {
          action: "remove",
          store: "state",
          update: undefined,
          socketId: socket.id,
        });
        io.to(socketRooms[0]).emit("package-sync-awareness", {
          type: "state",
          update: undefined,
          action: "remove",
          socketId: socket.id,
        });

        return {
          type: "deleted",
          answer: "leave instance",
        };
      },
      socket
    )
  );
};