import { ServerInformation, serverInformationEndpoint, TransportEvents } from 'shared';
import { io, Socket } from 'socket.io-client';
import { Configuration } from './configuration';
import parser from 'socket.io-msgpack-parser';
import { SoundHandler, Sounds } from './sound-handler';

export type PlayerDecision = {
  name: string;
  server: string;
};

const pollInterval = 8000;
export class JoinFormHandler {
  private joinButton: HTMLButtonElement;
  private waitingForDecision: Promise<PlayerDecision>;
  private resolvePlayerDecision!: (d: PlayerDecision) => void;
  private pollServersTimer: any;
  private keyUpListener = (e: KeyboardEvent) => {
    if (e.key === 'enter') {
      this.form.submit();
    }
  };

  constructor(
    private form: HTMLFormElement,
    private readonly container: HTMLElement,
  ) {
    this.joinButton = form.querySelector('button[type="submit"]') as HTMLButtonElement;
    this.joinButton.disabled = true;
    this.waitingForDecision = new Promise((r) => (this.resolvePlayerDecision = r));

    new FormData(form);

    addEventListener('keyup', this.keyUpListener);

    form.onsubmit = (e) => {
      SoundHandler.play(Sounds.click);
      const result: PlayerDecision = (
        Array.from((new FormData(form) as any).entries()) as Array<[string, any]>
      ).reduce((result, [name, value]) => {
        (result as any)[name] = value;
        return result;
      }, {}) as any;

      this.resolvePlayerDecision(result);

      e.preventDefault();
    };

    this.pollServersTimer = setInterval(this.loadServers.bind(this), pollInterval);
    this.loadServers();
    this.waitForFinish();
  }

  private async waitForFinish() {
    await this.waitingForDecision;
    this.destroy();
  }

  private destroy() {
    removeEventListener('keyup', this.keyUpListener);
    clearInterval(this.pollServersTimer);
    this.servers.forEach((s) => s.destroy());
  }

  private servers: Array<ServerChooserOption> = [];
  private async loadServers() {
    await Configuration.initialize();

    const serverList = Configuration.servers.filter(
      (u) => !this.servers.find((s) => s.url === u),
    );

    serverList.map(async (url) => {
      const controller = new AbortController();
      const signal = controller.signal;
      setTimeout(() => controller.abort(), pollInterval * 0.8);

      let response: Response | undefined;
      try {
        response = await fetch(url + serverInformationEndpoint, { signal });
      } catch {
        // it's okay
      }

      if (response?.ok) {
        const content: ServerInformation = await response.json();
        if (!this.servers.find((s) => s.url === url)) {
          const server = new ServerChooserOption(
            content,
            url,
            this.removeServer.bind(this),
            this.servers.length === 0,
          );
          this.servers.push(server);
          this.joinButton.disabled = false;
          this.container.appendChild(server.element);
        }
      }
    });
  }

  private removeServer(server: ServerChooserOption) {
    this.servers = this.servers.filter((s) => s !== server);
    if (!this.servers.length) {
      this.joinButton.disabled = true;
    }
  }

  public async getPlayerDecision(): Promise<PlayerDecision> {
    return this.waitingForDecision;
  }
}

class ServerChooserOption {
  private divElement = document.createElement('div');
  private inputElement = document.createElement('input');
  private labelElement = document.createElement('label');
  private serverNameElement = document.createElement('span');
  private completionElement = document.createElement('span');

  private socket: Socket;

  constructor(
    private content: ServerInformation,
    public readonly url: string,
    private onDestroy: (v: ServerChooserOption) => unknown,
    isFirst: boolean,
  ) {
    this.inputElement.required = true;
    this.inputElement.type = 'radio';
    this.inputElement.id = this.inputElement.value = url;
    this.inputElement.name = 'server';
    this.inputElement.checked = isFirst;
    this.labelElement.htmlFor = url;
    this.labelElement.onclick = () => SoundHandler.play(Sounds.click);
    this.divElement.appendChild(this.inputElement);
    this.divElement.appendChild(this.labelElement);
    this.labelElement.appendChild(this.serverNameElement);
    this.labelElement.appendChild(document.createElement('br'));
    this.labelElement.appendChild(this.completionElement);
    this.completionElement.className = 'completion';
    this.setServerInfoLabelText();

    this.socket = io(url, {
      reconnection: true,
      reconnectionAttempts: 5,
      timeout: 4000,
      parser,
    } as any);

    this.socket.io.on('reconnect_failed', this.destroy.bind(this));

    this.socket.on('connect', () =>
      this.socket.emit(TransportEvents.SubscribeForServerInfoUpdates),
    );
    this.socket.on(
      TransportEvents.ServerInfoUpdate,
      ([playerCount, gameState]: [number, number]) => {
        this.content.playerCount = playerCount;
        this.content.gameStatePercent = gameState;
        this.setServerInfoLabelText();
      },
    );
  }

  public destroy() {
    this.socket.close();
    this.divElement.parentElement?.removeChild(this.divElement);
    this.onDestroy(this);
  }

  private getRoundCompletionText(percent: number): string {
    const texts = [
      'Just started',
      'Just started',
      'Ongoing',
      'Halfway through',
      'Nearly over',
      'About to finish',
      'Game is over',
    ];

    return texts[Math.floor((percent / 100) * (texts.length - 1))];
  }

  private setServerInfoLabelText() {
    this.serverNameElement.innerText = `${this.content.serverName} - ${this.content.playerCount}/${this.content.playerLimit} players`;
    this.completionElement.innerText = this.getRoundCompletionText(
      this.content.gameStatePercent,
    );
  }

  public get element(): HTMLElement {
    return this.divElement;
  }
}
