/**
 * Warranty & Liability
 * To the extent permitted by applicable law and unless explicitly
 * otherwise agreed upon, XLOG Technologies AG makes no warranties
 * regarding the provided information. XLOG Technologies AG assumes
 * no liability that any problems might be solved with the information
 * provided by XLOG Technologies AG.
 *
 * Rights & License
 * All industrial property rights regarding the information - copyright
 * and patent rights in particular - are the sole property of XLOG
 * Technologies AG. If the company was not the originator of some
 * excerpts, XLOG Technologies AG has at least obtained the right to
 * reproduce, change and translate the information.
 *
 * Reproduction is restricted to the whole unaltered document. Reproduction
 * of the information is only allowed for non-commercial uses. Selling,
 * giving away or letting of the execution of the library is prohibited.
 * The library can be distributed as part of your applications and libraries
 * for execution provided this comment remains unchanged.
 *
 * Restrictions
 * Only to be distributed with programs that add significant and primary
 * functionality to the library. Not to be distributed with additional
 * software intended to replace any components of the library.
 *
 * Trademarks
 * Jekejeke is a registered trademark of XLOG Technologies AG.
 */

const MAX_TILT = 64;

let fs;
let workers;
if (typeof window === 'undefined') {
    fs = await import("node:fs");
    workers = await import("node:worker_threads");
} else {
    fs = undefined;
    workers = undefined;
}

function PiMap() {
    this.code = null;
    this.offset = null;
    this.state = null;
}

function PiWarp() {
    this.map = null;
    this.from = 0;
    this.to = 0;
    this.id = 0;
    this.tilt = 0;
    this.cmd = false;
    this.buf = null;
}

let warp = new PiWarp();

function run_warp(warp, from, to) {
    let active = true;
    while (active) {
        active = false;
        for (let i = (warp.cmd ? warp.id : from); i < to; i++) {
            if (run_id(warp, i)) {
                if (warp.cmd) {
                    warp.id = i;
                    return;
                } else {
                    active = true;
                }
            }
        }
    }
}

function run_id(warp, id) {
    let pi = warp.map;
    let offset = pi.offset[id];
    let pc = pi.state[offset-2];
    let accu = pi.state[offset-1];
    for (let k = (warp.cmd ? warp.tilt : 0); k < MAX_TILT; k++) {
        if (pc < pi.code.length) {
            let instr = pi.code[pc];
            let value = run_get(warp, pi, instr, offset);
            if (warp.cmd) {
                pi.state[offset-2] = pc;
                pi.state[offset-1] = accu;
                warp.tilt = k;
                return true;
            } else {
                accu = run_fun(instr, accu, value);
                run_set(pi, instr, accu, offset);
                pc = pc + 1 + run_jump(instr, accu);
            }
        } else {
            pi.state[offset-2] = pc;
            pi.state[offset-1] = accu;
            return false;
        }
    }
    pi.state[offset-2] = pc;
    pi.state[offset-1] = accu;
    return true;
}

function run_get(warp, pi, instr, offset) {
    let act = instr >> 20;
    let obj = ((instr >> 10) & 0x03FF) - 512;
    switch ((act >> 4) & 0x0F) {
        case 0: /* get */
            return pi.state[offset+obj];
        case 1: /* cst */
            return obj;
        case 2: /* set */
            return 0;
        case 4: /* out */
            return 0;
        case 5: /* in */
            extra_outline(warp, pi, obj, offset);
            return 0;
        case 6: /* tbl */
            return pi.code[obj];
        default:
            throw new Error("illegal mode");
    }
}

function run_fun(instr, accu, value) {
    let act = instr >> 20;
    switch (act >> 8) {
        case 0: /* val */
            return value;
        case 1: /* acc */
            return accu;
        case 2: /* add */
            return (value + accu) | 0;
        case 3: /* sub */
            return (value - accu) | 0;
        case 4: /* mul */
            return Math.imul(value, accu);
        case 5: /* zdiv */
            return (value / accu) | 0;
        case 6: /* rem */
            return value % accu;
        default:
            throw new Error("illegal fun");
    }
}

function run_set(pi, instr, accu, offset) {
    let act = instr >> 20;
    let obj = ((instr >> 10) & 0x03FF) - 512;
    switch ((act >> 4) & 0x0F) {
        case 0: /* get */
            return;
        case 1: /* cst */
            return;
        case 2: /* set */
            pi.state[offset+obj] = accu;
            return;
        case 4: /* out */
            thread_send({cmd: "output", arg: load_ints(obj, pi.state, offset)});
            return;
        case 5: /* in */
            return;
        case 6: /* tbl */
            return;
        default:
            throw new Error("illegal mode");
    }
}

function run_jump(instr, accu) {
    let act = instr >> 20;
    let rel = (instr & 0x03FF) - 512;
    switch (act & 0x0F) {
        case 0: /* eq */
            return (accu === 0 ? rel : 0);
        case 1: /* nq */
            return (accu !== 0 ? rel : 0);
        case 2: /* ls */
            return (accu < 0 ? rel : 0);
        case 3: /* gr */
            return (accu > 0 ? rel : 0);
        case 4: /* lq */
            return (accu <= 0 ? rel : 0);
        case 5: /* gq */
            return (accu >= 0 ? rel : 0);
        case 15: /* true */
            return rel;
        default:
            throw new Error("illegal cond");
    }
}

/******************************************************************/
/* π-WAM Store/Load                                               */
/******************************************************************/

function store_ints(res, max, state, offset) {
    for (let i = 0; i < max; i++) {
        if (i < res.length) {
            state[offset + i] = res[i];
        } else {
            state[offset + i] = 0;
        }
    }
}

function load_ints(max, state, offset) {
    let res = new Array(max);
    for (let i = 0; i < max; i++)
        res[i] = pi.state[offset + i]
    return res;
}

function result_promise() {
    return new Promise((resolve) => {
        if (res !== undefined) {
            let val = res;
            res = undefined;
            resolve(val);
        } else {
            func = resolve;
        }
    });
}

function extra_outline(warp, pi, obj, offset) {
    if (warp.cmd) {
        store_ints(warp.buf, obj, pi.state, offset);
        warp.buf = null;
        warp.cmd = false;
    } else {
        warp.cmd = true;
    }
}

/******************************************************************/
/* Worker Utility                                                 */
/******************************************************************/

let pi = new PiMap();
let func = undefined;
let res = undefined;

function thread_send(msg) {
    if (fs !== undefined) {
        workers.parentPort.postMessage(msg);
    } else {
        self.postMessage(msg);
    }
}

async function thread_handler(msg) {
    switch (msg.cmd) {
        case "init":
            pi.code = msg.code;
            pi.offset = msg.offset;
            pi.state = msg.state;
            break;
        case "run":
            warp.map = pi;
            warp.from = msg.at;
            warp.to = msg.at+msg.len;
            for (let i = warp.from; i < warp.to; i++)
                warp.map.state[warp.map.offset[i]-3] = i;
            run_warp(warp, warp.from, warp.to);
            while (warp.cmd) {
                thread_send({cmd: "input"});
                warp.buf = await result_promise();
                run_warp(warp, warp.from, warp.to);
            }
            thread_send({cmd: "finish"});
            break;
        case "result":
            if (func !== undefined) {
                let temp = func;
                func = undefined;
                temp(msg.arg);
            } else {
                res = msg.arg;
            }
            break;
        default:
            throw new Error("illegal cmd");
    }
}

if (fs !== undefined) {
    workers.parentPort.on('message', thread_handler);
} else {
    self.onmessage = async (event) => thread_handler(event.data);
}