import {CodeEvent, EventEvent, InitEvent, Message, PlayEvent, UUID} from "@solid/types";
import {API, timestampToPts, Utils} from "@solid/libs";
import WebSocket from "ws";
import {performance} from "perf_hooks";

export class Checkws {
  api: API;
  constructor(api: API) {
    this.api = api;
  }
  async check(id: UUID, startTime: number, endTime: number, isLocal: boolean = false): Promise<void> {
    const {url} = await this.getURL({
      cameraid: id,
      startTime,
      endTime,
      isLocal
    });

    const buffer = [];

    console.log(`connecto to ${url}`);

    // const protocols = isLocal ? "media.videonext.com" : undefined;
    const websocket = new WebSocket(url, {
      rejectUnauthorized: false
    });
    websocket.binaryType = "arraybuffer";

    return new Promise((resolve, reject) => {
      let size = 0;
      let time = performance.now();
      websocket.addEventListener("open", async () => {
        console.log("ws open");

        let isFirstChunk = false;
        websocket.addEventListener("message", (message) => {
          if (message.data instanceof ArrayBuffer) {
            //console.log("chunk> " + message.data.byteLength + " bytes");

            size += message.data.byteLength;
            buffer.push(message.data);
          } else {
            // console.log("message> ", message.data);

            const data: Message = JSON.parse(message.data as string);
            if ("mime" in data) {
              console.log("< ", message.data);

              // const mimeEvent = data as MimeEvent;

              console.log("init >", performance.now() - time, "ms");
              time = performance.now();

              const playCMD: PlayEvent = {cmd: "play", params: {}};
              const playCMDMessage = JSON.stringify(playCMD);
              console.log("> ", playCMDMessage);
              websocket.send(playCMDMessage);
            } else
            if ("pts" in data) {
              // const ptsEvent = data as PtsEvent;

              if (!isFirstChunk) {
                isFirstChunk = true;

                console.log("startPlay >", performance.now() - time, "ms");
                time = performance.now();
              }
            } else
            if ("event" in data) {
              console.log("< ", message.data);

              const ptsEvent = data as EventEvent;
              if (ptsEvent.event == "EOC" || ptsEvent.event == "EOS" || ptsEvent.event == "EOA") {
                console.log("end >", performance.now() - time, "ms");
                console.log("size >", size / 1024 / 1024, "MB");

                websocket.close();

                resolve();
              }
            } else
            if ("code" in data) {
              const codeEvent = data as CodeEvent
              console.error(`[${codeEvent.code}] ${codeEvent.error}`);

              websocket.close();

              reject(`[${codeEvent.code}] ${codeEvent.error}`);
            }
          }
        });
        websocket.addEventListener("error", () => {
          console.error('onwserror');

          websocket.close();

          reject();
        });

        try {
          const {ticket} = await this.api.getMediaTicket({
            cameraid: id,
            isLocal
          });

          const init: InitEvent = {
            cmd: "init",
            objid: id,
            streamid: "1",
            params: {
              startTime: timestampToPts(startTime),
              endTime: timestampToPts(endTime),
              fast: true,
              raw: true
            },
            ticket: ticket
          };

          const initMessage = JSON.stringify(init);
          console.log("> ", initMessage);
          websocket.send(initMessage);
        }
        catch (e) {
          console.error(e);
        }
      });

      websocket.onerror = (e) => {
        console.log("onerror", e);
      };

      websocket.addEventListener("close", (e) => {
        let message = Utils.webSocketCloseCodeMessage(e.code);
        console.log(`onwsclose: [${e.code}] ${message}`);

        console.log("end >", performance.now() - time, "ms");
        console.log("size >", size / 1024 / 1024, "MB");

        reject();
      });
    });
  }

  async getURL({cameraid, startTime, endTime, streamnum = "1", isLocal = false}: any): Promise<{ url: string }> {
    try {
      let parameters: any = {
        cameraid,
        streamnum,
        isLocal
      };
      if (startTime && endTime) {
        parameters.startTime = timestampToPts(startTime);
        parameters.endTime = timestampToPts(endTime);
      }
      const {url} = await this.api.getMediaURL(parameters);
      return {url};
    }
    catch ({message}) {
      throw {message};
    }
  }
}
