var net = require('net');
var autoConnectTimersCache = {};
var socketInstanceCache = {};
var KtLoggerWrapper = require('@keytop/ktloggerwrapper');
function KtSocketClient(host, port, connectedCallback, logger) {
  this.logger = new KtLoggerWrapper(logger);
  this.keyStr = (host + ':' + port).replace(/\s/g, '');
  if (autoConnectTimersCache[this.keyStr]) {
    var timerHandle = autoConnectTimersCache[this.keyStr];
    clearInterval(autoConnectTimersCache[this.keyStr]);
    delete autoConnectTimersCache[this.keyStr];
    this.logger.i(
      '释放Socket自动重连定时器:' + this.keyStr + '=' + timerHandle
    );
  }
  if (socketInstanceCache[this.keyStr]) {
    var socketInstance = socketInstanceCache[this.keyStr];
    delete socketInstanceCache[this.keyStr];
    if (socketInstance.close) {
      socketInstance.close();
      this.logger.i('释放Socket实例:' + this.keyStr);
    }
  }
  if (net.isIP(host) == 0) {
    throw new Error('Socket IP(' + host + ') 无效');
  }
  if (isNaN(port) || parseInt(port) <= 0) {
    throw new Error('Socket Port(' + port + ') 无效');
  }
  var $this = this;

  this.heartBeatTimer = null; //心跳定时器。
  this.autoConnectTimer = null; //断线自动重连定时器。
  this.isOpen = false;
  this.host = host; //Socket IP 地址
  this.port = port; //Socket 端口号
  this.lastReceiveDataTime = null; //最后一次收到数据包的时间。
  this.connectedCallback = connectedCallback; //Socket 连接成功回调函数。
  initSocket.call(this);
  socketInstanceCache[this.keyStr] = this;
}
function initSocket() {
  var $this = this;
  this.socket = new net.Socket();
  this.cacheData = null; //用于缓存不完整数据包。
  this.socket.on('data', function (bytesData) {
    try {
      if (bytesData.length > 0 && bytesData[0] == 251) {
        $this.cacheData = null;
      }
      if (
        bytesData.length > 0 &&
        bytesData[0] != 251 &&
        $this.cacheData &&
        $this.cacheData.length > 0 &&
        $this.cacheData[0] == 251
      ) {
        debugger;
        bytesData = $this.cacheData.concat(
          Array.prototype.slice.call(bytesData, 0)
        );
        $this.logger.w(
          '缓存数据包和本次数据包拼接后如下:' + bytesData.join(' ')
        );
        if (bytesData[bytesData.length - 1] != 254) {
          return;
        }
      }
      var dataPackages = decodePackages(bytesData);
      if (dataPackages && dataPackages.length > 0) {
        dataPackages.forEach(function (dataPackage) {
          if (isFunction($this.onReceiveData)) {
            $this.onReceiveData(dataPackage);
          }
        });
      }
    } finally {
      if (
        bytesData &&
        bytesData.length > 2 &&
        (bytesData[0] != 251 || bytesData[bytesData.length - 1] != 254)
      ) {
        $this.logger.w(
          'Socket->Socket收到不完整数据包:' + bytesData.slice(0).join(' ')
        );
        $this.logger.w('缓存不完整数据包:');
        $this.cacheData = Array.prototype.slice.call(bytesData, 0);
      }
    }
  });
  this.socket.on('close', function (hadError) {
    try {
      $this.logger.e('Socket Closed：(hadError=' + hadError + ')');
      $this.close(false);
    } finally {
      if (isFunction($this.onClose)) {
        $this.onClose(hadError);
      }
    }
  });
  //Socket 发生错误时触发。
  this.socket.on('error', function (e) {
    $this.logger.e('Socket Error：' + e.message);
    $this.close(false);
    if (isFunction($this.onError)) {
      $this.onError();
    }
  });
  //Socket 连接超时时触发
  this.socket.setTimeout(3000);
  this.socket.on('timeout', function () {
    if (!$this.heartBeatTimer) {
      $this.logger.e('Socket Timeout');
      $this.onClose();
    }
  });
}
function escapeSendData(byteArray) {
  var newByteArray = byteArray.reduce(function (preValue, currentValue, index) {
    if (currentValue == 0xfb) {
      //console.warn("escapeSendData 0xfb");
      preValue.push(0xff);
      preValue.push(0xbb);
    } else if (currentValue == 0xff) {
      //console.warn("escapeSendData 0xff");
      preValue.push(0xff);
      preValue.push(0xfc);
    } else if (currentValue == 0xfe) {
      //console.warn("escapeSendData 0xfe");
      preValue.push(0xff);
      preValue.push(0xee);
    } else {
      preValue.push(currentValue);
    }
    return preValue;
  }, []);
  return newByteArray;
}
/**
 * Socket 服务端一次性返回多个包的情况。
 * @param {Buffer} byteArray
 */
function decodePackages(byteArray) {
  var packages = [];
  for (var i = 0; i < byteArray.length; i++) {
    if (byteArray[i] == 0xfb) {
      var packageBytes = [];
      for (var j = i; j < byteArray.length; j++) {
        packageBytes.push(byteArray[j]);
        if (byteArray[j] == 0xfe) {
          var packageObj = decodePackage(packageBytes);
          packages.push(packageObj);
          break;
        }
      }
    }
  }
  return packages;
}
function decodePackage(byteArray) {
  //debugger;
  byteArray = escapeReceiveData(byteArray);
  var index = 1;

  //时间戳
  var timestamp = Buffer.from(byteArray.slice(index, index + 4)).readInt32BE();
  index += 4;

  //命令码
  var commond = String.fromCharCode(byteArray.slice(index, index + 1)[0]);
  index += 1;

  //总包数
  var totalPackages = Buffer.from(
    byteArray.slice(index, index + 2)
  ).readInt16BE();
  index += 2;

  //包序号
  var packageSerial = Buffer.from(
    byteArray.slice(index, index + 2)
  ).readInt16BE();
  index += 2;

  // 数据内容长度
  index += 2;

  var dataBytes = [];
  if (byteArray.length > 15) {
    for (var i = 12; i < byteArray.length - 3; i++) {
      dataBytes.push(byteArray[i]);
    }
  }
  var dataPackage = new DataPackage();
  dataPackage.commond = commond;
  dataPackage.timestamp = timestamp;
  dataPackage.totalPackage = totalPackages;
  dataPackage.packageSerialNo = packageSerial;
  dataPackage.data = dataBytes;
  return dataPackage;
}
function escapeReceiveData(byteArray) {
  var newByteArray = [];
  for (var i = 0; i < byteArray.length; i++) {
    if (byteArray[i] == 0xff) {
      if (byteArray[i + 1] == 0xbb) {
        //console.warn("escapeReceiveData 0xff");
        newByteArray.push(0xfb);
        i++;
      } else if (byteArray[i + 1] == 0xfc) {
        //console.warn("escapeReceiveData 0xfc");
        newByteArray.push(0xff);
        i++;
      } else if (byteArray[i + 1] == 0xee) {
        //console.warn("escapeReceiveData 0xee");
        newByteArray.push(0xfe);
        i++;
      } else {
        newByteArray.push(byteArray[i]);
      }
    } else {
      newByteArray.push(byteArray[i]);
    }
  }
  return newByteArray;
}
function combineBytes(toArray, fromArray) {
  for (var i = 0; i < fromArray.length; i++) {
    toArray.push(fromArray[i]);
  }
}
function accumulateCheckCode(byteArray) {
  var sum = 0;
  for (var i = 0; i < byteArray.length; i++) {
    sum += byteArray[i];
  }
  //console.log("sum=" + sum);
  return sum;
}
function isFunction(obj) {
  return Object.prototype.toString.call(obj) === '[object Function]';
}
/**
 * 数据包信息描述。
 */
function DataPackage() {
  this.timestamp = 0; //时间戳
  this.commond = ''; //命令码
  this.data = []; //数据体
  this.totalPackage = 0; //总包数
  this.packageSerialNo = ''; //包序号
}
/**
 * Socket连接心跳。
 * heartBeatInterval：多少秒发一次心跳包(默认5秒)。
 */
function socketHeartBeat(heartBeatInterval) {
  if (isNaN(heartBeatInterval) || heartBeatInterval <= 0) {
    heartBeatInterval = 5;
  }
  var $this = this;
  //console.log("heartBeatInterval=" + heartBeatInterval);
  return setInterval(function () {
    //console.log("Send HeartBeat(" + new Date().toLocaleString() + ")");
    $this.sendData('F', [0x00]);
  }, parseInt(heartBeatInterval) * 1000);
}
/**
 * Socket 断线自动重连机制。
 */
function socketAutoConnect() {
  var $this = this;
  return setInterval(function () {
    if (!$this.socket || $this.socket.destroyed) {
      //console.log("Socket Reconnect(" + $this.host + ":" + $this.port + ")...");
      $this.logger.i(
        'Socket Reconnect(' + $this.host + ':' + $this.port + ')...'
      );
      $this.connect($this.host, $this.port, $this.connectedCallback);
    }
  }, 3000);
}
/**
 * 启动Socket 连接
 */
KtSocketClient.prototype.connect = function () {
  var $this = this;
  if (this.isOpen) {
    return;
  }
  if (!this.socket || this.socket.destroyed) {
    this.close(false);
    initSocket.call($this);
  }
  this.socket.connect(this.port, this.host, function () {
    $this.logger.i(
      '********Socket Connected(' +
        $this.host +
        ':' +
        $this.port +
        '，本地IP：' +
        $this.socket.localAddress +
        ':' +
        $this.socket.localPort +
        ')******** '
    );
    if ($this.heartBeatTimer) {
      clearInterval($this.heartBeatTimer);
    }
    $this.heartBeatTimer = socketHeartBeat.call($this); //启动心跳
    if (isFunction($this.connectedCallback)) {
      $this.connectedCallback();
    }
  });
  this.isOpen = true;
  if (!this.autoConnectTimer) {
    autoConnectTimersCache[
      this.keyStr
    ] = this.autoConnectTimer = socketAutoConnect.call($this); //启动断开自动重连机制。
  }
};
/**
 * 发送数据到服务端
 */
KtSocketClient.prototype.sendData = function (
  cmd,
  contentBytes,
  totalPackages,
  packageSerialNo
) {
  // if (!this.socket || this.socket.destroyed) {
  //     console.log("KtSocketClient->sendData Failure,Socket Destroyed");
  //     return;
  // }
  try {
    if (totalPackages == undefined || totalPackages == null) {
      totalPackages = 1;
    }
    if (packageSerialNo == undefined || packageSerialNo == null) {
      packageSerialNo = 0;
    }
    //debugger
    // package=0xfb(协议头)+时间戳(4byte)+命令码(1byte)+总包数(值为1,2byte)
    // +包序列号(值为0,2byte)+数据长度(2byte)+数据体(nbyte)+校验码(2byte)+0xfe(协议尾)
    var bytes = [];
    var timestampBuffer = Buffer.alloc(4);
    timestampBuffer.writeInt32BE(Math.floor(Date.now() / 1000));
    var timestampBytes = new Uint8Array(timestampBuffer);
    combineBytes(bytes, timestampBytes); // 时间戳

    bytes.push(cmd.charCodeAt(0)); // 命令码

    var totalPackageBuffer = Buffer.alloc(2);
    totalPackageBuffer.writeInt16BE(totalPackages);
    var totalPackageBytes = new Uint8Array(totalPackageBuffer);
    combineBytes(bytes, totalPackageBytes); // 总包数

    var packageSerialBuffer = Buffer.alloc(2);
    packageSerialBuffer.writeInt16BE(packageSerialNo);
    var packageSerialBytes = new Uint8Array(packageSerialBuffer);
    combineBytes(bytes, packageSerialBytes); // 包序列号

    var dataLengthBuffer = Buffer.alloc(2);
    dataLengthBuffer.writeUInt16BE(contentBytes.length);
    var dataLengthBytes = new Uint8Array(dataLengthBuffer);
    combineBytes(bytes, dataLengthBytes); // 数据长度

    var sum = accumulateCheckCode(bytes);
    sum += accumulateCheckCode(contentBytes);
    //校验码值。

    combineBytes(bytes, contentBytes); // 数据体

    var checkCodeBuffer = Buffer.alloc(2);
    checkCodeBuffer.writeUInt16BE(sum);
    var checkCodeBytes = new Uint8Array(checkCodeBuffer);
    combineBytes(bytes, checkCodeBytes); // 校验码

    //console.log("---------Before escapeSendData---------");
    //console.log(bytes);
    bytes = escapeSendData(bytes);
    //console.log(bytes);
    //console.log("---------escapeSendData Complete---------");

    bytes.unshift(0xfb); // 协议头
    bytes.push(0xfe); // 协议尾
    // console.log("********Send DataPackage********");
    // console.log(bytes);
    this.socket.write(Buffer.from(bytes));
  } catch (e) {
    this.logger.e(
      'KtSocketClient-sendData Exception:' + e.message + 'stack:' + e.stack
    );
  }
};
/**
 * 关闭Socket 连接。
 */
KtSocketClient.prototype.close = function (isExitAutoConnect) {
  try {
    this.isOpen = false;
    if (this.heartBeatTimer) {
      clearInterval(this.heartBeatTimer);
      this.heartBeatTimer = null;
    }
    if (
      this.autoConnectTimer &&
      (isExitAutoConnect + '').toLowerCase() != 'false'
    ) {
      clearInterval(this.autoConnectTimer);
      this.autoConnectTimer = null;
      delete autoConnectTimersCache[this.keyStr];
      delete socketInstanceCache[this.keyStr];
    }
    if (this.socket && !this.socket.destroyed) {
      this.socket.destroy();
      this.socket = null;
    }
  } catch (e) {
    this.logger.e('KtSocketClient->close Exception:' + e.message);
  }
};
/**
 * 接收到数据包时调用。
 */
KtSocketClient.prototype.onReceiveData = null;
/**
 * 连接断开时触发。
 */
KtSocketClient.prototype.onClose = null;
/**
 * Socket 通讯异常时触发。
 */
KtSocketClient.prototype.onError = null;
/**
 * 注册Socket事件
 */
KtSocketClient.prototype.on = function (eventName, callback) {
  if (!isFunction(callback)) {
    return;
  }
  switch (eventName.toString().toLowerCase()) {
    case 'data':
      this.onReceiveData = callback;
      break;
    case 'close':
      this.onClose = callback;
      break;
    case 'error':
      this.onError = callback;
    default:
      this.socket.on(eventName, callback);
      break;
  }
};
/**
 * 本地IP地址
 */
KtSocketClient.prototype.getLocalIP = function () {
  return this.socket.localAddress;
};
/**
 * 本地端口
 */
KtSocketClient.prototype.getLocalPort = function () {
  return this.socket.localPort;
};
module.exports = KtSocketClient;
