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';
import { TapRequestHelper } from './request/tap-request-helper';
const TAG = 'Client';
export class TapClient {
    constructor(requestEncoder, responseDecoder) {
        this._onProtocolChange = new Subject();
        this._commandEncoder = requestEncoder;
        this._responseDecoder = responseDecoder;
        this.protocols = {};
        this._interceptorChain = new InterceptorChain((context) => {
            try {
                const command = context.request;
                // const bodyDecoder = context.bodyDecoder;
                return this._command(command);
            }
            catch (err) {
                return throwError(err);
            }
        });
    }
    static create(protocol) {
        const client = new TapClient(TAP_REQUEST_FRAME_CONVERTER, TAP_RESPONSE_FRAME_CONVERTER);
        if (protocol) {
            client.addComProtocol(protocol);
        }
        return client;
    }
    // public isEncryptionEnabled(): boolean {
    //     return this._options.encryption;
    // }
    // public get cryptedFrameConverter() {
    //     return this._cryptedFrameConverter;
    // }
    get interceptorChain() {
        return this._interceptorChain;
    }
    addInterceptor(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();
    //     }
    // }
    isConnected() {
        return (this.hasProtocol(this.currentProtocolId) &&
            this.getCurrentProtocol().isConnected());
    }
    /**
     * see {@link getCurrentProtocol}
     */
    get protocol() {
        return this.getCurrentProtocol();
    }
    get commandEncoder() {
        return this._commandEncoder;
    }
    get responseDecoder() {
        return this._responseDecoder;
    }
    /**
     * Get current protocol or throw an error if no protocol
     */
    getCurrentProtocol() {
        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
     */
    addComProtocol(newProtocol, 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
     */
    switchProtocol(name) {
        if (!this.hasProtocol(name)) {
            throw TapClientError.illegalArgument('Unkonwn protocol: ' + name);
        }
        const oldProtocol = this.getCurrentProtocol();
        this.currentProtocolId = name;
        debug(TAG, `Changing protocol to: ${name}`);
        this._onProtocolChange.next({
            newProtocol: this.getCurrentProtocol(),
            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
     */
    useComProtocol(protocol) {
        let 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
     */
    hasProtocol(id) {
        return id in this.protocols;
    }
    connect() {
        try {
            return this.getCurrentProtocol().connect();
        }
        catch (err) {
            return throwError(err);
        }
    }
    disconnect() {
        try {
            return this.getCurrentProtocol().disconnect();
        }
        catch (err) {
            return throwError(err);
        }
    }
    /**
     * Send request
     * @param request
     * @param bodyDecoder
     */
    request(request) {
        return this._interceptorChain.execute({
            // bodyDecoder,
            request,
            client: this,
        });
    }
    /**
     * Send raw request
     * No interceptors/No encryption will be triggered
     * @param dataIn request bytes
     * @param bodyDecoder optional body decoder to use
     */
    send(dataIn) {
        try {
            return this.getCurrentProtocol()
                .send(dataIn)
                .pipe(map((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..
     */
    onConnectionStateChange() {
        try {
            return this.getCurrentProtocol().onConnectionStateChange();
        }
        catch (err) {
            return throwError(err);
        }
    }
    onProtocolChange() {
        try {
            return this._onProtocolChange.asObservable();
        }
        catch (err) {
            return throwError(err);
        }
    }
    _command(command) {
        try {
            if (!this.isConnected()) {
                return throwError(TapClientError.notConnectedError());
            }
            let dataIn;
            try {
                dataIn = this._commandEncoder.encode(command);
            }
            catch (e) {
                return throwError(TapClientError.encodeRequestError(command, e));
            }
            debug(TAG, `Sending ${TapRequestHelper.toString(command)} (${dataIn.length} bytes 0x${bufferToHexString(dataIn)})`);
            return this.getCurrentProtocol()
                .send(dataIn)
                .pipe(map((result) => {
                // debug(
                //     TAG,
                //     'Received result: ',
                //     bufferToHexString(result),
                //     'DECODER: ',
                //     bodyDecoder ? bodyDecoder.constructor.name : 'NO'
                // );
                try {
                    const response = this._responseDecoder.decode(result);
                    // if (bodyDecoder) {
                    //     response.setBodyDecoder(bodyDecoder);
                    // }
                    return response;
                }
                catch (err) {
                    throw TapClientError.decodeResponseError(err, command);
                }
            }));
        }
        catch (err) {
            return throwError(err);
        }
    }
}
//# sourceMappingURL=data:application/json;base64, 