"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SocketClient = exports.encode = void 0;
const net = require("net");
const events_1 = require("events");
/**
 * 接收数据转义
 *
 * 0xff 0xbb => 0xfb 0xff 0xfc => 0xff 0xff 0xee => 0xfe
 */
function receive_escape(data) {
    let arr = [];
    for (let i = 0; i < data.length; i++) {
        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;
}
/**
 * 发送数据转义
 *
 * 0xfb => 0xff 0xbb  0xff => 0xff 0xfc  0xfe => 0xff 0xee
 */
function send_escape(data) {
    let arr = [];
    for (let i = 0; i < data.length; i++) {
        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;
}
/**
 * 解码
 */
function decode(datas) {
    let packs = [];
    let unescape = 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 = {
                                ts: Buffer.from(escape.slice(1, 5)).readUInt32BE(),
                                cmd: 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 = [];
    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 };
}
/**
 * 编码
 */
function encode(cmd, data) {
    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 = [];
    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;
}
exports.encode = encode;
/**
 * 视频服务程序SOCKET客户端
 */
class SocketClient {
    get socketInstance() {
        return this._socket;
    }
    constructor(opts) {
        this._socket = null;
        this.opts = null;
        this._datas = null;
        this._disposed = null;
        this._maxBufferSize = 99;
        this.opts = Object.assign({}, opts);
        this._emitter = new events_1.EventEmitter();
        this._connect();
    }
    on(event, listener) {
        if (this._disposed)
            return;
        this._emitter.on(event, listener);
        return this;
    }
    off(eventName, listener) {
        if (this._disposed)
            return;
        this._emitter.off(eventName, listener);
        return this;
    }
    /**
     * @param cmd 'A' | 'C' | 'D' | 'F' | 'n' | 'Z' | 'P' | 'V' 等
     * @param data
     */
    send(cmd, data) {
        this._send(cmd, data);
    }
    destroy() {
        this._dispose();
    }
    _connect() {
        if (this._disposed)
            return;
        if (this._socket) {
            this._socket.removeAllListeners();
            this._socket.destroy();
            this._socket = null;
        }
        this._datas = null;
        let socket = net.connect(this.opts);
        socket.on("connect", () => {
            this._onConnect();
            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", (hadError) => __awaiter(this, void 0, void 0, function* () {
            this._onClose();
            this._emitter.emit("close", hadError);
        }));
        this._socket = socket;
    }
    _onConnect() {
        clearInterval(this._heartbeatTimer);
        this._heartbeatTimer = setInterval(() => {
            this.send("F", Buffer.alloc(0));
        }, 1000 * 5);
    }
    _onData(data) {
        if (!data || data.length === 0)
            return;
        if (data[0] === 0xfb) {
            this._datas = [data];
        }
        else {
            if (this._datas) {
                if (this._datas.length > this._maxBufferSize) {
                    this._datas.shift();
                }
                this._datas.push(data);
            }
            else {
                this._datas = [data];
            }
        }
        if (!data.includes(0xfe))
            return;
        try {
            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'));
                        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);
        }
    }
    _onClose() {
        return __awaiter(this, void 0, void 0, function* () {
            clearInterval(this._heartbeatTimer);
            this._datas = null;
            if (!this._disposed) {
                yield new Promise((resolve) => { setTimeout(resolve, this.opts.reconnectDelay || 1000 * 10); });
                this._connect();
            }
        });
    }
    _send(cmd, data) {
        if (!this._socket || !this._socket.writable || this._socket.destroyed)
            return;
        let _data = Buffer.isBuffer(data) ? data : Buffer.from(JSON.stringify(data));
        let bufs = encode(cmd, _data);
        for (const buf of bufs) {
            this._socket.write(buf);
        }
    }
    _dispose() {
        if (this._disposed)
            return;
        this._disposed = true;
        if (this._socket) {
            if (!this._socket.destroyed) {
                this._emitter.emit("close", false);
            }
            this._socket.removeAllListeners();
            this._socket.destroy();
            this._socket = null;
        }
        this._emitter.removeAllListeners();
        this._emitter = null;
        clearInterval(this._heartbeatTimer);
        this._datas = null;
    }
}
exports.SocketClient = SocketClient;
