/**
 * 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.
 */

import {
    set, deref, is_structure, make_check, MAX_ARITY, check_nil,
    exec_build, exec_unify, fs, Compound, norm_smallint, get_ctx
} from "../../nova/core.mjs";

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

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

/******************************************************************/
/* π-WAM Input/Output                                             */
/******************************************************************/

function read_ints() {
    fs.writeSync(process.stdout.fd, ": ");
    let res = Buffer.alloc(1024);
    let len = fs.readSync(0, res, 0, 1024);
    let line = res.toString('utf8', 0, len);
    return line.split(" ")
        .filter(s => s !== "")
        .map(t => Number(BigInt.asIntN(32, BigInt(t))));
}

function write_ints(res) {
    for (let i = 0; i < res.length; i++) {
        fs.writeSync(process.stdout.fd, res[i].toString());
        fs.writeSync(process.stdout.fd, " ");
    }
    fs.writeSync(process.stdout.fd, "\n");
}

/******************************************************************/
/* π-WAM Constructors                                             */
/******************************************************************/

/**
 * test_ir_buf_new(A, B): internal only
 * The predicate succeeds in B with a pi-WAM buffer for the list A.
 */
function test_ir_buf_new(args) {
    let alpha = exec_build(args[0]);
    let res = list_ints(alpha);
    return exec_unify(args[1], res);
}

function list_ints(obj) {
    let peek = obj;
    let i = 0;
    while (is_structure(peek) &&
    "." === peek.functor &&
    peek.args.length === 2 &&
    i < MAX_ARITY) {
        i++;
        peek = deref(peek.args[1]);
    }
    check_nil(peek);
    let buf = new SharedArrayBuffer(i * 4);
    let args = new Int32Array(buf);
    peek = obj;
    i = 0;
    while (is_structure(peek) &&
        "." === peek.functor &&
        peek.args.length === 2) {
        let val = deref(peek.args[0]);
        args[i++] = int32(val);
        peek = deref(peek.args[1]);
    }
    return args;
}

function int32(val) {
    if (typeof val === 'number') {
        return val | 0;
    } else {
        return Number(BigInt.asIntN(32, val));
    }
}

/**
 * test_ir_buf_get(A, B): internal only
 * The predicate succeeds in B with a list for the pi-WAM buffer A.
 */
function test_ir_buf_get(args) {
    let alpha = exec_build(args[0]);
    let res = ints_list(alpha, 0, alpha.length);
    return exec_unify(args[1], res);
}

function ints_list(alpha, off, count) {
    let res = "[]";
    for (let i = off + count - 1; i >= off; i--) {
        res = new Compound(".", [norm_smallint(alpha[i]), res]);
    }
    return res;
}

/**
 * test_ir_map_new(C, O, S, M): internal only
 * The predicate succeeds in M with a CPU backed π-WAM for
 * the code buffer C, the offsets O and the state buffer S.
 */
function test_ir_map_new(args) {
    let res = new PiMap();
    res.code = exec_build(args[0]);
    res.offset = exec_build(args[1]);
    res.state = exec_build(args[2]);
    return exec_unify(args[3], res);
}

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

function thread_handler(worker, msg) {
    switch (msg.cmd) {
        case "finish":
            if (worker.func !== undefined) {
                let temp = worker.func;
                worker.func = undefined;
                temp();
            } else {
                worker.res = null;
            }
            break;
        case "output":
            write_ints(msg.arg);
            break;
        case "input":
            let para = read_ints();
            worker.postMessage({cmd: "result", arg: para});
            break;
        default:
            throw new Error("illegal cmd");
    }
}

function thread_new() {
    let worker;
    if (fs !== undefined) {
        worker = new workers.Worker(new URL('worker.mjs', import.meta.url),
            { type: 'module' });
    } else {
        worker = new Worker(new URL('worker.mjs', import.meta.url),
            { type: 'module' });
    }
    if (fs !== undefined) {
        worker.on('message', (msg) => thread_handler(worker, msg));
    } else {
        worker.onmessage = (event) => thread_handler(worker, event.data);
    }
    return worker;
}

/******************************************************************/
/* π-WAM Interface                                                */
/******************************************************************/

/**
 * test_ir_warp_new(M, W): internal only
 * The predicate succeeds in W with a new 
 * warp for the π-WAM M.
 */
function test_ir_warp_new(args) {
    let alpha = exec_build(args[0]);
    let res = thread_new();
    res.map = alpha;
    res.postMessage({cmd: "init", code: alpha.code, 
                    offset: alpha.offset, state: alpha.state});
    return exec_unify(args[1], res);
}

/**
 * test_ir_warp_start(W, S, L): internal only
 * The predicate succeeds. As a side effect it 
 * starts the warp W at start S of length L.
 */
function test_ir_warp_start(args) {
    let alpha = exec_build(args[0]);
    let beta = exec_build(args[1]);
    let gamma = exec_build(args[2]);
    let at = int32(beta);
    let len = Math.min(alpha.map.offset.length - at, int32(gamma));
    alpha.postMessage({cmd: "run", at: at, len: len});
    return true;
}

/**
 * test_ir_warp_join(W, P): internal only
 * The predicate succeeds in P with a new promise
 * that waits for the warp W to finish.
 */
function test_ir_warp_join(args) {
    let alpha = exec_build(args[0]);
    let buf = get_ctx();
    return exec_unify(args[1], join_promise(buf, alpha));
}

function join_promise(buf, alpha) {
    return new Promise((resolve) => {
        if (alpha.res !== undefined) {
            alpha.res = undefined;
            resolve();
        } else {
            alpha.func = resolve;
        }
    });
}

/**
 * test_ir_warp_free(W): internal only
 * The predicate succeeds. As a side effect it frees the warp W.
 */
function test_ir_warp_free(args) {
    let alpha = exec_build(args[0]);
    alpha.terminate();
    return true;
}

/******************************************************************/
/* π-WAM Lib Init                                                 */
/******************************************************************/

export function main() {
    set("ir_buf_new", 2, make_check(test_ir_buf_new));
    set("ir_buf_get", 2, make_check(test_ir_buf_get));
    set("ir_map_new", 4, make_check(test_ir_map_new));
    set("ir_warp_new", 2, make_check(test_ir_warp_new));
    set("ir_warp_start", 3, make_check(test_ir_warp_start));
    set("ir_warp_join", 2, make_check(test_ir_warp_join));
    set("ir_warp_free", 1, make_check(test_ir_warp_free));
}

