const protobuf = require('protobufjs');
const ws = require('nodejs-websocket');

class ClientNet {

  constructor(url, protoPbFile, msgIdPbFile) {
    this.url = url;
    this.conn = null;
    this.protoPbFile = protoPbFile;
    this.msgIdPbFile = msgIdPbFile;
    this.protoPb = null;
    this.msgIdPb = null;
    this.cmMsgId = null;
    this.smMsgId = null;
    this.recvBuf = Buffer.alloc(0);
    this.uniqId = 1000;
    this.msgHandlerMap = new Map();
  }

  async init() {
    {
      const {err, root} = await protobuf.load(this.protoPbFile);
      this.protoPb = root;
    }
    {
      const {err, root} = await protobuf.load(this.msgIdPbFile);
      this.msgIdPb = root;
      this.cmMsgId = this.msgIdPb.lookup('CMMessageId_e');
      this.smMsgId = this.msgIdPb.lookup('SMMessageId_e');
    }
  }

  async connect() {
    this.conn = await ws.connect(this.url);
    this.on('binary', this.#onReceive.bind(this));
  }

  on(eventName, ...args) {
    this.conn.on(eventName, ...args);
  }

  async #onReceive(inStream) {
    inStream.on('readable', async () => {
      //console.log('inStream.readable');
      const newData = inStream.read();
      if (newData) {
        this.recvBuf = Buffer.concat([this.recvBuf, newData]);
        await this.#onParsePacket();
      }
    });
    inStream.on('end', () => {
      //console.log('inStream.end', this.recvBuf.length);
    });
    inStream.on('close', () => {
      //console.log('inStream.close');
    });
  }

  async #onParsePacket() {
    while (this.recvBuf.length > 12) {
      const msgSize = this.recvBuf.readUInt16LE(0);
      const msgId = this.recvBuf.readUInt16LE(2);
      const seqId = this.recvBuf.readUInt32LE(4);
      const magicCode = this.recvBuf.readUInt16LE(8);
      const ext = this.recvBuf.readUInt16LE(10);
      if (this.recvBuf.length >= 12 + msgSize) {
        await this.#processMsg(msgId, this.recvBuf.slice(12, 12 + msgSize));
        this.recvBuf = this.recvBuf.slice(12 + msgSize);
      }
    }
  }

  async #processMsg(msgId, buff) {
    const handlers = this.msgHandlerMap.get(msgId);
    if (handlers) {
      let msg = null;
      handlers.forEach((value, key) => {
        if (!msg) {
          msg = value.msgType.decode(buff);
        }
        value.cb(msg);
      });
    }
  }

  registerMsgHandle(msgName, cb) {
    const msgType = this.protoPb.lookupType(msgName);
    const msgId = this.smMsgId.values['_' + msgName];
    if (!msgId) {
      return null;
    }
    const handle = {
      msgId: msgId,
      msgName: msgName,
      msgType: msgType,
      cb: cb,
      uniqId: ++this.uniqId}
    ;
    if (!this.msgHandlerMap.has(msgId)) {
      this.msgHandlerMap.set(msgId, new Map([
        [handle.uniqId, handle]
      ]));
    } else {
      this.msgHandlerMap[msgId].set(handle.uniqId, handle);
    }
    return handle;
  }

  unRegisterMsgHandle(handle) {
    if (this.msgHandlerMap.has(handle.msgId)) {
      if (this.msgHandlerMap[handle.msgId].has(handle.uniqId)) {
        this.msgHandlerMap[handle.msgId].delete(handle.uniqId);
      }
    }
  }

  async sendMsg(name, msg) {
    const msgType = this.protoPb.lookupType(name);
    const msgId = this.cmMsgId.values['_' + name];
    const msgPb = msgType.create(msg);

    const msgBuf = msgType.encode(msg).finish();
    let buf = Buffer.alloc(12);
    buf.writeInt16LE(msgBuf.length, 0);
    buf.writeInt16LE(msgId, 2);
    buf.writeInt32LE(0, 4);
    buf.writeUInt8('K'.charCodeAt(0), 8);
    buf.writeUInt8('S'.charCodeAt(0), 9);
    buf.writeInt16LE(0, 10);
    this.conn.sendBinary(Buffer.concat([buf, msgBuf]));
    console.log(name, msg);
  }

}

module.exports = ClientNet;
