import {Injectable} from '@angular/core';
import {Setting} from './setting';
import {ChatMessage} from './chat.message';
import {SocketRegistrationMessage} from './socket.registration.message';
import {SocketReceivedChatMessage} from './socketReceivedChatMessage';
import {WebsocketListener} from './websocket.listener';
import {SocketSendMessage} from './socket.send.message';
import {SocketKeepaliveMessage} from './socket.keepalive.message';
import {User} from './user';

@Injectable({
  providedIn: 'root'
})
export class WebsocketService {
  private socket: WebSocket = new WebSocket(Setting.WEBSOCKET);
  private userList: Map<number, string> = new Map<number, string>();
  private listener: WebsocketListener;
  private chatToken: string;
  private isReconnectDesired = true;
  private lastReconnectAttempts: Date[] = [];

  initializeSocket(chatToken: string): void {
    this.chatToken = chatToken;

    this.socket = new WebSocket(Setting.WEBSOCKET);
    this.socket.addEventListener('open', () => {this.authorize(); this.listener.onConnection(); });
    this.socket.addEventListener('message', (transmission: MessageEvent) => {this.onMessage(transmission); });
    this.socket.addEventListener('close', () => {this.onClose(); });
    this.socket.addEventListener('error', () => {this.onError(); });
  }

  destroy(): void
  {
    this.isReconnectDesired = false;
    this.socket.close();
  }

  setListener(listener: WebsocketListener): void {
    this.listener = listener;
  }

  sendChatMessage(message: string): void {
    const socketMessage: SocketSendMessage = {
      type: Response.CHAT_MESSAGE,
      message
    };
    this.socket.send(JSON.stringify(socketMessage));
  }

  sendKeepAliveMessage(): void {
    if (this.socket === null) {
      return;
    }

    const socketMessage: SocketKeepaliveMessage = {
      type: Response.KEEP_ALIVE
    };

    this.socket.send(JSON.stringify(socketMessage));
  }

  private authorize(): void {
    const message: SocketRegistrationMessage = {type: Response.REGISTRATION_MESSAGE, token: this.chatToken};
    this.socket.send(JSON.stringify(message));
  }

  private onMessage(transmission: MessageEvent): void {
    const response = JSON.parse(transmission.data);

    switch (response.type) {
      case Response.CHAT_MESSAGE:
        const messageReceived: SocketReceivedChatMessage = response;

        const message: ChatMessage = {
          id: messageReceived.id,
          userId: messageReceived.userId,
          username: this.userList.get(messageReceived.userId),
          datetime: messageReceived.datetime,
          message: messageReceived.message,
          htmlMessage: '',
        };

        this.listener.onChatMessage(message);

        break;

      case Response.REGISTRATION_MESSAGE:
        this.userList.set(response.userId, response.username);
        this.listener.onUserConnected({userId: response.userId, username: response.username});
        break;

      case Response.USERLIST:
        const userList: User[] = [];

        response.users.forEach(
          (user) => {
            this.userList.set(user.userId, user.username);

            userList.push(
              {
                userId: user.userId,
                username: user.username,
              }
            );
          }
        );

        this.listener.onUserListInit(userList);

        break;

      case Response.USER_DISCONNECTED:
        this.listener.onUserDisconnected(response.userId);
        break;

      default:
        throw new Error('Unknown message type: ' + response.type);
    }
  }

  private reconnect(): void
  {
    this.initializeSocket(this.chatToken);
    this.authorize();
    this.listener.onReconnect();
  }

  private needsAuthorizationForFurtherReconnectAttempts(): boolean {
    if (this.lastReconnectAttempts.length < 3) {
      return false;
    }

    const now = new Date().getTime();
    let recentAttempts = 0;

    for (const attempt of this.lastReconnectAttempts) {
      if (now - attempt.getTime() < 20000) {
        recentAttempts++;
      }
    }

    return recentAttempts >= 3;
  }

  private onError(): void {
    if (this.needsAuthorizationForFurtherReconnectAttempts()) {
      this.lastReconnectAttempts = [];
      this.isReconnectDesired = false;

      setTimeout(
        () => {
          this.isReconnectDesired = true;
          this.onClose();
        }, 10000
      );
    }

    if (this.isReconnectDesired) {
      this.lastReconnectAttempts.push(new Date());
    }

    this.listener.onError('Die Verbindung konnte nicht hergestellt werden!');
  }

  private onClose(): void
  {
    this.listener.onError('Verbindung unterbrochen!');

    if (this.isReconnectDesired) {
      this.reconnect();
    }
  }
}

enum Response {
  CHAT_MESSAGE = 1,
  REGISTRATION_MESSAGE,
  USERLIST,
  KEEP_ALIVE,
  USER_DISCONNECTED = 5,
}
