/**
 * 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, get_ctx, make_error
} from "../../nova/core.mjs";

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

const GROUP_SIZE = 8;

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

/******************************************************************/
/* Hack 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");
}

/******************************************************************/
/* Hack Constructors                                              */
/******************************************************************/

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

function cpu_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);
    if (typeof SharedArrayBuffer === 'undefined')
        throw make_error(new Compound("resource_error", ["shared_missing"]));
    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;
}

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

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

/**
 * test_cpu_comp_work(M, W): internal only
 * The predicate succeeds in W with the current
 * group size of the CPU backed π-WAM M.
 */
function test_cpu_comp_work(args) {
    let alpha = exec_build(args[0]);
    let res = GROUP_SIZE;
    return exec_unify(args[1], 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;
}

/******************************************************************/
/* Hack Interface                                                 */
/******************************************************************/

/**
 * test_cpu_group_new(M, G): internal only
 * The predicate succeeds in G with a
 * new group for the CPU backed π-WAM M.
 */
function test_cpu_group_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_cpu_group_start(G, I): internal only
 * The predicate succeeds. As a side effect it
 * starts the group G at group index I.
 */
function test_cpu_group_start(args) {
    let alpha = exec_build(args[0]);
    let beta = exec_build(args[1]);
    let at = int32(beta)*GROUP_SIZE;
    let len = Math.min(alpha.map.offset.length - at, GROUP_SIZE);
    alpha.postMessage({cmd: "run", at: at, len: len});
    return true;
}

/**
 * test_cpu_group_join(G, P): internal only
 * The predicate succeeds in P with a new promise
 * that waits for the group G to finish.
 */
function test_cpu_group_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_cpu_group_free(G): internal only
 * The predicate succeeds. As a side effect
 * it frees the group G.
 */
function test_cpu_group_free(args) {
    let alpha = exec_build(args[0]);
    alpha.terminate();
    return true;
}

/******************************************************************/
/* Hack Lib Init                                                  */
/******************************************************************/

export function main() {
    set("cpu_data_new", 2, make_check(test_cpu_data_new));
    set("cpu_comp_new", 4, make_check(test_cpu_comp_new));
    set("cpu_comp_work", 2, make_check(test_cpu_comp_work));
    set("cpu_group_new", 2, make_check(test_cpu_group_new));
    set("cpu_group_start", 2, make_check(test_cpu_group_start));
    set("cpu_group_join", 2, make_check(test_cpu_group_join));
    set("cpu_group_free", 1, make_check(test_cpu_group_free));
}

