package doglet.edge;

import nova.Machine;
import nova.Store;
import nova.special;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.math.BigInteger;

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

public class piwamlib {

    private static final class PiWam {
        private int[] code;
        private int[] state;
    }

    private static void run(PiWam pi) {
        int pc = 0;
        int accu = 0;
        while (pc < pi.code.length) {
            int instr = pi.code[pc++];
            int value = run_get(pi, instr);
            accu = run_fun(instr, accu, value);
            run_set(pi, instr, accu);
            pc += run_jump(instr, accu);
        }
    }

    private static int run_get(PiWam pi, int instr) {
        int act = instr>>20;
        int 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 */
                System.out.print(": ");
                System.out.flush();
                return readBig().intValue();
            default:
                throw new IllegalArgumentException("illegal mode");
        }
    }

    private static int run_fun(int instr, int accu, int value) {
        int act = instr>>20;
        switch (act>>8) {
            case 0: /* val */
                return value;
            case 1: /* acc */
                return accu;
            case 2: /* add */
                return value + accu;
            case 3: /* sub */
                return value - accu;
            case 4: /* mul */
                return 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 IllegalArgumentException("illegal fun");
        }
    }

    private static void run_set(PiWam pi, int instr, int accu) {
        int act = instr>>20;
        int 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 */
                System.out.println(accu);
                return;
            case 5: /* in */
                return;
            default:
                throw new IllegalArgumentException("illegal mode");
        }
    }

    private static int run_jump(int instr, int accu) {
        int act = instr>>20;
        int 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 IllegalArgumentException("illegal cond");
        }
    }

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

    private static BigInteger readBig() {
        try {
            return new BigInteger(
                    new LineNumberReader(
                            new InputStreamReader(System.in)).readLine());
        } catch (IOException e) {
            throw new IllegalArgumentException("read aborted");
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("read aborted");
        }
    }

    /******************************************************************/
    /* π-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.
     */
    private static boolean test_ir_piwam_new(Object[] args) {
        PiWam pi = new PiWam();
        Object alpha = Machine.exec_build(args[0]);
        pi.code = list_ints(alpha);
        Object beta = Machine.exec_build(args[1]);
        pi.state = list_ints(beta);
        return Machine.exec_unify(args[2], pi);
    }

    private static int[] list_ints(Object obj) {
        Object peek = obj;
        int i = 0;
        while (Store.is_structure(peek) &&
                ".".equals(((Store.Structure) peek).functor) &&
                ((Store.Structure) peek).args.length == 2 &&
                i < special.MAX_ARITY) {
            i++;
            peek = Store.deref(((Store.Structure) peek).args[1]);
        }
        special.check_nil(peek);
        int[] args = new int[i];
        peek = obj;
        i = 0;
        while (Store.is_structure(peek) &&
                    ".".equals(((Store.Structure) peek).functor) &&
                ((Store.Structure) peek).args.length == 2) {
            Object val = Store.deref(((Store.Structure) peek).args[0]);
            special.check_integer(val);
            if (val instanceof Integer) {
                args[i++] = ((Integer)val).intValue();
            } else {
                args[i++] = ((BigInteger)val).intValue();
            }
            peek = Store.deref(((Store.Structure) 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.
     */
    private static boolean test_ir_piwam_run(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        run((PiWam)alpha);
        return false;
    }

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

    public static void main() {
        Store.set("ir_piwam_new", 3, special.make_check(piwamlib::test_ir_piwam_new));
        Store.set("ir_piwam_run", 1, special.make_check(piwamlib::test_ir_piwam_run));
    }

}
