import buffer from 'buffer';
import rxjs from 'rxjs';
import common from '@iotize/common';
import byteConverter from '@iotize/common/byte-converter';

var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};

function commonjsRequire () {
	throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs');
}

function unwrapExports (x) {
	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

function createCommonjsModule(fn, module) {
	return module = { exports: {} }, fn(module, module.exports), module.exports;
}

function getCjsExportFromNamespace (n) {
	return n && n['default'] || n;
}

var debug = createCommonjsModule(function (module, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

const prefix = '@iotize:device-client.js:relay';
exports.debug = common.createDebugger(prefix);

});

var debug$1 = unwrapExports(debug);
var debug_1 = debug.debug;

var relayServer = createCommonjsModule(function (module, exports) {
"use strict";
var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });




const TAG = 'Relay';
class RelayServer {
    constructor(_server, params) {
        this._server = _server;
        this._params = params || {
            port: 2000
        };
        this._clients = [];
    }
    get server() {
        return this._server;
    }
    addClient(socket) {
        this._clients.push(socket);
        return this;
    }
    removeClient(client) {
        let index = this._clients.indexOf(client);
        if (index >= 0) {
            client.close();
            debug.debug(TAG, `Removing client ${client.getClientName()}...`);
            delete this._clients[index];
        }
        return this;
    }
    start() {
        this._subscription = new rxjs.Subject();
        this._server
            .connections()
            .subscribe((socket) => __awaiter(this, void 0, void 0, function* () {
            // Identify this client
            let clientName = socket.getClientName();
            debug.debug(TAG, `[NEW_CLIENT] ${clientName}`);
            yield socket.ready();
            debug.debug(TAG, `[NEW_CLIENT] ${clientName} IS READY`);
            // Put this new client in the list
            this.addClient(socket);
            socket.events().subscribe({
                next: (event) => {
                    switch (event.type) {
                        case 'data':
                            let messageString = this.messageToLog(event.payload);
                            debug.debug(TAG, `[MESSAGE] ${messageString} from ${clientName}`);
                            this.broadcastMessage(event.payload, socket);
                            break;
                        case 'error':
                            debug.debug(TAG, `[ERROR] ${clientName} `, event.payload);
                            // Do we have to remove client every time ?
                            this.removeClient(socket);
                            break;
                        case 'end':
                            debug.debug(TAG, `[CLIENTEND] ${clientName}`);
                            this.removeClient(socket);
                            break;
                    }
                }
            });
        }));
        return this._server.listen(this._params);
    }
    stop() {
        return __awaiter(this, void 0, void 0, function* () {
            this._clients.forEach((client) => {
                client.close();
            });
            if (this._server) {
                yield this._server.stop();
            }
            if (this._subscription) {
                this._subscription.complete();
            }
        });
    }
    broadcastMessage(message, sender) {
        let messageString = this.messageToLog(message);
        debug.debug(TAG, `[BROADCAST] ${messageString} from ${sender.getClientName()}`);
        if (this._clients.length == 0) {
            debug.debug(TAG, 'There is no client yet');
            return;
        }
        // Send a message to all clients
        this._clients.forEach(function (client) {
            // Don't want to send it to sender
            try {
                if (client === sender) {
                    debug.debug(TAG, `\t- Skip sender ${client.getClientName()}`);
                    return;
                }
                if (client.isWritable()) {
                    debug.debug(TAG, `\t- Sent to ${client.getClientName()}`);
                    client.write(message);
                }
                else {
                    debug.debug(TAG, `Client ${client.getClientName()} is not writable. Cannot send to it`);
                }
            }
            catch (err) {
                debug.debug(`Cannot broadcast to ${client.getClientName()}`, err);
            }
        });
    }
    messageToLog(message) {
        if (typeof message === 'string') {
            return `"${message}"`;
        }
        else if (message instanceof buffer.Buffer) {
            return byteConverter.bufferToHexString(message);
        }
        else {
            return message;
        }
    }
}
exports.RelayServer = RelayServer;

});

var relayServer$1 = unwrapExports(relayServer);
var relayServer_1 = relayServer.RelayServer;

var comRelay = createCommonjsModule(function (module, exports) {
"use strict";
var __awaiter = (commonjsGlobal && commonjsGlobal.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });


const TAG = 'Relay';
class ComRelayError extends Error {
    constructor(code, msg) {
        super(msg);
        this.code = code;
    }
    static illegalArgument(message) {
        return new ComRelayError(ComRelayError.Code.IllegalArgmentError, message);
    }
}
exports.ComRelayError = ComRelayError;
(function (ComRelayError) {
    let Code;
    (function (Code) {
        Code["IllegalArgmentError"] = "IllegalArgmentError";
    })(Code = ComRelayError.Code || (ComRelayError.Code = {}));
})(ComRelayError = exports.ComRelayError || (exports.ComRelayError = {}));
/**
 * This is an experimental feature.
 *
 * @experimental
 */
class ComRelay {
    // private IRelayCallback mCallback;
    // public final RelayServiceInfoModel mRelayInfo;
    // private DefaultSocketServer mSocketServer;
    // private WebSocketServer mWebSocketServer;
    /**
     * @param sourceProtocol protocol that will received commands
     * @param targetProtocol protocol on wich commmand will be sent
     */
    constructor(sourceProtocol, targetProtocol) {
        if (sourceProtocol) {
            this.setSourceProtocol(sourceProtocol);
        }
        if (targetProtocol) {
            this.setTargetProtocol(targetProtocol);
        }
    }
    setSourceProtocol(protocol) {
        if (this.mSourceProtocol) {
            this.removeSourceProtocol();
        }
        this.mSourceProtocol = protocol;
        return this;
    }
    removeSourceProtocol() {
        if (this._sourceProtocolSubscription) {
            this._sourceProtocolSubscription.unsubscribe();
            this._sourceProtocolSubscription = undefined;
        }
        this.mSourceProtocol = undefined;
        return this;
    }
    setTargetProtocol(protocol) {
        this.mTargetProtocol = protocol;
        return this;
    }
    start(options) {
        return __awaiter(this, void 0, void 0, function* () {
            return promiseTimout(options ? options.timeout || -1 : -1, (resolve, reject) => __awaiter(this, void 0, void 0, function* () {
                try {
                    if (!this.mSourceProtocol) {
                        throw ComRelayError.illegalArgument('Missing source protocol');
                    }
                    if (!this.mTargetProtocol) {
                        throw ComRelayError.illegalArgument('Missing target protocol');
                    }
                    if (!this.mSourceProtocol.isConnected()) {
                        debug.debug(TAG, 'Connecting to source protocol...');
                        yield this.mSourceProtocol.connect().toPromise();
                    }
                    if (!this.mTargetProtocol.isConnected()) {
                        debug.debug(TAG, 'Connecting to target protocol...');
                        yield this.mTargetProtocol.connect().toPromise();
                    }
                    debug.debug(TAG, 'Source/Target are now connected. Listening to messages...');
                    // We need to reconnect
                    this.listenToDataStream();
                    resolve();
                }
                catch (ex) {
                    reject(ex);
                }
            }));
        });
    }
    stop() {
        if (this.mTargetProtocol && this.mTargetProtocol.isConnected()) {
            this.mSourceProtocol.disconnect();
        }
        if (this.mSourceProtocol && this.mSourceProtocol.isConnected()) {
            this.mSourceProtocol.disconnect();
        }
    }
    listenToDataStream() {
        // if (!this.mSourceProtocol!.isConnected()){
        //     await this.mSourceProtocol!.connect();
        // }
        return new Promise((resolve, reject) => {
            this._sourceProtocolSubscription = this
                .mSourceProtocol
                .receiveStream()
                .subscribe({
                next: (data) => {
                    if (!data) {
                        debug.debug(TAG, 'Received null event...');
                        return;
                    }
                    if (!this.mTargetProtocol) {
                        debug.debug(TAG, 'Target protocol is null. Cannot redirect message to it');
                        return;
                    }
                    try {
                        debug.debug(TAG, `Received new message`, byteConverter.bufferToHexString(data));
                        this.mTargetProtocol
                            .send(data)
                            .toPromise()
                            // .catch((err) => {
                            //     debug(TAG, `Source cannot send to target protocol`, err);
                            //     // TODO check if we are still connected
                            //     return Response.create();
                            // })
                            .then((response) => {
                            debug.debug(TAG, `Target protocol responded with: `, byteConverter.bufferToHexString(response));
                            return this.mSourceProtocol.write(response);
                        })
                            .then(() => {
                            debug.debug(TAG, `Successfully sent response!`);
                        })
                            .catch((err) => {
                            debug.debug(TAG, `Cannot transfer response to source protocol: ${err} JSON=${JSON.stringify(err)}`, err);
                        });
                    }
                    catch (err) {
                        debug.debug(TAG, `Source cannot transfer data to target due to error: ${err}`, err);
                    }
                },
                error: (err) => {
                    debug.debug(TAG, `Error occured ${err}`);
                },
                complete: () => {
                    resolve();
                }
            });
        });
    }
    onStop() {
        // TODO cleanup
        return this;
    }
}
exports.ComRelay = ComRelay;
function promiseTimout(ms, callback) {
    return new Promise(function (resolve, reject) {
        debug.debug(TAG, `promiseTimout start with ${ms} milliseconds`);
        // Set up the real work
        callback(resolve, reject);
        // Set up the timeout
        if (ms >= 0) {
            setTimeout(function () {
                reject('Promise timed out after ' + ms + ' ms');
            }, ms);
        }
    });
}

});

var comRelay$1 = unwrapExports(comRelay);
var comRelay_1 = comRelay.ComRelayError;
var comRelay_2 = comRelay.ComRelay;

var lib = createCommonjsModule(function (module, exports) {
"use strict";
function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(relayServer);
__export(comRelay);

});

var index = unwrapExports(lib);

var public_api = createCommonjsModule(function (module, exports) {
"use strict";
function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(lib);

});

var public_api$1 = unwrapExports(public_api);

var iotizeDeviceClient_jsRelayImpl = createCommonjsModule(function (module, exports) {
"use strict";
/**
 * Generated bundle index. Do not edit.
 */
function __export(m) {
    for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });
__export(public_api);

});

var iotizeDeviceClient_jsRelayImpl$1 = unwrapExports(iotizeDeviceClient_jsRelayImpl);

export default iotizeDeviceClient_jsRelayImpl$1;
//# sourceMappingURL=iotize-device-client.js-relay-impl.js.map
