import { bufferToHexString } from '@iotize/common/byte-converter';
import { Subject, throwError } from 'rxjs';
import { map } from 'rxjs/operators';
import { TAP_REQUEST_FRAME_CONVERTER, TAP_RESPONSE_FRAME_CONVERTER } from './converters';
import { debug } from './debug';
import { InterceptorChain } from './interceptors/interceptor-chain';
import { TapClientError } from './tap-client-error';
var TAG = 'Client';
var TapClient = /** @class */ (function () {
    function TapClient(requestEncoder, responseDecoder) {
        var _this = this;
        this._onProtocolChange = new Subject();
        this._commandEncoder = requestEncoder;
        this._responseDecoder = responseDecoder;
        this.protocols = {};
        this._interceptorChain = new InterceptorChain(function (context) {
            try {
                var command = context.request;
                // const bodyDecoder = context.bodyDecoder;
                return _this._command(command);
            }
            catch (err) {
                return throwError(err);
            }
        });
    }
    TapClient.create = function (protocol) {
        var client = new TapClient(TAP_REQUEST_FRAME_CONVERTER, TAP_RESPONSE_FRAME_CONVERTER);
        if (protocol) {
            client.addComProtocol(protocol);
        }
        return client;
    };
    Object.defineProperty(TapClient.prototype, "interceptorChain", {
        // public isEncryptionEnabled(): boolean {
        //     return this._options.encryption;
        // }
        // public get cryptedFrameConverter() {
        //     return this._cryptedFrameConverter;
        // }
        get: function () {
            return this._interceptorChain;
        },
        enumerable: true,
        configurable: true
    });
    TapClient.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();
    //     }
    // }
    TapClient.prototype.isConnected = function () {
        return (this.hasProtocol(this.currentProtocolId) &&
            this.getCurrentProtocol().isConnected());
    };
    Object.defineProperty(TapClient.prototype, "protocol", {
        /**
         * see {@link getCurrentProtocol}
         */
        get: function () {
            return this.getCurrentProtocol();
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TapClient.prototype, "commandEncoder", {
        get: function () {
            return this._commandEncoder;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(TapClient.prototype, "responseDecoder", {
        get: function () {
            return this._responseDecoder;
        },
        enumerable: true,
        configurable: true
    });
    /**
     * Get current protocol or throw an error if no protocol
     */
    TapClient.prototype.getCurrentProtocol = function () {
        if (!this.currentProtocolId) {
            throw TapClientError.illegalStateError('Tap client protocol is required');
        }
        if (!this.hasProtocol(this.currentProtocolId)) {
            throw 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
     */
    TapClient.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
     */
    TapClient.prototype.switchProtocol = function (name) {
        if (!this.hasProtocol(name)) {
            throw TapClientError.illegalArgument('Unkonwn protocol: ' + name);
        }
        var oldProtocol = this.getCurrentProtocol();
        this.currentProtocolId = name;
        debug(TAG, "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
     */
    TapClient.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
     */
    TapClient.prototype.hasProtocol = function (id) {
        return id in this.protocols;
    };
    TapClient.prototype.connect = function () {
        try {
            return this.getCurrentProtocol().connect();
        }
        catch (err) {
            return throwError(err);
        }
    };
    TapClient.prototype.disconnect = function () {
        try {
            return this.getCurrentProtocol().disconnect();
        }
        catch (err) {
            return throwError(err);
        }
    };
    /**
     * Send request
     * @param request
     * @param bodyDecoder
     */
    TapClient.prototype.request = function (request) {
        return this._interceptorChain.execute({
            // bodyDecoder,
            request: request,
            client: this
        });
    };
    /**
     * Send raw request
     * No interceptors/No encryption will be triggered
     * @param dataIn request bytes
     * @param bodyDecoder optional body decoder to use
     */
    TapClient.prototype.send = function (dataIn) {
        var _this = this;
        try {
            return this.getCurrentProtocol()
                .send(dataIn)
                .pipe(map(function (result) {
                // debug(
                //     TAG,
                //     'Received result: ',
                //     bufferToHexString(result),
                //     'DECODER: ',
                //     bodyDecoder ? bodyDecoder.constructor.name : 'NO'
                // );
                try {
                    return _this._responseDecoder.decode(result);
                }
                catch (err) {
                    throw TapClientError.decodeResponseError(err, dataIn);
                }
            }));
        }
        catch (err) {
            return throwError(err);
        }
    };
    /**
     * Listen to connection state change event
     * We have to resubscribe when protocol change..
     */
    TapClient.prototype.onConnectionStateChange = function () {
        try {
            return this.getCurrentProtocol().onConnectionStateChange();
        }
        catch (err) {
            return throwError(err);
        }
    };
    TapClient.prototype.onProtocolChange = function () {
        try {
            return this._onProtocolChange.asObservable();
        }
        catch (err) {
            return throwError(err);
        }
    };
    TapClient.prototype._command = function (command
    // bodyDecoder?: BodyDecoder<T>
    ) {
        var _this = this;
        try {
            if (!this.isConnected()) {
                return throwError(TapClientError.notConnectedError());
            }
            var dataIn = void 0;
            try {
                dataIn = this._commandEncoder.encode(command);
            }
            catch (e) {
                return throwError(TapClientError.encodeRequestError(command, e));
            }
            debug(TAG, "Sending " + dataIn.length + " bytes 0x" + bufferToHexString(dataIn));
            return this.getCurrentProtocol()
                .send(dataIn)
                .pipe(map(function (result) {
                // debug(
                //     TAG,
                //     'Received result: ',
                //     bufferToHexString(result),
                //     'DECODER: ',
                //     bodyDecoder ? bodyDecoder.constructor.name : 'NO'
                // );
                try {
                    var response = _this._responseDecoder.decode(result);
                    // if (bodyDecoder) {
                    //     response.setBodyDecoder(bodyDecoder);
                    // }
                    return response;
                }
                catch (err) {
                    throw TapClientError.decodeResponseError(err, command);
                }
            }));
        }
        catch (err) {
            return throwError(err);
        }
    };
    return TapClient;
}());
export { TapClient };
//# sourceMappingURL=data:application/json;base64, 