import { Subject } from "../observe/Observable";
import { Observer } from "../observe/Observer";
import { JsonAPI } from "../singleton/JsonAPI";
import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel";
import { ChatModelHelper } from "./ChatModelHelper";
import log = require("loglevel");
import { ObserverData } from "../observe/ObserverData";
import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel";
import moment = require("moment");

interface Params {
  userName: string;
  data: ChatMessageViewModel[];
  op: string;
}

export class ChatModel implements Subject<ChatMessageViewModel> {
  /**
   * @type {Observer[]} List of subscribers. In real life, the list of
   * subscribers can be stored more comprehensively (categorized by event
   * type, etc.).
   */
  private readonly _observers: Observer<ChatMessageViewModel>[] = [];
  private readonly _messagePageMap: Map<string, number>;
  private readonly _messagesMap: Map<string, ChatMessageViewModel[]>;
  private readonly _chatModelHelper: ChatModelHelper;

  constructor(chatModelHelper: ChatModelHelper) {
    this._messagePageMap = new Map();
    this._messagesMap = new Map();
    this._chatModelHelper = chatModelHelper;
  }
  /**
   * The subscription management methods.
   */
  public attach(observer: Observer<ChatMessageViewModel>): void {
    log.info("Subject: Attached an observer.");
    this._observers.push(observer);
  }

  public detach(observer: Observer<ChatMessageViewModel>): void {
    const observerIndex = this._observers.indexOf(observer);
    this._observers.splice(observerIndex, 1);
    log.info("Subject: Detached an observer.");
  }

  private storeUserMessages(
    username: string,
    messages: ChatMessageViewModel[],
    op: string
  ) {
    switch (op) {
      case "clear":
        this._messagesMap.set(username, []);
        break;
      case "page":
        this._messagesMap.set(
          username,
          messages.concat(this.getStoredUserMessages(username))
        );
        break;
      // case "page": this._messagesMap.set(username, messages);
      case "new":
        this._messagesMap.set(
          username,
          this.getStoredUserMessages(username).concat(messages)
        );
        break;
      case "update":
        this._messagesMap.set(
          username,
          this.getStoredUserMessages(username).concat(messages)
        );
        break;
      default:
        new Error("Invalid option");
    }
  }

  private getStoredUserMessages(username: string): ChatMessageViewModel[] {
    let temp = this._messagesMap.get(username);
    if (temp == null) return [];
    else {
      return temp;
    }
  }

  /**
   * Trigger an update in each subscriber.
   */
  public notify(p: Params): void {
    log.info("Subject: Notifying observers...");
    switch (p.op) {
      case "clear":
        {
          const od: ObserverData<ChatMessageViewModel> = {
            data: [],
            op: "clear",
          };
          for (const observer of this._observers) {
            observer.update(od);
          }
        }
        break;
      case "new":
        {
          const od: ObserverData<ChatMessageViewModel> = {
            data: p.data,
            op: p.op,
          };
          for (const observer of this._observers) {
            observer.update(od);
          }
        }
        break;
      case "page":
        {
          const od: ObserverData<ChatMessageViewModel> = {
            data: p.data,
            op: p.op,
          };
          for (const observer of this._observers) {
            observer.update(od);
          }
        }
        break;
      case "update":
        {
          const od: ObserverData<ChatMessageViewModel> = {
            data: p.data,
            op: p.op,
          };
          for (const observer of this._observers) {
            observer.update(od);
          }
        }
        break;
      default: {
        log.error("error");
      }
    }
  }

  public someBusinessMethod(chatMessageList: ChatMessageViewModel[]): void {}

  public clear(): void {
    log.info("Clearing model");
    this._messagePageMap.set(JsonAPI.contactName!, 0);
    this.storeUserMessages(JsonAPI.contactName!, Array(), "clear");
    this.notify({ userName: "", data: [], op: "clear" });
  }

  public async getMessages(
    aVm: ActiveUserViewModel,
    op: string
  ): Promise<ChatMessageViewModel[]> {
    if (this._messagePageMap.get(aVm.userName!) == null)
      this._messagePageMap.set(aVm.userName!, 0);

    const pageNumber = this._messagePageMap.get(aVm.userName!);
    const cVMs = await this._chatModelHelper.getMessages(
      aVm.userName!,
      aVm.passphrase,
      pageNumber!,
      aVm.lastMessageTime!,
      op
    );
    let cVMsFiltered = Array<ChatMessageViewModel>();
    if (cVMs != null && cVMs.length != 0) {
      log.info("Subject: My state has just changed");
      const existingMessages = this.getStoredUserMessages(aVm.userName!);
      log.debug("existing message:");
      log.debug(existingMessages);
      log.debug("new messages:");
      log.debug(cVMs);

      // filter duplicates
      cVMsFiltered = cVMs.filter((c) => {
        const res = existingMessages.filter((m) => {
          if (moment(c.messageTime).isSame(moment(m.messageTime))) return true;
        });
        if (res.length > 0) return false;
        return true;
      });

      if (op == "update") {
        //   update the active user last message text and times for each user
        const lastMessage = cVMsFiltered[cVMsFiltered.length - 1];
        cVMsFiltered.forEach((v) => {
          if (v.fromUser == aVm.userName) {
            aVm.lastMessageTime = lastMessage.messageTime;
            aVm.lastMessageText = lastMessage.message.slice(0, 15) + "...";
          }
        });
      } else {
        this._messagePageMap.set(
          aVm.userName!,
          this._messagePageMap.get(aVm.userName!)! + 1
        );
      }

      this.storeUserMessages(aVm.userName!, cVMsFiltered, op);
      this.notify({ userName: aVm.userName!, data: cVMsFiltered, op: op });
    }

    return cVMsFiltered;
  }

  public async isPassphraseValid(
    passphrase: string,
    userName: string
  ): Promise<boolean> {
    let valid = await this._chatModelHelper.isPassphraseValid(
      passphrase,
      userName
    );
    return valid;
  }
}
