import { Socket } from "socket.io";

import User from "./User";
import Room, { RoomOptions } from "./Room";
import { getCurrentTime } from "./util";

export default class Service {
  private roomIdToRooms = new Map<number, Room>();
  private socketsToUsers = new Map<string, User>();

  get rooms(): Room[] {
    const rooms = [];
    for (const [, room] of this.roomIdToRooms) {
      rooms.push(room);
    }

    return rooms;
  }

  onSocketConnect(socket: Socket) {
    const user = new User(socket);
    this.socketsToUsers.set(socket.id, user);
    user.sync();
  }

  async onSocketDisconnect(socket: Socket) {
    const user = this.getUser(socket);

    if (user.room != null) {
      await user.room.leave(user);
    }

    this.deleteEmptyRooms();
  }

  onTimeSync(socket: Socket, requestId: number, clientTime: number) {
    const user = this.getUser(socket);

    const now = getCurrentTime();
    user.emit("time_sync", {
      requestId: requestId,
      clientDiff: now - clientTime,
      serverTime: now,
    });
  }

  onSetRoomOptions(socket: Socket, options: RoomOptions) {
    const user = this.getUser(socket);

    if (user.room?.getLeader() == user) {
      user.room.setOptions(options);
    }
  }

  onRequestStart(socket: Socket) {
    const user = this.getUser(socket);

    if (user.room?.getLeader() === user) {
      user.room.start();
      user.room.sync();
    }
  }

  async onRequestJoin(socket: Socket, roomId: number) {
    const user = this.getUser(socket);
    if (user.room && user.room.id == roomId) return false;

    if (user.room) {
      await user.room.leave(user);
      this.deleteEmptyRooms();
    }

    if (!this.roomIdToRooms.has(roomId)) {
      this.createRoomWithId(roomId);
    }

    const room = this.roomIdToRooms.get(roomId);
    if (!room) {
      return false;
    }

    await room.join(user);

    return true;
  }

  onRequestSetReady(socket: Socket) {
    const user = this.getUser(socket);
    if (!user.room || user.readyToParticipate) return;
    user.readyToParticipate = true;
    user.room.sync();
  }

  async onRequestJoinRandom(socket: Socket) {
    const user = this.getUser(socket);

    if (user.room) {
      await user.room.leave(user);
      this.deleteEmptyRooms();
    }

    const room = this.createRandomRoom();
    if (!room) throw Error("Too many rooms active");
    await room.join(user);
  }

  hasRoomId(roomId: number): boolean {
    return this.roomIdToRooms.has(roomId);
  }

  submitTickerMessage(socket: Socket, message: string) {
    const user = this.getUser(socket);

    if (!user.room) {
      throw new Error("User has no room");
    }

    user.room.submitTickerMessage(user, message);
  }

  private getUser(socket: Socket): User {
    const user = this.socketsToUsers.get(socket.id);
    if (!user) {
      throw new Error("User not found");
    }
    return user;
  }

  private deleteEmptyRooms() {
    for (const room of this.roomIdToRooms.values()) {
      if (room.users.length == 0) {
        this.deleteRoom(room);
      }
    }
  }

  private createRandomRoom(): Room | null {
    let tries = 0;
    let i = 1;
    while (tries++ < 1000) {
      if (this.roomIdToRooms.has(i)) {
        i++;
      } else {
        return this.createRoomWithId(i);
      }
    }
    return null;
  }

  private createRoomWithId(roomId: number): Room {
    if (this.roomIdToRooms.has(roomId)) {
      throw new Error("A room with the given id already exists");
    }

    const room = new Room(roomId);
    this.roomIdToRooms.set(roomId, room);
    return room;
  }

  private deleteRoom(room: Room) {
    this.roomIdToRooms.delete(room.id);
  }
}
