import * as net from "net";
import { EventEmitter } from "events";
import { CmdData, Package } from './Dto';

export type Options = net.TcpNetConnectOpts & { reconnectDelay?: number; };

export interface SocketClient {
  on(event: "connect", listener: () => void): this;
  on(event: "data", listener: (data: Buffer) => void): this;

  /**
   * 收到数据后，解码成功时触发
   * @param event
   * @param listener
   */
  on(event: "package", listener: (pack: Package) => void): this;

  /**
   * 收到数据后，解码成功且指令为n指令时触发
   * @param event
   * @param listener
   */
  on(event: "cmd", listener: (data: CmdData) => void): this;

  on(event: "error", listener: (err: Error) => void): this;
  on(event: "decodeError", listener: (err: Error) => void): this;
  on(event: "close", listener: (hadError: boolean) => void): this;
}

/**
 * 接收数据转义
 * @param data
 * @returns
 */
function receive_escape(data: number[]) {
  let arr: number[] = [];

  for (let i = 0; i < data.length; i++) {
    //转义 0xff 0xbb => 0xfb 0xff 0xfc => 0xff 0xff 0xee => 0xfe
    if (data[i] === 0xff) {
      if (data[i + 1] === 0xbb) {
        arr.push(0xfb);
        i++;
      } else if (data[i + 1] === 0xfc) {
        arr.push(0xff);
        i++;
      } else if (data[i + 1] === 0xee) {
        arr.push(0xfe);
        i++;
      }
    } else {
      arr.push(data[i]);
    }
  }

  return arr;
}

/**
 * 发送数据转义
 * @param data
 * @returns
 */
function send_escape(data: Buffer) {
  let arr: number[] = [];

  for (let i = 0; i < data.length; i++) {
    //转义 0xfb => 0xff 0xbb  0xff => 0xff 0xfc  0xfe => 0xff 0xee
    if (data[i] === 0xfb) {
      arr.push(0xff, 0xbb);
    } else if (data[i] === 0xff) {
      arr.push(0xff, 0xfc);
    } else if (data[i] === 0xfe) {
      arr.push(0xff, 0xee);
    } else {
      arr.push(data[i]);
    }
  }

  return arr;
}

/**
 * 解码（反序列化）
 * @param datas
 * @returns
 */
function decode(datas: Buffer[]): {
  packs: Package[];
  remains: Buffer[];
} {
  let packs: Package[] = [];
  let unescape: number[] = null;
  let _i = 0;
  let _j = 0;

  for (let i = 0; i < datas.length; i++) {
    for (let j = 0; j < datas[i].length; j++) {
      if (datas[i][j] === 0xfb) {
        unescape = [0xfb];
      } else {
        if (unescape) {
          unescape.push(datas[i][j]);

          if (datas[i][j] === 0xfe) {
            let escape = receive_escape(unescape);
            let data_length = Buffer.from(escape.slice(10, 12)).readUInt16BE();
            let checksum = Buffer.from(
              escape.slice(12 + data_length, 12 + data_length + 2)
            ).readUInt16BE();

            let new_checksum = 0;

            for (let i = 1; i < escape.length - 3; i++) {
              new_checksum += escape[i];

              //防溢出
              new_checksum = new_checksum & 0xffff;
            }

            if (new_checksum === checksum) {
              let pack: Package = {
                ts: Buffer.from(escape.slice(1, 5)).readUInt32BE(),
                cmd: <any>String.fromCharCode(...escape.slice(5, 6)),
                packageTotal: Buffer.from(escape.slice(6, 8)).readUInt16BE(),
                packageNo: Buffer.from(escape.slice(8, 10)).readUInt16BE(),
                dataLength: data_length,
                data: Buffer.from(escape.slice(12, 12 + data_length)),
              };

              packs.push(pack);
            }

            _i = i;
            _j = j;
            unescape = null;
          }
        }
      }
    }
  }

  let remains: Buffer[] = [];
  for (let i = _i; i < datas.length; i++) {
    if (i === _i) {
      if (_j === datas[i].length - 1) continue;
      remains.push(datas[i].subarray(_j + 1));
    } else {
      remains.push(datas[i]);
    }
  }

  return { packs, remains };
}

/**
 * 编码（序列化）
 * @param cmd
 * @param data
 * @returns
 */
export function encode(cmd: string, data: Buffer) {
  let ts_buf = Buffer.alloc(4);
  ts_buf.writeInt32BE(Math.floor(Date.now() / 1000));

  let cmd_buf = Buffer.alloc(1);
  cmd_buf.writeUInt8(cmd.charCodeAt(0));

  let pack_total = Math.ceil(data.length / 1024) || 1;
  let pack_total_buf = Buffer.alloc(2);
  pack_total_buf.writeInt16BE(pack_total);

  let pack_no_buf = Buffer.alloc(2);
  let data_length_buf = Buffer.alloc(2);
  let checksum_buf = Buffer.alloc(2);

  let bufs: Buffer[] = [];

  for (let i = 0; i < pack_total; i++) {
    let data_buf = data.subarray(i * 1024, (i + 1) * 1024);

    pack_no_buf.writeInt16BE(i);
    data_length_buf.writeUInt16BE(data_buf.length);

    let unescape = Buffer.concat([
      ts_buf,
      cmd_buf,
      pack_total_buf,
      pack_no_buf,
      data_length_buf,
      data_buf,
      checksum_buf,
    ]);

    let checksum = 0;
    for (let i = 0; i < unescape.length - 2; i++) {
      checksum += unescape[i];
    }

    unescape.writeUInt16BE(checksum, unescape.length - 2);

    let escape = send_escape(unescape);
    let buf = Buffer.concat([
      Uint8Array.from([0xfb]),
      Buffer.from(escape),
      Uint8Array.from([0xfe]),
    ]);
    bufs.push(buf);
  }

  return bufs;
}

/**
 * 视频服务程序SOCKET客户端
 */
export class SocketClient {
  private _socket: net.Socket = null;
  readonly opts: Options = null;
  private _emitter: EventEmitter;
  private _datas: Buffer[] = null;
  private _disposed = null;
  private _heartbeatTimer?: NodeJS.Timeout;

  get socketInstance() {
    return this._socket;
  }

  constructor(opts: Options) {
    this.opts = Object.assign({}, opts);
    this._emitter = new EventEmitter();
    this._connect();
  }

  on(event: string, listener: (...args: any[]) => void): this {
    this._emitter.on(event, listener);
    return this;
  }

  /**
   * @param cmd 'A' | 'C' | 'D' | 'F' | 'n' | 'Z' | 'P' | 'V' 等
   * @param data
   */
  send(cmd: string, data: Buffer | object) {
    this._send(cmd, Buffer.isBuffer(data) ? data : Buffer.from(JSON.stringify(data)));
  }

  destroy() {
    this._dispose();
  }

  private _connect() {
    let socket = net.connect(this.opts);

    socket.on("connect", () => {
      this._heartbeatTimer = setInterval(() => {
        this.send("F", Buffer.alloc(0));
      }, 1000 * 5);

      this._emitter.emit("connect");
    });

    socket.on("data", (data) => {
      this._onData(data);
      this._emitter.emit("data", data);
    });

    socket.on("error", (err) => {
      this._emitter.emit("error", err);
    });

    socket.on("close", async (hadError) => {
      this._onClose();
      this._emitter.emit("close", hadError);
    });

    this._socket = socket;
  }

  private async _reconnect() {
    if (this._disposed) return;

    await new Promise((resolve) => { setTimeout(resolve, this.opts.reconnectDelay || 1000 * 10); });

    this._connect();
  }

  private _onData(data: Buffer) {
    if (!data || data.length === 0) return;

    try {
      if (data[0] === 0xfb) {
        this._datas = [data];
      } else {
        if (this._datas) {
          if (this._datas.length > 20) {
            this._datas.shift();
          }

          this._datas.push(data);
        } else {
          this._datas = [data];
        }
      }

      if (!data.includes(0xfe)) return;

      let dec = decode(this._datas);
      for (const pack of dec.packs) {
        this._emitter.emit("package", pack);

        if (pack.cmd === "n") {
          try {
            let data = JSON.parse(pack.data.toString('utf-8')) as CmdData;
            if (data.cmd) {
              this._emitter.emit("cmd", data);
            }
          } catch (error) {
            this._emitter.emit("decodeError", error);
          }
        }
      }

      this._datas = dec.remains;
    } catch (error) {
      this._emitter.emit("decodeError", error);
    }
  }

  private _onClose() {
    clearInterval(this._heartbeatTimer);
    this._datas = null;
    this._reconnect();
  }

  private _send(cmd: string, data: Buffer) {
    if (!this._socket || !this._socket.writable || this._socket.destroyed) return;

    let bufs = encode(cmd, data);
    for (const buf of bufs) {
      this._socket.write(buf);
    }
  }

  private _dispose() {
    this._emitter.removeAllListeners();
    this._socket = null;
    this._socket?.destroy();
    
    this._disposed = true;
  }
}