import { ConnectionState, ComProtocol } from '@iotize/tap/protocol/api';
import { Subject, throwError, from, of } from 'rxjs';
import { createDebugger } from '@iotize/common/debug';
import { promiseTimeout } from '@iotize/common/promise';
import { timeoutObservable } from '@iotize/common/rxjs';
import { TaskQueue } from '@iotize/common/task-manager';

const prefix = '@iotize/tap/protocol/common';
const debug = createDebugger(prefix);

const TAG$1 = 'ComProtocol';
class AbstractComProtocol {
    constructor() {
        this.connectionState = ConnectionState.DISCONNECTED;
        this._options = {
            connect: {
                timeout: 1000,
            },
            disconnect: {
                timeout: 1000,
            },
            send: {
                timeout: 500,
            },
        };
    }
    get options() {
        return this._options;
    }
    set options(options) {
        this._options = options;
    }
    getConnectionState() {
        return this.connectionState;
    }
    isConnected() {
        return this.connectionState == ConnectionState.CONNECTED;
    }
    setConnectionState(connectionState) {
        debug(TAG$1, 'setConnectionState', ConnectionState[connectionState], 'Old: ', ConnectionState[this.connectionState]);
        let event = {
            newState: connectionState,
            oldState: this.connectionState,
        };
        this.connectionState = connectionState;
        if (this._connectionStateChange) {
            this._connectionStateChange.next(event);
        }
        return this;
    }
    /**
     * Must be implemented in childs
     */
    receiveStream() {
        throw new Error('Method not implemented.');
    }
    onConnectionStateChange() {
        if (!this._connectionStateChange) {
            this._connectionStateChange = new Subject();
        }
        return this._connectionStateChange;
    }
}

const TAG = 'ComProtocol';
/**
 *
 */
class QueueComProtocol extends AbstractComProtocol {
    constructor() {
        super();
        this._jobQueue = new TaskQueue();
    }
    _send(data, options) {
        try {
            if (!this.isConnected()) {
                let error = ComProtocol.Errors.notConnected({
                    protocol: this,
                });
                return throwError(error);
            }
            let startTime = new Date();
            return from(promiseTimeout(options.timeout, this.write(data), (info) => {
                return ComProtocol.Errors.timeoutError({
                    msg: `Communication timeout. Send request to device device timeout (${info.timeout}ms).`,
                    protocol: this,
                    timeout: info.timeout,
                    startTime: info.startTime,
                });
            }).then(() => {
                let elapsedTime = new Date().getTime() - startTime.getTime();
                let newTimeout = options.timeout - elapsedTime;
                return promiseTimeout(newTimeout, this.read(), (info) => {
                    return ComProtocol.Errors.timeoutError({
                        msg: `Communication timeout. Read response from device timeout (${newTimeout}ms).`,
                        protocol: this,
                        timeout: newTimeout,
                        startTime: info.startTime,
                    });
                });
            }));
        }
        catch (error) {
            return throwError(error);
        }
    }
    /**
     * Cancel pending requests
     */
    cancel() {
        debug(TAG, `Canceling operations`);
        this._jobQueue.cancelAll(ComProtocol.Errors.operationCanceled());
    }
    /**
     *
     * @param data
     * @param options
     */
    send(data, options) {
        // debug(TAG, `QueueComProtocol`, `::send() ENQUEUE request (${data.length} bytes) ${toHexString(data)}. Queue position: ${this._queue.size}`);
        if (true) {
            // Copy data ? TODO can be removed ?
            data = data.slice(0);
        }
        return this._jobQueue.addExecutor(() => {
            let timeout = options ? options.timeout : this._options.send.timeout;
            return this._send(data, {
                timeout: timeout,
            });
        });
    }
    /**
     * Connect with timeout
     *
     * If connect has already been called or is in progress / no further action
     *
     * @param options
     */
    connect(options) {
        if (this.connectionState === ConnectionState.CONNECTED) {
            debug(TAG, `QueueComProtocol is already connected`);
            return of(null);
        }
        if (this.connectionState == ConnectionState.CONNECTING) {
            return this._connect$;
        }
        let connectTimeout = options
            ? options.timeout
            : this._options.connect.timeout;
        let startTime = new Date();
        this._connect$ = timeoutObservable(this._connect(options), connectTimeout, (err) => {
            return ComProtocol.Errors.timeoutError({
                msg: 'Protocol connection timeout',
                protocol: this,
                timeout: connectTimeout,
                startTime: startTime,
            });
        });
        this.setConnectionState(ConnectionState.CONNECTING);
        this._connectionSubscription = this._connect$.subscribe(() => {
            this.setConnectionState(ConnectionState.CONNECTED);
        }, (error) => {
            this.setConnectionState(ConnectionState.DISCONNECTED);
        });
        return this._connect$;
    }
    disconnect(options) {
        if (this.connectionState === ConnectionState.DISCONNECTED) {
            return of(null);
        }
        if (this.connectionState == ConnectionState.DISCONNECTING) {
            return this._disconnect$;
        }
        this._connect$ = undefined;
        let disconnectTimeout = options
            ? options.timeout
            : this._options.disconnect.timeout;
        let startTime = new Date();
        this._disconnect$ = timeoutObservable(this._disconnect(options), disconnectTimeout, (err) => {
            return ComProtocol.Errors.timeoutError({
                msg: 'Protocol disconnection timeout',
                protocol: this,
                timeout: disconnectTimeout,
                startTime: startTime,
            });
        });
        this.setConnectionState(ConnectionState.DISCONNECTING);
        this._disconnect$.subscribe(() => {
            this.setConnectionState(ConnectionState.DISCONNECTED);
        }, (error) => {
            this.setConnectionState(ConnectionState.DISCONNECTED);
        });
        return this._disconnect$;
    }
}

/**
 * Generated bundle index. Do not edit.
 */

export { AbstractComProtocol, QueueComProtocol };
//# sourceMappingURL=iotize-tap-protocol-core.js.map
