package doglet.misc;

import nova.Handler;
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;
import java.util.ArrayList;
import java.util.StringTokenizer;

/**
 * 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 final class hacklib {
    private static final int MAX_TILT = 64;

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

    private static final class PiWarp extends Thread {
        private PiMap map;
        private int from;
        private int to;
        private int id;
        private int tilt;
        private boolean cmd;
        private int[] buf;
        private Thread thread;
    }

    private static void run(PiWarp warp) {
        for (int 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) {
            warp.buf = read_ints();
            run_warp(warp, warp.from, warp.to);
        }
    }

    private static void run_warp(PiWarp warp, int from, int to) {
        boolean active = true;
        while (active) {
            active = false;
            for (int i = (warp.cmd ? warp.id : from); i < to; i++) {
                if (run_id(warp, i)) {
                    if (warp.cmd) {
                        warp.id = i;
                        return;
                    } else {
                        active = true;
                    }
                }
            }
        }
    }

    private static boolean run_id(PiWarp warp, int id) {
        PiMap pi = warp.map;
        int offset = pi.offset[id];
        int pc = pi.state[offset-2];
        int accu = pi.state[offset-1];
        for (int k = (warp.cmd ? warp.tilt : 0); k < MAX_TILT; k++) {
            if (pc < pi.code.length) {
                int instr = pi.code[pc];
                int 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;
    }

    private static int run_get(PiWarp warp, PiMap pi, int instr, int offset) {
        int act = instr>>20;
        int 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 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 5: /* zdiv */
                return value / accu;
            case 6: /* rem */
                return value % accu;
            default:
                throw new IllegalArgumentException("illegal fun");
        }
    }

    private static void run_set(PiMap pi, int instr, int accu, int offset) {
        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[offset+obj] = accu;
                return;
            case 4: /* out */
                write_ints(load_ints(obj, pi.state, offset));
                return;
            case 5: /* in */
                return;
            case 6: /* tbl */
                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 15: /* true */
                return rel;
            default:
                throw new IllegalArgumentException("illegal cond");
        }
    }

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

    private static int[] read_ints() {
        try {
            System.out.print(": ");
            System.out.flush();
            StringTokenizer st = new StringTokenizer(new LineNumberReader(
                            new InputStreamReader(System.in)).readLine());
            ArrayList list = new ArrayList();
            while (st.hasMoreTokens())
                list.add(new BigInteger(st.nextToken()));
            int[] res = new int[list.size()];
            for (int i = 0; i < list.size(); i++)
                res[i] = ((BigInteger)list.get(i)).intValue();
            return res;
        } catch (IOException e) {
            throw new IllegalArgumentException("read aborted");
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("read aborted");
        }
    }

    private static void write_ints(int[] res) {
        for (int i = 0; i < res.length; i++) {
            System.out.print(res[i]);
            System.out.print(" ");
        }
        System.out.println();
    }

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

    private static void store_ints(int[] res, int max,
                                   int[] state, int offset) {
        for (int i = 0; i < max; i++) {
            if (i < res.length) {
                state[offset + i] = res[i];
            } else {
                state[offset + i] = 0;
            }
        }
    }

    private static int[] load_ints(int max, int[] state, int offset) {
        int[] res = new int[max];
        for (int i = 0; i < max; i++)
            res[i] = state[offset + i];
        return res;
    }

    private static void extra_outline(PiWarp warp, PiMap pi,
                                      int obj, int offset) {
        if (warp.cmd) {
            store_ints(warp.buf, obj, pi.state, offset);
            warp.buf = null;
            warp.cmd = false;
        } else {
            warp.cmd = true;
        }
    }

    /******************************************************************/
    /* π-WAM Buffers                                                  */
    /******************************************************************/

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

    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) {
            Number val = (Number)Store.deref(((Store.Structure) peek).args[0]);
            args[i++] = val.intValue();
            peek = Store.deref(((Store.Structure) peek).args[1]);
        }
        return args;
    }

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

    private static Object ints_list(int[] alpha, int off, int count) {
        Object res = "[]";
        for (int i = off + count - 1; i >= off; i--)
            res = new Store.Compound(".", new Object[]{Integer.valueOf(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.
     */
    private static boolean test_ir_map_new(Object[] args) {
        PiMap res = new PiMap();
        res.code = (int[])Machine.exec_build(args[0]);
        res.offset = (int[])Machine.exec_build(args[1]);
        res.state = (int[])Machine.exec_build(args[2]);
        return Machine.exec_unify(args[3], res);
    }

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


    /**
     * test_ir_warp_new(M, W): internal only
     * The predicate succeeds in W with a new
     * warp for the π-WAM M.
     */
    private static boolean test_ir_warp_new(Object[] args) {
        PiMap alpha = (PiMap)Machine.exec_build(args[0]);
        PiWarp res = new PiWarp();
        res.map = alpha;
        res.thread = Thread.ofVirtual().unstarted(() -> run(res));
        return Machine.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.
     */
    private static boolean test_ir_warp_start(Object[] args) {
        PiWarp alpha = (PiWarp)Machine.exec_build(args[0]);
        Number beta = (Number)Machine.exec_build(args[1]);
        Number gamma = (Number)Machine.exec_build(args[2]);
        int at = beta.intValue();
        int len = Math.min(alpha.map.offset.length - at, gamma.intValue());
        alpha.from = at;
        alpha.to = at+len;
        alpha.thread.start();
        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.
     */
    private static boolean test_ir_warp_join(Object[] args) {
        PiWarp alpha = (PiWarp)Machine.exec_build(args[0]);
        Object buf = Machine.ctx;
        return Machine.exec_unify(args[1], join_promise(buf, alpha.thread));
    }

    private static Handler.Promise join_promise(Object buf, Thread alpha) {
        return new Handler.Promise(() -> {
            Thread self = Thread.currentThread();
            Machine.register_interrupt(buf, self::interrupt);
            try {
                alpha.join();
            } catch (InterruptedException x) {
                /* */
            } finally {
                Machine.register_interrupt(buf, () -> {});
            }
        });
    }

    /**
     * test_ir_warp_free(W): internal only
     * The predicate succeeds. As a side effect it frees the warp W.
     */
    private static boolean test_ir_warp_free(Object[] args) {
        return true;
    }

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

    public static void main() {
        Store.set("ir_buf_new", 2, special.make_check(hacklib::test_ir_buf_new));
        Store.set("ir_buf_get", 2, special.make_check(hacklib::test_ir_buf_get));
        Store.set("ir_map_new", 4, special.make_check(hacklib::test_ir_map_new));
        Store.set("ir_warp_new", 2, special.make_check(hacklib::test_ir_warp_new));
        Store.set("ir_warp_start", 3, special.make_check(hacklib::test_ir_warp_start));
        Store.set("ir_warp_join", 2, special.make_check(hacklib::test_ir_warp_join));
        Store.set("ir_warp_free", 1, special.make_check(hacklib::test_ir_warp_free));
    }

}
