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());
    });
};
import { chunkArray } from '@iotize/common/array';
import { bufferToHexString } from '@iotize/common/byte-converter';
import { TapRequestFrame } from '@iotize/tap/client/api';
import { TapRequestFrameBuilder } from '@iotize/tap/client/impl';
import { defer, Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import { PathParameter } from './path-parameter';
import { TapResponse } from './response-impl';
import { TapError } from './tap-errors';
export function serviceCallToString(call, options) {
    function printObject(obj) {
        if (obj instanceof Uint8Array) {
            return '0x' + bufferToHexString(obj);
        }
        else if (typeof obj === 'object') {
            return '{' + Object.keys(obj).map(key => `"${key}":${printObject(obj[key])}`).join(',') + '}';
        }
        else {
            return JSON.stringify(obj);
        }
    }
    const lwm2mPath = ServiceCallRunner.resolvePathParameters(call);
    const pathAlias = call.pathAlias ? PathParameter.fillAllParams(call.pathAlias, call.pathParameters || {}) : undefined;
    let result = `${TapRequestFrame.MethodType[call.method]} ${pathAlias || lwm2mPath}`;
    if (call.pathAlias) {
        result += ` (${lwm2mPath})`;
    }
    if (!(options === null || options === void 0 ? void 0 : options.skipBody) && call.body !== undefined) {
        result += ' ' + printObject(call.body);
    }
    return result;
}
export class ServiceCallRunner {
    constructor(client, options = {
        chunkSize: 220
    }) {
        this.client = client;
        this.options = options;
    }
    get(path, body) {
        return this.execute({
            method: TapRequestFrame.MethodType.GET,
            path,
            body
        });
    }
    put(path, body) {
        return this.execute({
            method: TapRequestFrame.MethodType.PUT,
            path,
            body
        });
    }
    post(path, body) {
        return this.execute({
            method: TapRequestFrame.MethodType.POST,
            path,
            body
        });
    }
    prepare(call) {
        return defer(() => __awaiter(this, void 0, void 0, function* () {
            const tapRequestFrame = ServiceCallRunner.toTapRequest(call);
            // const debugArgs: string[] = [`CALL: ${call.method} ${call.path} body=`];
            // debugArgs.push(
            //     call.body instanceof Uint8Array
            //         ? bufferToHexString(call.body)
            //         : call.body
            // );
            // debugArgs.push(`(${args[0].toString()})`);
            try {
                const tapResponseFrame = yield this.client.request(tapRequestFrame).toPromise();
                const response = new TapResponse(tapResponseFrame, tapRequestFrame);
                const responseBodyDecoder = ServiceCallRunner.resolveResponseBodyDecoder(call);
                if (responseBodyDecoder) {
                    response.setBodyDecoder({
                        decode: responseBodyDecoder
                    });
                }
                if (!response.isSuccessful()) {
                    response.setError(TapError.reponseStatusError(response, call));
                }
                return response;
            }
            catch (err) {
                if (!(err instanceof TapError)) {
                    throw TapError.executeRequestError(call, err);
                }
                throw err;
            }
        }));
        // return response$.pipe(
        //     tap(
        //         response => {
        //             debug(this.constructor.name, ...debugArgs, '=>', response.toString());
        //         },
        //         err => {
        //             debug(this.constructor.name, ...debugArgs, ` => ${err.message}`);
        //         }
        //     )
        // );
    }
    execute(call) {
        return this.prepare(call).pipe(first()).toPromise();
    }
    createAppendCalls(call, chunkSize = this.options.chunkSize) {
        const chunks = chunkArray(Array.from(call.body || new Uint8Array()), chunkSize)
            .map((chunk, index) => {
            return Object.assign(Object.assign({}, call), { body: Uint8Array.from(chunk) });
        });
        return chunks;
    }
    prepareAppendCall(call, chunkSize = this.options.chunkSize) {
        return new Observable(emitter => {
            let isCancelled = false;
            (() => __awaiter(this, void 0, void 0, function* () {
                try {
                    const calls = this.createAppendCalls(call, chunkSize);
                    let index = 0;
                    let byteOffset = 0;
                    for (const call of calls) {
                        if (isCancelled) {
                            return;
                        }
                        const chunk = call.body;
                        const progress = {
                            total: chunk.length,
                            loaded: index + 1,
                            byteOffset
                        };
                        emitter.next(progress);
                        const response = yield this.execute({
                            method: call.method || TapRequestFrame.MethodType.PUT,
                            path: call.path,
                            body: chunk
                        });
                        try {
                            response.successful();
                        }
                        catch (err) {
                            throw TapError.appendChunkError(err, chunk, progress);
                        }
                        byteOffset += chunk.length;
                        index++;
                    }
                    emitter.complete();
                }
                catch (err) {
                    emitter.error(err);
                    return;
                }
            }))();
            return () => {
                // TODO throw error on cancel
                isCancelled = true;
            };
        });
    }
    static toTapRequest(call) {
        const path = ServiceCallRunner.resolvePathParameters(call);
        const body = ServiceCallRunner.encodeBody(call);
        return TapRequestFrameBuilder.create(call.method, path, body);
    }
    static resolveResponseBodyDecoder(call) {
        if (call.responseBodyDecoder) {
            return toDecoderFunction(call.responseBodyDecoder);
        }
        return undefined;
    }
    static resolvePathParameters(info) {
        return PathParameter.fillAllParams(info.path, info.pathParameters || {});
    }
    /**
     * Encode body according to options and configuration
     */
    static encodeBody(options) {
        if (options.body === undefined) {
            return undefined;
        }
        const encoded = options.bodyEncoder
            ? toEncoderFunction(options.bodyEncoder)(options.body)
            : options.body;
        if (encoded !== undefined && !(encoded instanceof Uint8Array)) {
            throw TapError.invalidServiceCallConfiguration(`Request body encoder is required for call to ${serviceCallToString(options)}`);
        }
        return encoded;
    }
}
function toEncoderFunction(classOrFunction) {
    return typeof classOrFunction === 'function' ? classOrFunction : classOrFunction.encode.bind(classOrFunction);
}
function toDecoderFunction(classOrFunction) {
    return typeof classOrFunction === 'function' ? classOrFunction : classOrFunction.decode.bind(classOrFunction);
}
//# sourceMappingURL=data:application/json;base64, 