import { QueueComProtocol } from '@iotize/tap/protocol/core';
import { Subject, of, throwError, from } from 'rxjs';
import { delay, first, timeout } from 'rxjs/operators';
import { createDebugger } from '@iotize/common/debug';
import { hexStringToBuffer } from '@iotize/common/byte-converter';
import { TapRequestFrame, ResultCode } from '@iotize/tap/client/api';
import { TapRequestFrameBuilder, TapRequestHelper, TapClient, TapApduRequestConverter, TapApduResponseConverter, TapClientError, TapResponseFrameBuilder } from '@iotize/tap/client/impl';
import { TapResponse, ServiceCallRunner, Tap } from '@iotize/tap';
import '@iotize/tap/service/core';

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

class MockRouter {
    constructor() {
        this._routes = [];
    }
    //     constructor() {
    //         this._routes = [];
    //     }
    get routes() {
        return this._routes;
    }
    findRoute(request) {
        const candidates = this._routes.filter((r) => r.predicate(request));
        if (candidates.length === 0) {
            return undefined;
        }
        else {
            return candidates[0].adapter(request);
        }
    }
    // addRoutes(map: RouteMapping) {
    //     // throw new Error('Method not implemented.');
    //     // TODO
    // }
    addRoute(predicate, adapter) {
        // var filter: Predicate<Command>;
        // if (typeof left === 'string') {
        //     left = Util.parseApiRequestString(left);
        // }
        // if (left instanceof 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 as Predicate<Command>;
        // }
        // var adapter: ResponseAdapter<T>;
        // if (right instanceof ResponseImpl) {
        //     adapter = new (class Tmp implements ResponseAdapter<T> {
        //         public adapt(command: Command) {
        //             return right;
        //         }
        //     })();
        // } else if (right instanceof Uint8Array) {
        //     adapter = new (class Tmp implements ResponseAdapter<T> {
        //         public adapt(command: Command) {
        //             return new ResponseImpl<T>(right);
        //         }
        //     })();
        // } else if (typeof right === 'function') {
        //     adapter = {
        //         adapt: right
        //     };
        // } else {
        //     adapter = right as ResponseAdapter<T>;
        // }
        this._routes.push({
            predicate,
            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
    //      *
    //      * TapResponse must either be:
    //      *  - an hexadecimal data represented as a string (0xABCD)
    //      *  - An object with a coreRet and a body
    //      *  - A Uint8Array
    //      */
    // addRoutes(map: { [key: string]: RouteType }): this {
    //     for (const pathString in map) {
    //         const info = map[pathString];
    //         const command: Command = Util.parseApiRequestString(pathString);
    //         let response;
    //         if (info instanceof Uint8Array) {
    //             response = new ResponseImpl(info);
    //         } else if (info instanceof ResponseImpl) {
    //             response = info;
    //         } else if (typeof info === 'number') {
    //             response = ResponseImpl.create(info);
    //         } else if (typeof info === 'string') {
    //             response = new ResponseImpl(
    //                 hexStringToBuffer(info)
    //             );
    //         } else if (typeof info === 'object') {
    //             const codeRet: number = (info as RouteTypeObject).codeRet;
    //             response = new ResponseImpl(
    //                 KaitaiStreamWriter.mergeArrays(
    //                     Uint8Array.from([codeRet]),
    //                     (info as RouteTypeObject).body || new Uint8Array(0)
    //                 )
    //             );
    //         } else {
    //             throw new Error(
    //                 `Invalid response type ${typeof info} with value: ${JSON.stringify(
    //                     info
    //                 )}`
    //             );
    //         }
    //         debug(TAG, `\t- Add map ${command} -> ${response}`);
    //         this.addRoute(command, response);
    //     }
    //     return this;
    // }
    clearRoutes() {
        this._routes = [];
        return this;
    }
}
// /**
//  * @deprecated in favor of {@link #addRoute}
//  */
// public mapResponse<T>(
//     left:
//         | FilterFunction<TapRequestFrame>
//         | Predicate<TapRequestFrame>
//         | TapRequestFrame,
//     right:
//         | ResponseAdapterFunction<T>
//         | ResponseAdapter<T>
//         | TapResponse<T>
//         | Uint8Array
// ): this {
//     return this.addRoute(left, right);
// }
// public addRoute<T>(
//     left:
//         | FilterFunction<TapRequestFrame>
//         | Predicate<TapRequestFrame>
//         | TapRequestFrame,
//     right:
//         | ResponseAdapterFunction<T>
//         | ResponseAdapter<T>
//         | TapResponse<T>
//         | Uint8Array
// ): this {
//     this.router.addRoute(left, right);
//     return this;
// }
// //     public Configuration getConfiguration() {
// //     return configuration;
// // }

var __awaiter = (this && this.__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());
    });
};
const TAG$1 = 'MockProtocol';
function createMockProtocol() {
    return new 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 QueueComProtocol {
    constructor() {
        super();
        this.mockOptions = {
            connect: {
                delay: 100,
            },
            disconnect: {
                delay: 100,
            },
            write: {
                delay: 1,
            },
        };
        this.input = new Subject();
        this.output = new Subject();
        this._router = new MockRouter();
        // this.connectDelay = 2000;
        // this.disconnectDelay = 2000;
        // this.callback = callback;
        // this.lastReceivedData = new Uint8Array(0);
        // this.configuration = new Configuration();
        // this.configuration.connectionTimeoutMillis = 3000;
        // this.configuration.sendTimeoutMillis = 3000;
    }
    static MIRROR() {
        const mockProtocol = new MockProtocol();
        mockProtocol.input.subscribe((input) => {
            mockProtocol.setOutputData(input);
        });
        // mockProtocol.router.addRoute(
        //     (command: Uint8Array) => {
        //         return true;
        //     },
        //     (request: Uint8Array) => {
        //         return request;
        //     }
        // );
        return mockProtocol;
    }
    // /**
    //  * Create a new instance from roote mapping
    //  */
    // public static createFromRoutes(map: RouteMapping): MockProtocol {
    //     const mockProtocol = new MockProtocol();
    //     mockProtocol.router.addRoutes(map);
    //     return mockProtocol;
    // }
    // public get router(): MockRouter {
    //     return this._router;
    // }
    setConnectionState(connectionState) {
        debug(TAG$1, `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;
    }
    setOutputData(inputData) {
        this.lastTranseiveData = inputData;
        this.output.next(inputData);
    }
    setInputData(outputData) {
        this.lastReceivedData = outputData;
        this.input.next(outputData);
    }
    setCallback(callback) {
        this.callback = callback;
    }
    _connect(options) {
        return of(undefined).pipe(delay(this.mockOptions.connect.delay));
    }
    _disconnect(options) {
        return of(undefined).pipe(delay(this.mockOptions.disconnect.delay));
    }
    write(request) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (this.callback) {
                    yield this.callback.beforeWrite(request);
                }
                // this.lastReceivedData = request;
                // const response = this.router.findRoute(request);
                // if (!response) {
                //     throw new Error(`Not route for request ${bufferToHexString(request)}`)
                // }
                // debug(
                //     TAG,
                //     'MockProtocol',
                //     'Mocking response for ' + request + ' => ' + response
                // );
                // var result = this.responseEncoder.encode(response);
                this.setInputData(request);
                if (this.callback) {
                    yield this.callback.afterWrite(request);
                }
                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();
            }
            return this.output.pipe(first()).toPromise();
            // while (!this.hasNewData()) {
            //     // TODO wait ??
            // }
            // if (this.lastTranseiveData) {
            //     const result = of(this.lastTranseiveData);
            //     if (this.callback) {
            //         this.callback.afterRead(result);
            //     }
            //     return result.toPromise();
            // } else {
            //     return Promise.reject(new Error('Read timeout'));
            // }
        }
        catch (err) {
            return Promise.reject(err);
        }
    }
}
//    public addOnRequestListener() {
//        this.mOnRequestListener =
//    }
//    public interface onRequestListener{
//        onRequest(RequestMessage request);
//    }

const TAP_REQUEST_STRING_REGEX = /^(GET|PUT|POST)\s+(\/[0-9]+\/[0-9]*\/[0-9]+)\s?(\s+(0x[a-zA-Z0-9]+))?$/i;
function createTapRequestFromString(request) {
    const match = TAP_REQUEST_STRING_REGEX.exec(request);
    if (match) {
        const methodType = match[1];
        const path = match[2];
        const body = match[4] ? hexStringToBuffer(match[4]) : undefined;
        return TapRequestFrameBuilder.create(TapRequestFrame.MethodType[methodType], path, body);
    }
    else {
        throw new Error(`Invalid tap request string format: "${request}".`);
    }
}
const tapRequestEqualFilter = (expectedRequest) => {
    return (request) => {
        return (TapRequestHelper.toString(expectedRequest) ===
            TapRequestHelper.toString(request));
    };
};
const ɵ0 = tapRequestEqualFilter;

const TAG = 'MockClient';
function createMockClient(options) {
    return new MockClient({
        isConnected: !!(options === null || options === void 0 ? void 0 : options.connected),
    });
}
class MockClient extends TapClient {
    constructor(options = {}, router = new MockRouter()) {
        super(new TapApduRequestConverter(), new TapApduResponseConverter());
        this.router = router;
        this.input = new Subject();
        this.output = new Subject();
        this.options = Object.assign(Object.assign({}, MockClient.DEFAULT_OPTIONS), options);
    }
    isConnected() {
        return this.options.isConnected;
    }
    // /**
    //  *
    //  * TOPO add support for parameters ?
    //  * TODO use Shell Grammar instead ?
    //  *
    //  * @param map
    //  */
    // public static createFromMap(map: MappingType): MockClient {
    //     const client = new MockClient();
    //     client.addRoutes(map);
    //     return client;
    // }
    static createWithMockProtocol() {
        const client = new MockClient({
            isConnected: false,
        });
        client.addComProtocol(createMockProtocol());
        return client;
    }
    connect() {
        const observable = of(undefined).pipe(delay(this.options.connect.delay));
        observable.subscribe(() => {
            this.options.isConnected = true;
        });
        return observable;
    }
    disconnect() {
        const observable = of(undefined).pipe(delay(this.options.disconnect.delay));
        observable.subscribe(() => {
            this.options.isConnected = false;
        });
        return observable;
    }
    mockResponse(tapResponse) {
        this.output.next(tapResponse);
    }
    request(request) {
        if (!this.isConnected()) {
            return throwError(TapClientError.notConnectedError());
        }
        const responseP = this.output
            .pipe(
        // tap(event => console.log('EVENT', event)),
        first(), delay(this.options.command.delay), timeout(this.options.command.timeout))
            .toPromise();
        this.input.next(request);
        return from(responseP);
    }
    // /**
    //  * @deprecated in favor of addRoute
    //  * @param cmd
    //  * @param response
    //  */
    // public mapCommandToResponse(cmd: Command, response: TapResponse<any>) {
    //     return this.addRoute(cmd, response);
    // }
    // public addRoute<T>(
    //     command: Command | string | RegExp | TapRequestFilter,
    //     response: ResponseMapType
    // ): this {
    //     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.parseApiRequestString(command);
    //     }
    //     this.router.addRoute(
    //         command,
    //         this.createResponse(command, response)
    //     );
    //     return this;
    // }
    // public addRoutes(map: MappingType): this {
    //     for (const pathString in map) {
    //         const info = map[pathString];
    //         const command: Command = Util.parseApiRequestString(pathString);
    //         const response = this.createResponse(command, info);
    //         this.addRoute(command, response);
    //     }
    //     return this;
    // }
    // public hasRoute(command: Command | string): boolean {
    //     if (typeof command === 'string') {
    //         command = Util.parseApiRequestString(command);
    //     }
    //     return this.router.findRoute(command) != undefined;
    // }
    // createResponse<T>(
    //     command: Command | TapRequestFilter,
    //     info: ResponseMapType
    // ): TapResponse<T> | ResponseAdapterFunction<any> {
    //     let response: TapResponse<T>;
    //     if (typeof info === 'number') {
    //         response = TapResponse.create(info);
    //     } else if (typeof info === 'string') {
    //         response = new TapResponse(hexStringToBuffer(info));
    //     } else if (typeof info === 'object') {
    //         if (info instanceof TapResponse) {
    //             response = info;
    //         } else {
    //             response = TapResponse.create(typeof info.codeRet === 'number' ? info.codeRet : info.status);
    //             (response as TapResponse<T>).setBody(info.body);
    //         }
    //     } else if (typeof info === 'function') {
    //         if (command instanceof ApiRequest) {
    //             response = info(command);
    //         } else {
    //             return (command: TapRequestFrame) => {
    //                 const response = info(command);
    //                 if (response instanceof ResponseImpl) {
    //                     response.setRequest(command);
    //                 }
    //                 return response;
    //             };
    //         }
    //     } else {
    //         throw new Error(
    //             `Invalid payload type from key ${command.toString()}`
    //         );
    //     }
    //     if (command instanceof ApiRequest && response instanceof ResponseImpl) {
    //         response.setRequest(command);
    //     }
    //     return response;
    // }
    computeResponse(cmd) {
        const response = this.router.findRoute(cmd);
        if (response) {
            return response;
        }
        else {
            debug(TAG, `MockClient`, `command ${cmd} has not been mapped. Returning 404 not found`);
            return TapResponseFrameBuilder.ERROR(ResultCode.NOT_FOUND);
        }
    }
}
MockClient.DEFAULT_OPTIONS = {
    isConnected: false,
    connect: {
        delay: 100,
    },
    disconnect: {
        delay: 100,
    },
    command: {
        delay: 100,
        timeout: 500,
    },
    encryption: true,
};

function createMockServiceFunctionCustom(responseBuilder, options = {
    responseDelay: 10,
}) {
    return (...args) => {
        return of(responseBuilder(...args))
            .pipe(delay(options.responseDelay))
            .toPromise();
    };
}
function createMockServiceFunction(responseStatus = ResultCode.OK, options = {
    responseDelay: 10,
}) {
    return () => {
        const response = createMockServiceResponse(responseStatus);
        return of(response).pipe(delay(options.responseDelay)).toPromise();
    };
}
function createMockServiceFunctionWithBody(responseStatus = ResultCode.OK, body, options = {
    responseDelay: 10,
}) {
    return () => {
        const response = createMockServiceResponseWithBody(responseStatus, body);
        return of(response).pipe(delay(options.responseDelay)).toPromise();
    };
}
function createMockServiceResponse(responseStatus = ResultCode.OK) {
    const response = new TapResponse(TapResponseFrameBuilder.create(responseStatus));
    return response;
}
function createMockServiceResponseWithBody(responseStatus = ResultCode.OK, body) {
    const response = new TapResponse(TapResponseFrameBuilder.create(responseStatus));
    if (body instanceof Uint8Array) {
        response['_tapResponse'].data = body;
    }
    if (body) {
        response.setBody(body);
    }
    return response;
}
function createMockServiceResponseError(code) {
    const response = new TapResponse(TapResponseFrameBuilder.ERROR(code));
    return response;
}
function createMockService(constructorFct) {
    const serviceCallRunnner = new MockServiceCallRunner();
    return new constructorFct(serviceCallRunnner);
}
class MockServiceCallRunner extends ServiceCallRunner {
    // TODO
    constructor() {
        super(new MockClient());
    }
}

function createMockTap(options) {
    return new MockTap(options);
}
/**
 * @experimental
 */
class MockTap extends Tap {
    get client() {
        return this._client;
    }
    get lwm2m() {
        return this._lwm2m;
    }
    constructor(options) {
        super(createMockClient(options));
    }
}

"use strict";
Array.prototype.findSafe = function (predicate) {
    const val = this.find(predicate);
    if (val === undefined) {
        throw new Error(`Cannot find element with predicate: ${predicate}`);
    }
    return val;
};

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

export { MockClient, MockProtocol, MockServiceCallRunner, MockTap, createMockClient, createMockProtocol, createMockService, createMockServiceFunction, createMockServiceFunctionCustom, createMockServiceFunctionWithBody, createMockServiceResponse, createMockServiceResponseError, createMockServiceResponseWithBody, createMockTap, createTapRequestFromString, ɵ0, MockRouter as ɵa };
//# sourceMappingURL=iotize-tap-testing.js.map
