"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var lwm2m_response_converter_1 = require("./converter/lwm2m-response-converter");
var crypted_frame_converter_1 = require("./converter/crypted-frame-converter");
var core_1 = require("../../core");
var rxjs_1 = require("rxjs");
var operators_1 = require("rxjs/operators");
var default_command_encoder_1 = require("./converter/default-command-encoder");
var response_decoder_1 = require("./converter/response-decoder");
var response_1 = require("../../core/apdu/response");
var lwm2m_command_converter_1 = require("./converter/lwm2m-command-converter");
var request_1 = require("./request");
var DefaultIoTizeClient = /** @class */ (function () {
    function DefaultIoTizeClient(requestEncoder, responseDecoder) {
        var _this = this;
        this.lwm2mResponseConverter = new lwm2m_response_converter_1.IotizeLWM2MResponseConverter();
        this._lwm2mCommandConverter = new lwm2m_command_converter_1.IotizeLWM2MCommandConverter();
        this._apduResponseConverter = new response_1.APDUResponseConverter();
        this._cryptedFrameConverter = new crypted_frame_converter_1.CryptedFrameConverter({
            next: function () {
                return _this.cmdCounter++;
            }
        });
        this.commandConverter = requestEncoder;
        this.responseDecoder = responseDecoder;
        this.protocols = {};
        this._options = {
            encryption: false
        };
        this.cmdCounter = 0;
        // this._encryptionAlgo = encryptionAlgo;
    }
    Object.defineProperty(DefaultIoTizeClient.prototype, "cryptedFrameConverter", {
        get: function () {
            return this._cryptedFrameConverter;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Enable/Disable encrytion when communicating with a device
     * @param value true if requests must be encrypted
     * @param algo optional if you have already provided the algo with @{link setEncryptionAlgo(algo)} method
     */
    DefaultIoTizeClient.prototype.enableEncryption = function (value, algo) {
        if (value) {
            algo = algo || this._encryptionAlgo;
            if (!algo) {
                return Promise.reject(new Error('Illegal argument, encryption algo must be set'));
            }
            return this._startEncryption(algo);
        }
        else {
            return this._stopEncryption();
        }
    };
    DefaultIoTizeClient.create = function (protocol) {
        var client = new DefaultIoTizeClient(new default_command_encoder_1.DefaultCommandEncoder(), new response_decoder_1.DefaultResponseDecoder());
        if (protocol) {
            client.addComProtocol(protocol);
        }
        return client;
    };
    DefaultIoTizeClient.prototype.getEncryptionAlgo = function () {
        return this._encryptionAlgo;
    };
    DefaultIoTizeClient.prototype.setEncryptionAlgo = function (algo) {
        this._encryptionAlgo = algo;
    };
    DefaultIoTizeClient.prototype.isConnected = function () {
        return this.hasProtocol(this.currentProtocolId) && this.getCurrentProtocol().isConnected();
    };
    DefaultIoTizeClient.prototype.getCurrentProtocol = function () {
        if (!this.currentProtocolId) {
            throw new Error("Not protocol");
        }
        if (!this.hasProtocol(this.currentProtocolId)) {
            throw new Error("Protocol " + this.currentProtocolId + " does not exist");
        }
        return this.protocols[this.currentProtocolId];
    };
    DefaultIoTizeClient.prototype.addComProtocol = function (newProtocol, id) {
        if (id === void 0) { id = 'default'; }
        if (!this.currentProtocolId) {
            this.currentProtocolId = id;
        }
        this.protocols[this.currentProtocolId] = newProtocol;
        return this;
    };
    DefaultIoTizeClient.prototype.switchProtocol = function (name) {
        if (!this.hasProtocol(name)) {
            throw new Error("Unkonwn protocol: " + name);
        }
        this.currentProtocolId = name;
        return this;
    };
    DefaultIoTizeClient.prototype.hasProtocol = function (id) {
        return id in this.protocols;
    };
    DefaultIoTizeClient.prototype.connect = function () {
        return this.getCurrentProtocol().connect();
    };
    DefaultIoTizeClient.prototype.disconnect = function () {
        return this.getCurrentProtocol().disconnect();
    };
    DefaultIoTizeClient.prototype._encryptedCommand = function (command, bodyDecoder) {
        var _this = this;
        if (!this._cryptedFrameConverter) {
            return rxjs_1.throwError(new Error("Crypted convert has not been specified yet"));
        }
        if (!this._encryptionAlgo) {
            return rxjs_1.throwError(new Error("Cryto algo has not been specified yet"));
        }
        var iotizeFrame = this._lwm2mCommandConverter.encode(command);
        var cryptedFrameWarpper = this._cryptedFrameConverter.encode(iotizeFrame);
        var cryptedFrame = this._encryptionAlgo.encode(cryptedFrameWarpper);
        return this
            ._command(request_1.ApiRequest.GET("/1024//48", cryptedFrame)) // TODO constants
            .pipe(operators_1.map(function (crypterWarpperResponse) {
            console.debug("Iotize client crypted frame: " + crypterWarpperResponse);
            crypterWarpperResponse.successful();
            if (!_this._encryptionAlgo) {
                throw new Error("Encryption algo has not been set yet");
            }
            var cryptedFrameContent = _this._encryptionAlgo.decode(crypterWarpperResponse.rawBody());
            console.debug("Decrypted frame value: " + core_1.FormatHelper.toHexString(cryptedFrameContent));
            var apduFrame = _this._cryptedFrameConverter.decode(cryptedFrameContent);
            var lwm2mResponseFrame = _this._apduResponseConverter.decode(apduFrame);
            // TODO do we need to check apdu status ? 
            var response = _this.lwm2mResponseConverter.decode(lwm2mResponseFrame.data);
            if (bodyDecoder) {
                response.setBodyDecoder(bodyDecoder);
            }
            console.debug("Iotize client decoded response: " + response);
            return response;
        }));
    };
    DefaultIoTizeClient.prototype._startEncryption = function (encryptionAlog) {
        console.debug("_startEncryption");
        this._options.encryption = true;
        this.cmdCounter = 0;
        this._encryptionAlgo = encryptionAlog;
        this._options.encryption = true;
        return Promise.resolve();
    };
    DefaultIoTizeClient.prototype._stopEncryption = function () {
        this._options.encryption = false;
        return Promise.resolve();
    };
    DefaultIoTizeClient.prototype.command = function (command, bodyDecoder) {
        try {
            console.debug("Client: exec " + command + " (encryption:" + this._options.encryption + ")");
            if (this._options.encryption) {
                return this._encryptedCommand(command, bodyDecoder);
            }
            else {
                return this._command(command, bodyDecoder);
            }
        }
        catch (err) {
            console.warn('Unexpected error in command', command, err);
            return rxjs_1.throwError(err);
        }
    };
    DefaultIoTizeClient.prototype._command = function (command, bodyDecoder) {
        var _this = this;
        if (!this.isConnected()) {
            return rxjs_1.throwError(new Error("Trying to execute commant but device is not connected"));
        }
        var dataIn;
        try {
            dataIn = this.commandConverter.encode(command);
        }
        catch (e) {
            return rxjs_1.throwError(new Error("Cannot encode this request: " + command + ". Error: " + e));
        }
        console.debug(DefaultIoTizeClient.TAG, "Sending " + dataIn.length + " bytes 0x" + core_1.FormatHelper.toHexString(dataIn));
        var observable = this.getCurrentProtocol()
            .send(dataIn)
            .pipe(operators_1.map(function (result) {
            console.debug(DefaultIoTizeClient.TAG, "Received result: ", core_1.FormatHelper.toHexString(result), "DECODER: ", bodyDecoder);
            var response = _this.responseDecoder.decode(result);
            if (bodyDecoder) {
                // console.debug(`SETTING BODY DECODER: `, bodyDecoder);
                response.setBodyDecoder(bodyDecoder);
            }
            console.debug(DefaultIoTizeClient.TAG, "Command \"" + command.toString() + "\" => Response: " + response.toString());
            return response;
        }));
        // observable.pipe(first()).subscribe((result: Uint8Array) => {
        //     let response: Response<any> = this.responseDecoder.decode(result);
        //     console.debug(DefaultIoTizeClient.TAG, `CommandBIS "${command.toString()}" => Response: ${response.toString()}`);
        // });
        return observable;
    };
    DefaultIoTizeClient.prototype.onConnectionStateChange = function () {
        return this.getCurrentProtocol().onConnectionStateChange();
    };
    DefaultIoTizeClient.TAG = "DeviceClient";
    return DefaultIoTizeClient;
}());
exports.DefaultIoTizeClient = DefaultIoTizeClient;
