import byteConverter from '@iotize/common/byte-converter';
import impl from '@iotize/device-client.js/client/impl';
import core from '@iotize/device-client.js/protocol/core';
import rxjs from 'rxjs';
import operators from 'rxjs/operators';
import common from '@iotize/common';
import byteStream from '@iotize/common/byte-stream';
import api from '@iotize/device-client.js/client/api';
import deviceClient from '@iotize/device-client.js';

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:testing';
exports.debug = common.createDebugger(prefix);

});

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

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



const impl_2 = impl;

const TAG = 'MockProtocol';
class EqualRequestFilter {
    constructor(command) {
        this.command = command;
    }
    test(command) {
        if (!command) {
            return true;
        }
        return this.command.toString() === command.toString();
    }
    toString() {
        return `EqualRequestFilter ${this.command}`;
    }
}
exports.EqualRequestFilter = EqualRequestFilter;
class RegExpFilter {
    constructor(regex) {
        this.regex = regex;
    }
    test(input) {
        const cmdString = input.toString();
        return Boolean(cmdString.match(this.regex));
    }
}
exports.RegExpFilter = RegExpFilter;
class Util {
    // public static REQUEST_REGEX: RegExp = /^(GET|PUT|POST) ((\/[a-zA-Z0-9])+) (0x([a-zA-Z0-9]+))?$/i;
    static parseApiRequestString(cmdString) {
        const parts = cmdString.split(' ');
        const methodtype = parts[0].toUpperCase();
        if (['GET', 'PUT', 'POST'].indexOf(methodtype) === -1) {
            throw new Error(`Invalid command type: ${methodtype}. From command: "${cmdString}"`);
        }
        const methodPath = parts[1];
        let payload = undefined;
        if (parts.length >= 3) {
            payload = byteConverter.hexStringToBuffer(parts[2]);
        }
        // TODO improve as we can call anyother static function
        // It should be reduce to GET, PUT, POST
        return impl_2.ApiRequest[methodtype](methodPath, payload);
    }
}
exports.Util = Util;
class Router {
    constructor() {
        this._routes = [];
    }
    get routes() {
        return this._routes;
    }
    findRoute(request) {
        var adapter;
        for (const route of this._routes) {
            // debug("\t- MockProtocol", `Check route: ${route.filter.toString()}`);
            if (route.predicate.test(request)) {
                adapter = route.adapter;
                break;
            }
        }
        var response;
        if (!adapter) {
            return null;
        }
        else {
            response = adapter.adapt(request);
        }
        return response;
    }
    addRoute(left, right) {
        var filter;
        if (typeof left === 'string') {
            left = Util.parseApiRequestString(left);
        }
        if (left instanceof impl_2.ApiRequest) {
            filter = new EqualRequestFilter(left);
        }
        else if (left instanceof RegExp) {
            filter = new RegExpFilter(left);
        }
        else if (typeof left === 'function') {
            filter = {
                test: left
            };
        }
        else {
            filter = left;
        }
        var adapter;
        if (right instanceof impl.ResponseImpl) {
            adapter = new (class Tmp {
                adapt(command) {
                    return right;
                }
            })();
        }
        else if (right instanceof Uint8Array) {
            adapter = new (class Tmp {
                adapt(command) {
                    return new impl.ResponseImpl(right);
                }
            })();
        }
        else if (typeof right === 'function') {
            adapter = {
                adapt: right
            };
        }
        else {
            adapter = right;
        }
        this._routes.push({
            predicate: filter,
            adapter: adapter
        });
        return this;
    }
    /**
     * Mapping a command (represented as a string with format "<request-type> <path> [data]"
     * ie: GET /3//3 0xABCD or POST /1024//0 0xtest
     *
     * Response must either be:
     *  - an hexadecimal data represented as a string (0xABCD)
     *  - An object with a coreRet and a body
     *  - A Uint8Array
     */
    addRoutes(map) {
        for (const pathString in map) {
            const info = map[pathString];
            const command = Util.parseApiRequestString(pathString);
            let response;
            if (info instanceof Uint8Array) {
                response = new impl.ResponseImpl(info);
            }
            else if (info instanceof impl.ResponseImpl) {
                response = info;
            }
            else if (typeof info === 'number') {
                response = impl.ResponseImpl.create(info);
            }
            else if (typeof info === 'string') {
                response = new impl.ResponseImpl(byteConverter.hexStringToBuffer(info));
            }
            else if (typeof info === 'object') {
                const codeRet = info.codeRet;
                response = new impl.ResponseImpl(byteStream.KaitaiStreamWriter.mergeArrays(Uint8Array.from([codeRet]), info.body || new Uint8Array(0)));
            }
            else {
                throw new Error(`Invalid response type ${typeof info} with value: ${JSON.stringify(info)}`);
            }
            debug.debug(TAG, `\t- Add map ${command} -> ${response}`);
            this.addRoute(command, response);
        }
        return this;
    }
    clearRoutes() {
        this._routes = [];
        return this;
    }
}
exports.Router = Router;

});

var util$1 = unwrapExports(util);
var util_1 = util.EqualRequestFilter;
var util_2 = util.RegExpFilter;
var util_3 = util.Util;
var util_4 = util.Router;

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








const TAG = 'MockProtocol';
/**
 *
 * <p>
 * This is a mock com protocol for testing only
 * <p>
 * We can simulate iotize response according to the request message
 */
class MockProtocol extends core.QueueComProtocol {
    constructor() {
        super();
        this.mockOptions = {
            connect: {
                delay: 100
            },
            disconnect: {
                delay: 100
            },
            write: {
                delay: 1
            }
        };
        this._router = new util.Router();
        // this.connectDelay = 2000;
        // this.disconnectDelay = 2000;
        // this.callback = callback;
        this.inputData = new Uint8Array(0);
        this.outputData = new Uint8Array(0);
        this.commandDecoder = new impl.DefaultCommandDecoder();
        this.responseEncoder = new impl.DefaultResponseEncoder();
        // this.configuration = new Configuration();
        // this.configuration.connectionTimeoutMillis = 3000;
        // this.configuration.sendTimeoutMillis = 3000;
    }
    static MIRROR() {
        var mockProtocol = new MockProtocol();
        mockProtocol.router.addRoute({
            test: (command) => {
                return true;
            }
            // toString: () => {
            //     return `MirrorFilter for ALL request`;
            // }
        }, {
            adapt: (command) => {
                return impl.Response.SUCCESS(command.getData());
            }
        });
        return mockProtocol;
    }
    get router() {
        return this._router;
    }
    setConnectionState(connectionState) {
        debug.debug(TAG, `setConnectionState: ${connectionState}`);
        this.connectionState = connectionState;
        return this;
    }
    setConnectDelay(connectDelay) {
        this.mockOptions.connect.delay = connectDelay;
        return this;
    }
    getDisconnectDelay() {
        return this.mockOptions.disconnect.delay;
    }
    setDisconnectDelay(disconnectDelay) {
        this.mockOptions.disconnect.delay = disconnectDelay;
        return this;
    }
    hasNewData() {
        return this.inputData != null;
    }
    setInputData(inputData) {
        this.inputData = inputData;
    }
    setOutputData(outputData) {
        this.outputData = outputData;
    }
    setCallback(callback) {
        this.callback = callback;
    }
    _connect(options) {
        return rxjs.of(null).pipe(operators.delay(this.mockOptions.connect.delay));
    }
    _disconnect(options) {
        return rxjs.of(null).pipe(operators.delay(this.mockOptions.disconnect.delay));
    }
    write(message) {
        try {
            if (this.callback != null) {
                this.callback.beforeWrite(message);
            }
            this.outputData = message;
            // TODO we need to know if it's a command or a response ?
            var request = this.commandDecoder.decode(message);
            debug.debug(TAG, `MockProtocol decoded input ${byteConverter.bufferToHexString(message)} to ${request}`);
            let response = this.router.findRoute(request);
            if (!response) {
                response = impl.Response.ERROR(api.ResultCode.IOTIZE_501_NOT_IMPLEMENTED);
            }
            debug.debug(TAG, 'MockProtocol', 'Mocking response for ' + request + ' => ' + response);
            var result = this.responseEncoder.encode(response);
            this.inputData = result;
            if (this.callback != null) {
                this.callback.afterWrite(message);
            }
            return new Promise(resolve => setTimeout(resolve, this.mockOptions.write.delay));
        }
        catch (err) {
            return new Promise((resolve, reject) => setTimeout(() => reject(err), this.mockOptions.write.delay));
        }
    }
    read() {
        try {
            if (this.callback) {
                this.callback.beforeRead();
            }
            while (!this.hasNewData()) {
                // TODO wait ??
            }
            if (this.hasNewData()) {
                var result = rxjs.of(this.inputData);
                if (this.callback) {
                    this.callback.afterRead(result);
                }
                return result.toPromise();
            }
            else {
                return Promise.reject(new Error('Read timeout'));
            }
        }
        catch (err) {
            return Promise.reject(err);
        }
    }
    /**
     * @deprecated in favor of {@link #addRoute}
     */
    mapResponse(left, right) {
        return this.addRoute(left, right);
    }
    addRoute(left, right) {
        this.router.addRoute(left, right);
        return this;
    }
    //     public Configuration getConfiguration() {
    //     return configuration;
    // }
    /**
     * Create a new instance from roote mapping
     */
    static createFromRoutes(map) {
        let mockProtocol = new MockProtocol();
        mockProtocol.router.addRoutes(map);
        return mockProtocol;
    }
}
exports.MockProtocol = MockProtocol;
//    public addOnRequestListener() {
//        this.mOnRequestListener =
//    }
//    public interface onRequestListener{
//        onRequest(RequestMessage request);
//    }

});

var mockProtocol$1 = unwrapExports(mockProtocol);
var mockProtocol_1 = mockProtocol.MockProtocol;

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







const TAG = 'MockClient';
;
class MockClient {
    constructor(mapping, options) {
        this._isConnected = false;
        this._router = mapping || new util.Router();
        this.options = options || MockClient.DEFAULT_OPTIONS;
    }
    addInterceptor(interceptor) {
        throw new Error('Method not implemented.');
    }
    isEncryptionEnabled() {
        throw new Error('Method not implemented.');
    }
    /**
     *
     * TOPO add support for parameters ?
     * TODO use Shell Grammar instead ?
     *
     * @param map
     */
    static createFromMap(map) {
        let client = new MockClient();
        client.addRoutes(map);
        return client;
    }
    static fromMockProtocol(protocol) {
        let client = impl.DefaultIoTizeClient.create();
        client.addComProtocol(protocol);
        return client;
    }
    get router() {
        return this._router;
    }
    isConnected() {
        return this._isConnected;
    }
    onConnectionStateChange() {
        throw new Error('Method not implemented.');
    }
    addComProtocol(newProtocol, id) {
        this._protocol = newProtocol;
        return this;
    }
    switchProtocol(type) {
        throw new Error('Method not implemented.');
    }
    getCurrentProtocol() {
        throw new Error('Method not implemented.');
    }
    connect() {
        let observable = rxjs.of(null).pipe(operators.delay(this.options.connect.delay));
        observable.subscribe(() => {
            this._isConnected = true;
        });
        return observable;
    }
    disconnect() {
        let observable = rxjs.of(null).pipe(operators.delay(this.options.disconnect.delay));
        observable.subscribe(() => {
            this._isConnected = false;
        });
        return observable;
    }
    command(request, bodyDecoder) {
        if (!this._isConnected) {
            throw new Error(`Device is not connected`);
        }
        let response = this.computeResponse(request);
        return rxjs.from([response]).pipe(operators.delay(this.options.command.delay));
    }
    useComProtocol(protocol) {
        throw new Error('Method not implemented.');
    }
    send(dataIn, bodyDecoder) {
        throw new Error('Method not implemented.');
    }
    /**
     * @deprecated in favor of addRoute
     * @param cmd
     * @param response
     */
    mapCommandToResponse(cmd, response) {
        return this.addRoute(cmd, response);
    }
    addRoute(command, response) {
        debug.debug(TAG, `\t- Mapping command ${command.toString()} to response ${response.toString()}`);
        if (command instanceof RegExp) {
            throw new Error(`Route with regular expressions are not implemented yet`);
        }
        else if (typeof command === 'string') {
            command = util.Util.parseApiRequestString(command);
        }
        this.router.addRoute(command, this.createResponse(command, response));
        return this;
    }
    addRoutes(map) {
        for (let pathString in map) {
            let info = map[pathString];
            let command = util.Util.parseApiRequestString(pathString);
            let response = this.createResponse(command, info);
            this.addRoute(command, response);
        }
        return this;
    }
    hasRoute(command) {
        if (typeof command === 'string') {
            command = util.Util.parseApiRequestString(command);
        }
        return this.router.findRoute(command) != null;
    }
    setEncryptionAlgo(algo) {
        this.encryptionAlgo = algo;
    }
    getEncryptionAlgo() {
        return this.encryptionAlgo;
    }
    enableEncryption(value) {
        this.options.encryption = value;
        return Promise.resolve();
    }
    createResponse(command, info) {
        let response;
        if (typeof info === 'number') {
            response = impl.Response.create(info);
        }
        else if (typeof info === 'string') {
            response = new impl.Response(byteConverter.hexStringToBuffer(info));
        }
        else if (typeof info === 'object') {
            if (info instanceof impl.Response) {
                response = info;
            }
            else {
                response = impl.Response.create(info.codeRet);
                response.setBody(info.body);
            }
        }
        else if (typeof info === 'function') {
            if (command instanceof impl.ApiRequest) {
                response = info(command);
            }
            else {
                return (command) => {
                    return info(command);
                };
            }
        }
        else {
            throw new Error(`Invalid payload type from key ${command.toString()}`);
        }
        return response;
    }
    computeResponse(cmd) {
        let response = this.router.findRoute(cmd);
        if (response) {
            return response;
        }
        else {
            debug.debug(TAG, `MockClient`, `command ${cmd} has not been mapped. Returning 404 not found`);
            return impl.Response.ERROR(api.ResultCode.IOTIZE_404_NOT_FOUND);
        }
    }
}
exports.MockClient = MockClient;
MockClient.DEFAULT_OPTIONS = {
    connect: {
        delay: 100
    },
    disconnect: {
        delay: 100
    },
    command: {
        delay: 100
    },
    encryption: true
};

});

var mockClient$1 = unwrapExports(mockClient);
var mockClient_1 = mockClient.MockClient;

var mockDevice = 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 });


function getMockDeviceFromSnapshot(snapshotData) {
    return __awaiter(this, void 0, void 0, function* () {
        let protocol = mockProtocol.MockProtocol.createFromRoutes(snapshotData);
        return deviceClient.Tap.fromProtocol(protocol);
    });
}
exports.getMockDeviceFromSnapshot = getMockDeviceFromSnapshot;

});

var mockDevice$1 = unwrapExports(mockDevice);
var mockDevice_1 = mockDevice.getMockDeviceFromSnapshot;

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(mockProtocol);
__export(util);
__export(mockClient);
__export(mockDevice);

});

var public_api$1 = unwrapExports(public_api);

var iotizeDeviceClient_jsTesting = 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_jsTesting$1 = unwrapExports(iotizeDeviceClient_jsTesting);

export default iotizeDeviceClient_jsTesting$1;
//# sourceMappingURL=iotize-device-client.js-testing.js.map
