"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var rxjs_1 = require("rxjs");
var operators_1 = require("rxjs/operators");
var core_1 = require("../../core");
var logger_1 = require("../../logger");
var default_command_encoder_1 = require("./converter/default-command-encoder");
var response_decoder_1 = require("./converter/response-decoder");
var interceptors_1 = require("./interceptors");
var client_errors_1 = require("./client-errors");
var logger = logger_1.default('Client');
var DefaultIoTizeClient = /** @class */ (function () {
    function DefaultIoTizeClient(requestEncoder, responseDecoder) {
        var _this = this;
        this._onProtocolChange = new rxjs_1.Subject();
        this._commandEncoder = requestEncoder;
        this._responseDecoder = responseDecoder;
        this.protocols = {};
        this._interceptorChain = new interceptors_1.InterceptorChain(function (context) {
            try {
                var command = context.command;
                var bodyDecoder = context.bodyDecoder;
                return _this._command(command, bodyDecoder);
            }
            catch (err) {
                return rxjs_1.throwError(err);
            }
        });
    }
    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;
    };
    Object.defineProperty(DefaultIoTizeClient.prototype, "interceptorChain", {
        // public isEncryptionEnabled(): boolean {
        //     return this._options.encryption;
        // }
        // public get cryptedFrameConverter() {
        //     return this._cryptedFrameConverter;
        // }
        get: function () {
            return this._interceptorChain;
        },
        enumerable: true,
        configurable: true
    });
    DefaultIoTizeClient.prototype.addInterceptor = function (interceptor) {
        this.interceptorChain.addInterceptor(interceptor);
        return this;
    };
    // /**
    //  * 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
    //  */
    // public enableEncryption(value: boolean, algo?: EncryptionAlgo): Promise<any> {
    //     if (value) {
    //         algo = algo || this._encryptionAlgo;
    //         if (!algo) {
    //             return Promise.reject(
    //                 TapClientError.illegalArgument(
    //                     'Illegal argument, encryption algo must be set'
    //                 )
    //             );
    //         }
    //         return this._startEncryption(algo);
    //     }
    //     else {
    //         return this._stopEncryption();
    //     }
    // }
    DefaultIoTizeClient.prototype.isConnected = function () {
        return this.hasProtocol(this.currentProtocolId) && this.getCurrentProtocol().isConnected();
    };
    Object.defineProperty(DefaultIoTizeClient.prototype, "protocol", {
        /**
         * see {@link getCurrentProtocol}
         */
        get: function () {
            return this.getCurrentProtocol();
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(DefaultIoTizeClient.prototype, "commandEncoder", {
        get: function () {
            return this._commandEncoder;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(DefaultIoTizeClient.prototype, "responseDecoder", {
        get: function () {
            return this._responseDecoder;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Get current protocol or throw an error if no protocol
     */
    DefaultIoTizeClient.prototype.getCurrentProtocol = function () {
        if (!this.currentProtocolId) {
            throw client_errors_1.TapClientError.illegalStateError("No protocol");
        }
        if (!this.hasProtocol(this.currentProtocolId)) {
            throw client_errors_1.TapClientError.illegalArgument("Protocol " + this.currentProtocolId + " does not exist");
        }
        return this.protocols[this.currentProtocolId];
    };
    /**
     * Register/replace a communication protocol with given id
     * If no communication protocol is selected yet, it will select it.
     * @param newProtocol
     * @param id
     */
    DefaultIoTizeClient.prototype.addComProtocol = function (newProtocol, id) {
        if (id === void 0) { id = 'default'; }
        this.protocols[id] = newProtocol;
        if (!this.currentProtocolId) {
            this.currentProtocolId = id;
        }
        return this;
    };
    /**
     * Switch to given communication protocol identifier
     * @throws if protocol identifier does not exist
     * @param name
     */
    DefaultIoTizeClient.prototype.switchProtocol = function (name) {
        if (!this.hasProtocol(name)) {
            throw client_errors_1.TapClientError.illegalArgument("Unkonwn protocol: " + name);
        }
        var oldProtocol = this.getCurrentProtocol();
        this.currentProtocolId = name;
        logger.debug("Changing protocol to: " + name);
        this._onProtocolChange.next({
            newProtocol: this.getCurrentProtocol(),
            oldProtocol: oldProtocol
        });
        return this;
    };
    /**
     * Change communication protocol
     * @throws if protocol identifier does not exist
     * @param protocol can either be the communication protocol instance or a string identifier
     */
    DefaultIoTizeClient.prototype.useComProtocol = function (protocol) {
        var protocolId;
        if (typeof protocol == "string") {
            protocolId = protocol;
        }
        else {
            protocolId = protocol.constructor.name;
            this.addComProtocol(protocol, protocolId);
        }
        this.switchProtocol(protocolId);
        return this;
    };
    /**
     * Return true if protocol identifier is registered
     * @param id
     */
    DefaultIoTizeClient.prototype.hasProtocol = function (id) {
        return id in this.protocols;
    };
    DefaultIoTizeClient.prototype.connect = function () {
        try {
            return this.getCurrentProtocol().connect();
        }
        catch (err) {
            return rxjs_1.throwError(err);
        }
    };
    DefaultIoTizeClient.prototype.disconnect = function () {
        try {
            return this.getCurrentProtocol().disconnect();
        }
        catch (err) {
            return rxjs_1.throwError(err);
        }
    };
    /**
     * Send command
     * @param command
     * @param bodyDecoder
     */
    DefaultIoTizeClient.prototype.command = function (command, bodyDecoder) {
        return this._interceptorChain.execute({
            bodyDecoder: bodyDecoder,
            command: command,
            client: this
        });
    };
    /**
     * Send raw request
     * No interceptors/No encryption will be triggered
     * @param dataIn request bytes
     * @param bodyDecoder optional body decoder to use
     */
    DefaultIoTizeClient.prototype.send = function (dataIn, bodyDecoder) {
        var _this = this;
        try {
            return this.getCurrentProtocol()
                .send(dataIn)
                .pipe(operators_1.map(function (result) {
                logger.debug("Received result: ", core_1.FormatHelper.toHexString(result), "DECODER: ", (bodyDecoder ? bodyDecoder.constructor.name : 'NO'));
                try {
                    var response = _this._responseDecoder.decode(result);
                    if (bodyDecoder) {
                        // logger.debug(`SETTING BODY DECODER: `, bodyDecoder);
                        response.setBodyDecoder(bodyDecoder);
                    }
                    return response;
                }
                catch (err) {
                    throw client_errors_1.TapClientError.decodeResponseError(err, dataIn);
                }
            }));
        }
        catch (err) {
            return rxjs_1.throwError(err);
        }
    };
    /**
     * Listen to connection state change event
     * We have to resubscribe when protocol change..
     */
    DefaultIoTizeClient.prototype.onConnectionStateChange = function () {
        try {
            return this.getCurrentProtocol().onConnectionStateChange();
        }
        catch (err) {
            return rxjs_1.throwError(err);
        }
    };
    DefaultIoTizeClient.prototype.onProtocolChange = function () {
        try {
            return this._onProtocolChange.asObservable();
        }
        catch (err) {
            return rxjs_1.throwError(err);
        }
    };
    // protected _encryptedCommand<T>(command: Command, bodyDecoder?: BodyDecoder<T>): Observable<Response<T>> {
    //     if (!this._cryptedFrameConverter) {
    //         return throwError(
    //             TapClientError.illegalStateError(`Crypted convert has not been specified yet`)
    //         );
    //     }
    //     if (!this._encryptionAlgo) {
    //         return throwError(
    //             TapClientError.illegalStateError(`Cryto algo has not been specified yet`)
    //         );
    //     }
    //     let iotizeFrame = this._lwm2mCommandConverter.encode(command);
    //     let cryptedFrameWarpper = this._cryptedFrameConverter.encode(iotizeFrame);
    //     let cryptedFrame = this._encryptionAlgo.encode(cryptedFrameWarpper);
    //     return this
    //         ._command<Uint8Array>(ApiRequest.GET("/1024//48", cryptedFrame)) // TODO constants
    //         .pipe(
    //             map<Response<Uint8Array>, Response<T>>((crypterWarpperResponse: Response<Uint8Array>) => {
    //                 logger.debug(`Iotize client crypted frame: ${crypterWarpperResponse}`);
    //                 crypterWarpperResponse.successful();
    //                 if (!this._encryptionAlgo) {
    //                     throw TapClientError.illegalStateError(`Encryption algo has not been set yet`)
    //                 }
    //                 let cryptedFrameContent = this._encryptionAlgo.decode(crypterWarpperResponse.rawBody());
    //                 logger.debug(`Decrypted frame value: ${FormatHelper.toHexString(cryptedFrameContent)}`);
    //                 let apduFrame = this._cryptedFrameConverter.decode(cryptedFrameContent);
    //                 let lwm2mResponseFrame = this._apduResponseConverter.decode(apduFrame);
    //                 // TODO do we need to check apdu status ? 
    //                 let response: Response<any> = this.lwm2mResponseConverter.decode(lwm2mResponseFrame.data);
    //                 if (bodyDecoder) {
    //                     response.setBodyDecoder(bodyDecoder);
    //                 }
    //                 logger.debug(`Iotize client decoded response: ${response}`)
    //                 return response as any as Response<T>;
    //             })
    //         );
    // }
    DefaultIoTizeClient.prototype._command = function (command, bodyDecoder) {
        var _this = this;
        try {
            if (!this.isConnected()) {
                return rxjs_1.throwError(client_errors_1.TapClientError.notConnectedError());
            }
            var dataIn = void 0;
            try {
                dataIn = this._commandEncoder.encode(command);
            }
            catch (e) {
                return rxjs_1.throwError(client_errors_1.TapClientError.encodeRequestError(command, e));
            }
            logger.debug("Sending " + dataIn.length + " bytes 0x" + core_1.FormatHelper.toHexString(dataIn));
            return this.getCurrentProtocol()
                .send(dataIn)
                .pipe(operators_1.map(function (result) {
                logger.debug("Received result: ", core_1.FormatHelper.toHexString(result), "DECODER: ", (bodyDecoder ? bodyDecoder.constructor.name : 'NO'));
                try {
                    var response = _this._responseDecoder.decode(result);
                    if (bodyDecoder) {
                        response.setBodyDecoder(bodyDecoder);
                    }
                    return response;
                }
                catch (err) {
                    throw client_errors_1.TapClientError.decodeResponseError(err, command);
                }
            }), operators_1.map(function (response) {
                logger.debug("Command \"" + command.toString() + "\" => Response: " + response.toString());
                response.setRequest(command);
                return response;
            }));
        }
        catch (err) {
            return rxjs_1.throwError(err);
        }
    };
    return DefaultIoTizeClient;
}());
exports.DefaultIoTizeClient = DefaultIoTizeClient;
