"use strict";
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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DebuggerClient = void 0;
const debugger_worker_1 = require("./debugger-worker");
const types_1 = require("./types");
class DebuggerClient {
    constructor(createWorker) {
        this.requestTimeout = 60000;
        this.commandsReturnLocation = [
            types_1.Command.StepOver,
            types_1.Command.StepInto,
            types_1.Command.StepNext,
            types_1.Command.StepOut,
            types_1.Command.StepInstruction,
            types_1.Command.ContinueUntilBreakpoint,
            types_1.Command.Reset,
            types_1.Command.Step,
        ];
        this.breakpointCommads = [
            types_1.Command.AddBreakpoint,
            types_1.Command.RemoveAllBreakpoints,
            types_1.Command.RemoveBreakpoint
        ];
        this.seq = 0;
        this.createWorker = createWorker !== null && createWorker !== void 0 ? createWorker : (() => new debugger_worker_1.DebuggerWorker());
        this.worker = this.createWorker();
        this.initialized = false;
        this.resolverMap = new Map();
    }
    initialize(init_arguments) {
        return __awaiter(this, void 0, void 0, function* () {
            this.finished = false;
            return yield this.sendRequest(types_1.Command.Initialize, init_arguments);
        });
    }
    reset() {
        return __awaiter(this, void 0, void 0, function* () {
            this.finished = false;
            return yield this.sendRequest(types_1.Command.Reset);
        });
    }
    stepOver() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.StepOver);
        });
    }
    stepInto() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.StepInto);
        });
    }
    stepOut() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.StepOut);
        });
    }
    stepNext() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.StepNext);
        });
    }
    advance(steps) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.StepInstruction, steps);
        });
    }
    continueUntilBreakpoint() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.ContinueUntilBreakpoint);
        });
    }
    addBreakpoint(breakpoint) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.sendRequest(types_1.Command.AddBreakpoint, breakpoint);
        });
    }
    removeBreakpoint(breakpoint) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.sendRequest(types_1.Command.RemoveAllBreakpoints, breakpoint);
        });
    }
    removeAllBreakpoints() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.sendRequest(types_1.Command.RemoveAllBreakpoints);
        });
    }
    requireVariables() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.Variables);
        });
    }
    requireStacktrace() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.Stacktrace);
        });
    }
    evaluateExpression(expression) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.Evaluate, expression);
        });
    }
    runToEnd() {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.sendRequest(types_1.Command.RunToEnd);
        });
    }
    requireCurrentStep() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.Step);
        });
    }
    getCallTrace() {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.sendRequest(types_1.Command.CallTrace);
        });
    }
    sendRequest(command, args) {
        // wrap the timeout
        return this.timeoutPromise(this.requestTimeout, this.internalSendRequest(command, args));
    }
    internalSendRequest(command, args) {
        return new Promise((res, rej) => {
            const channel = new MessageChannel();
            channel.port1.onmessage = ({ data }) => {
                channel.port1.close();
                const resp = data;
                if (resp.command === types_1.Command.Initialize) {
                    this.initialized = true;
                }
                if (this.commandsReturnLocation.includes(resp.command) && resp.body == null) {
                    this.finished = true;
                }
                if (resp.success) {
                    res(resp.body);
                }
                else {
                    rej(resp.message);
                }
            };
            if (this.finished && !this.breakpointCommads.includes(command)) {
                rej("Trace already finishes.");
            }
            else {
                const request = {
                    command: command,
                    seq: this.seq++,
                    arguments: args,
                    type: "request"
                };
                this.worker.postMessage(request, [channel.port2]);
            }
        });
    }
    timeoutPromise(ms, promise) {
        return new Promise((resolve, reject) => {
            const timeoutId = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
                // Re-init the worker since the previous worker hangs
                this.worker.terminate();
                this.worker = this.createWorker();
                reject(new Error("promise timeout"));
            }), ms);
            promise.then((res) => {
                clearTimeout(timeoutId);
                resolve(res);
            }, (err) => {
                clearTimeout(timeoutId);
                reject(err);
            });
        });
    }
}
exports.DebuggerClient = DebuggerClient;
//# sourceMappingURL=debugger-client.js.map