/**
 * 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,
    check_integer, MAX_ARITY, check_nil,
    exec_build, exec_unify, fs
} from "../../nova/core.mjs";

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

function run(pi) {
    let pc = 0;
    let accu = 0;
    while (pc < pi.code.length) {
        let instr = pi.code[pc++];
        let value = run_get(pi, instr);
        accu = run_fun(instr, accu, value);
        run_set(pi, instr, accu);
        pc += run_jump(instr, accu);
    }
}

function run_get(pi, instr) {
    let act = instr >> 20;
    let obj = ((instr >> 10) & 0x03FF) - 512;
    switch ((act >> 4) & 0x0F) {
        case 0: /* get */
            return pi.state[obj];
        case 1: /* cst */
            return obj;
        case 2: /* set */
            return 0;
        case 4: /* out */
            return 0;
        case 5: /* in */
            fs.writeSync(process.stdout.fd, ": ");
            return Number(BigInt.asIntN(32, readBig()));
        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 8: /* nil */
            /* t.b.d. */
            return 0;
        case 9: /* cons */
            /* t.b.d. */
            return 0;
        case 10: /* car */
            /* t.b.d. */
            return 0;
        case 11: /* cdr */
            /* t.b.d. */
            return 0;
        default:
            throw new Error("illegal fun");
    }
}

function run_set(pi, instr, accu) {
    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[obj] = accu;
            return;
        case 4: /* out */
            fs.writeSync(process.stdout.fd, accu.toString()+"\n");
            return;
        case 5: /* in */
            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 8: /* unit */
            /* t.b.d. */
            return 0;
        case 9: /* pair */
            /* t.b.d. */
            return 0;
        case 15: /* true */
            return rel;
        default:
            throw new Error("illegal cond");
    }
}

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

function readBig() {
    let res = Buffer.alloc(1024);
    let len = fs.readSync(0, res, 0, 1024);
    return BigInt(res.toString('utf8', 0, len));
}

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

/**
 * test_ir_piwam_new(C, S, P): internal only
 * The predicate succeeds in P with a CPU backed π-WAM for
 * the code C and the state S.
 */
function test_ir_piwam_new(args) {
    let pi = new PiWam();
    let alpha = exec_build(args[0]);
    pi.code = list_ints(alpha);
    let beta = exec_build(args[1]);
    pi.state = list_ints(beta);
    return exec_unify(args[2], pi);
}

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 args = new Array(i);
    peek = obj;
    i = 0;
    while (is_structure(peek) &&
        "." === peek.functor &&
        peek.args.length === 2) {
        let val = deref(peek.args[0]);
        check_integer(val);
        if (typeof val === 'number') {
            args[i++] = val | 0;
        } else {
            args[i++] = Number(BigInt.asIntN(32, val));
        }
        peek = deref(peek.args[1]);
    }
    return args;
}

/**
 * test_ir_piwam_run(P): internal only
 * The predicate fails. As a side effect it will run the
 * π-WAM P in the current thread. The predicate terminates
 * when the π-WAM P terminates.
 */
function test_ir_piwam_run(args) {
    let alpha = exec_build(args[0]);
    run(alpha);
    return false;
}

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

export function main() {
    set("ir_piwam_new", 3, make_check(test_ir_piwam_new));
    set("ir_piwam_run", 1, make_check(test_ir_piwam_run));
}

