package nova;

import java.io.*;
import java.math.*;
import java.net.*;
import java.nio.channels.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 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 runtime {
    private static final int MAX_BUF = 4096;

    public static String bootbase = "";
    public static String codebase = System.getProperty("user.dir") + File.separatorChar;

    /**
     * Set the boot base.
     *
     * @param url The boot base.
     */
    public static void set_bootbase(String url) {
        bootbase = url;
    }

    /**
     * Set the code base.
     *
     * @param url The code base.
     */
    public static void set_codebase(String url) {
        codebase = url;
    }

    /**
     * Resolve a child path against a parent.
     *
     * @param parent The parent path.
     * @param child  The child path.
     * @return The result.
     */
    public static File findWrite(String parent, String child) {
        File res = new File(child);
        if (!res.isAbsolute())
            res = new File(parent, child);
        return res;
    }

    /**************************************************************/
    /* current_output/1 and current_error/1                       */
    /**************************************************************/

    public static final int MASK_DST_COLR = 0x00000004;
    public static final int MASK_DST_CYCL = 0x00000008;
    public static final int MASK_DST_FEAT = MASK_DST_COLR | MASK_DST_CYCL;

    public static final int MASK_DST_LINE = 0x00000010;

    public static final class Sink {
        public StringBuilder buf;
        public BiFunction<Object,String,Object> send;
        public int last;
        public int flags;
        public Consumer notify;
        public Consumer release;
        public Object data;
        public int indent;
        public int offset;

        /**
         * Create a text output.
         */
        public Sink() {
            this.buf = null;
            this.send = (fd, buf) -> fd;
            this.last = -1;
            this.flags = 0;
            this.notify = (fd) -> { };
            this.release = (fd) -> { };
            this.data = Store.UNDEF_OBJ;
            this.indent = 0;
            this.offset = 0;
        }

    }

    /**
     * current_output(S): [ISO 8.11.2]
     * The built-in succeeds in S with the current output.
     */
    private static boolean test_current_output(Object[] args) {
        Object alpha = Store.engine.text_output;
        return Machine.exec_unify(args[0], alpha);
    }

    /**
     * current_error(S):
     * The built-in succeeds in S with the current error.
     */
    private static boolean test_current_error(Object[] args) {
        Object alpha = Store.engine.text_error;
        return Machine.exec_unify(args[0], alpha);
    }

    /**************************************************************/
    /* set_output/1 and set_error/1                               */
    /**************************************************************/

    /**
     * set_output(S): [ISO 8.11.4]
     * The built-in succeeds. As a side effect the current output is set to S.
     */
    private static boolean test_set_output(Object[] args) {
        Object obj = Machine.exec_build(args[0]);
        check_sink(obj);
        Store.engine.text_output = (Sink) obj;
        return true;
    }

    /**
     * set_error(S):
     * The built-in succeeds. As a side effect the current error is set to S.
     */
    private static boolean test_set_error(Object[] args) {
        Object obj = Machine.exec_build(args[0]);
        check_sink(obj);
        Store.engine.text_error = (Sink) obj;
        return true;
    }

    /**
     * Assure that the object is a sink.
     *
     * @param beta The object.
     */
    public static void check_sink(Object beta) {
        if (!(beta instanceof Sink)) {
            Store.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"writer", beta}));
        }
    }

    /**************************************************************/
    /* put_atom/2                                                 */
    /**************************************************************/

    /**
     * put_atom(S, A):
     * The built-in succeeds. As a side effect, it adds
     * the atom to the stream S.
     */
    private static boolean test_put_atom(Object[] args) {
        Object stream = Machine.exec_build(args[0]);
        check_sink(stream);
        Object text = Machine.exec_build(args[1]);
        special.check_atom(text);
        put_atom((Sink) stream, (String) text);
        return true;
    }

    public static void put_atom(Sink stream, String text) {
        if (text.length() > 0) {
            int pos = text.lastIndexOf('\n');
            if (pos != -1) {
                stream.offset = text.codePointCount(pos+1, text.length());
            } else {
                stream.offset += text.codePointCount(0, text.length());
            }
            stream.last = text.codePointBefore(text.length());
            if (stream.buf != null) {
                stream.buf.append(text);
                if (stream.buf.length() >= MAX_BUF)
                    flush_buffer(stream);
            } else {
                stream.data = stream.send.apply(stream.data, text);
            }
        }
    }

    private static void flush_buffer(Sink stream) {
        if (stream.buf != null && stream.buf.length() > 0) {
            String text = stream.buf.toString();
            stream.buf = new StringBuilder();
            stream.data = stream.send.apply(stream.data, text);
        }
    }

    /**************************************************************/
    /* current_input/1 and set_input/1                            */
    /**************************************************************/

    public static final int MASK_SRC_SKIP = 0x00000001;
    public static final int MASK_SRC_AREAD = 0x00000002;

    public static final class Source {
        public String buf;
        public int pos;
        public Object receive;
        public int flags;
        public int lineno;
        public Object release;
        public Object data;

        /**
         * Create a text input.
         */
        public Source() {
            this.buf = "";
            this.pos = 0;
            this.receive = (Function) ((fd) -> "");
            this.flags = 0;
            this.lineno = 0;
            this.release = (Consumer) ((fd) -> {});
            this.data = Store.UNDEF_OBJ;
        }

    }

    /**
     * current_input(S): [ISO 8.11.1]
     * The built-in succeeds in S with the current input.
     */
    private static boolean test_current_input(Object[] args) {
        Object alpha = Store.engine.text_input;
        return Machine.exec_unify(args[0], alpha);
    }

    /**
     * set_input(S): [ISO 8.11.3]
     * The built-in succeeds. As a side effect it sets the current input to S.
     */
    private static boolean test_set_input(Object[] args) {
        Object obj = Machine.exec_build(args[0]);
        check_source(obj);
        Store.engine.text_input = (Source) obj;
        return true;
    }

    /**
     * Assure that the object is a source.
     *
     * @param beta The object.
     */
    private static void check_source(Object beta) {
        if (!(beta instanceof Source)) {
            Store.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"reader", beta}));
        }
    }

    /*******************************************************************/
    /* os_get_code/2 and os_peek_code/2                                */
    /*******************************************************************/

    /**
     * os_get_code(S, C):
     * The predicate succeeds in C with the Unicode point from the stream buffer S.
     * As a side effect the stream position is advanced.
     */
    private static boolean test_os_get_code(Object[] args) {
        Source stream = (Source) Machine.exec_build(args[0]);
        int pos = stream.pos;
        String buf = stream.buf;
        if (pos < buf.length()) {
            int ch = buf.codePointAt(pos);
            pos += Character.charCount(ch);
            if (ch == 13 || (ch == 10 && (stream.flags & MASK_SRC_SKIP) == 0))
                stream.lineno++;
            if (ch == 13) {
                stream.flags |= MASK_SRC_SKIP;
            } else {
                stream.flags &= ~MASK_SRC_SKIP;
            }
            stream.pos = pos;
            return Machine.exec_unify(args[1], Integer.valueOf(ch));
        } else {
            return false;
        }
    }

    /**
     * os_peek_code(S, C):
     * The built-in succeeds in C with the Unicode point from the stream buffer S.
     */
    private static boolean test_os_peek_code(Object[] args) {
        Source stream = (Source) Machine.exec_build(args[0]);
        int pos = stream.pos;
        String buf = stream.buf;
        if (pos < buf.length()) {
            int ch = buf.codePointAt(pos);
            return Machine.exec_unify(args[1], Integer.valueOf(ch));
        } else {
            return false;
        }
    }

    /****************************************************************/
    /* os_open_promise_opts/4                                       */
    /****************************************************************/

    /**
     * os_open_promise_opts(P, L, S, Q):
     * The predicate succeeds in Q with a promise for open input S
     * on path P and option list L.
     */
    private static boolean test_os_open_promise(Object[] args) {
        Object obj = Machine.exec_build(args[0]);
        special.check_atom(obj);
        String url = (String) obj;
        Map opts = (Map) Machine.exec_build(args[1]);
        Source stream = new Source();
        if (!Machine.exec_unify(args[2], stream))
            return false;
        Object buf = Machine.ctx;
        Handler.Promise prom = open_file_promise_opts(buf, stream, url, opts);
        return Machine.exec_unify(args[3], prom);
    }

    public static Charset get_encoding(Map opts) {
        if (opts != null && opts.get("encoding") != null) {
            String encoding = opts.get("encoding").toString();
            return Charset.forName(encoding);
        } else {
            return StandardCharsets.UTF_8;
        }
    }

    private static Handler.Promise open_file_promise_opts(Object buf,
                Source stream, String url, Map opts) {
        return new Handler.Promise(() -> {
            File file = findWrite(codebase, url);
            try {
                InputStream ins = new FileInputStream(file);
                stream.data = new InputStreamReader(ins, get_encoding(opts));
                stream.receive = (BiFunction<Object, runtime.Source, Handler.Promise>) runtime::file_read_promise;
                stream.release = (BiFunction<Object, runtime.Source, Handler.Promise>) runtime::file_close_promise;
                stream.flags |= MASK_SRC_AREAD;
            } catch (IOException err) {
                Machine.register_signal(buf, map_file_error(err, url));
            }
        });
    }

    public static Object map_file_error(Exception err, String url) {
        if (err instanceof InterruptedException) {
            return new Store.Compound("resource_error",
                    new Object[]{"interrupted_exception"});
        } else if (err instanceof FileNotFoundException ||
              err instanceof NoSuchFileException) {
            return new Store.Compound("existence_error",
                    new Object[]{"source_sink", url});
        } else if (err instanceof ConnectException) {
            Throwable cause = err.getCause();
            if (cause != null && cause.getCause() instanceof UnresolvedAddressException) {
                return new Store.Compound("resource_error", new Object[]{"unknown_host"});
            } else {
                return new Store.Compound("resource_error", new Object[]{"connect_failed"});
            }
        } else {
            return new Store.Compound("resource_error", new Object[]{"remote_error"});
        }
    }

    /*******************************************************************/
    /* os_read_promise/2 and os_close_promise/2                        */
    /*******************************************************************/

    /**
     * os_read_promise(S, P):
     * The predicate suceeds in P with a read promise for a input S.
     */
    private static boolean test_os_read_promise(Object[] args) {
        Source stream = (Source) Machine.exec_build(args[0]);
        Object buf = Machine.ctx;
        return Machine.exec_unify(args[1], ((BiFunction) stream.receive).apply(buf, stream));
    }

    public static Handler.Promise file_read_promise(Object buf, Source stream) {
        return new Handler.Promise(() -> {
            try {
                char[] cbuf = new char[8192];
                int len = ((Reader)stream.data).read(cbuf, 0, cbuf.length);
                stream.buf = (len > 0 ? new String(cbuf, 0, len) : "");
                stream.pos = 0;
            } catch (IOException err) {
                Machine.register_signal(buf, map_stream_error(err));
            }
        });
    }

    /**
     * Map a stream error when in transit.
     *
     * @param err The offending error.
     * @return The Prolog error term.
     */
    public static Object map_stream_error(Exception err) {
        if (err instanceof SocketTimeoutException) {
            return new Store.Compound("resource_error",
                    new Object[]{"socket_timeout"});
        } else if (err instanceof BindException) {
            return new Store.Compound("resource_error",
                    new Object[]{"port_error"});
        } else if (err instanceof SocketException) {
            return new Store.Compound("resource_error",
                    new Object[]{"remote_error"});
        } else {
            return new Store.Compound("resource_error",
                    new Object[]{"io_exception"});
        }
    }

    /**
     * os_close_promise(S, P):
     * The predicate suceeds in P with a read promise for a input S.
     */
    private static boolean test_os_close_promise(Object[] args) {
        Source stream = (Source) Machine.exec_build(args[0]);
        Object buf = Machine.ctx;
        return Machine.exec_unify(args[1], ((BiFunction) stream.release).apply(buf, stream));
    }

    public static Handler.Promise file_close_promise(Object buf, Source stream) {
        return new Handler.Promise(() -> {
            try {
                ((Closeable) stream.data).close();
            } catch (IOException err) {
                Machine.register_signal(buf, map_stream_error(err));
            }
        });
    }

    /*******************************************************************/
    /* os_prop_promise/3                                               */
    /*******************************************************************/

    /**
     * os_prop_promise(F, M, Q):
     * The predicate succeeds in Q with with a promise for the
     * properties M of the file F. Barks if path F doesn't exist
     * or io exception while resolving.
     */
    private static boolean test_os_prop_promise(Object[] args) {
        Object obj = Machine.exec_build(args[0]);
        special.check_atom(obj);
        String url = (String) obj;
        Map res = new HashMap();
        if (!Machine.exec_unify(args[1], res))
            return false;
        Object buf = Machine.ctx;
        Handler.Promise prom = prop_file_promise(buf, url, res);
        return Machine.exec_unify(args[2], prom);
    }

    private static Handler.Promise prop_file_promise(Object buf, String url, Map res) {
        return new Handler.Promise(() -> {
            File file = findWrite(codebase, url);
            try {
                if (!file.exists())
                    throw new NoSuchFileException("");
                String ftype = (file.isFile() ? "regular" :
                        (file.isDirectory() ? "directory" : "other"));

                res.put("last_modified", special.norm_smallint(file.lastModified()));
                res.put("absolute_path", (ftype.equals("directory") ?
                        file.getPath() + File.separatorChar : file.getPath()));
                res.put("type", ftype);
                res.put("can_write", (file.canWrite() ? "true" : "false"));
            } catch (IOException err) {
                Machine.register_signal(buf, map_file_error(err, url));
            }
        });
    }

    /**************************************************************/
    /* os_open_sync_opts/4                                        */
    /**************************************************************/

    /**
     * os_open_sync_opts(P, M, L, S):
     * The predicate succeeds. As a side effect the stream S is
     * opened on the path P with the mode M and the option list L.
     */
    private static boolean test_os_open_sync(Object[] args) {
        Object obj = Machine.exec_build(args[0]);
        special.check_atom(obj);
        String url = (String) obj;
        Object mode = Machine.exec_build(args[1]);
        special.check_atom(mode);
        Map opts = (Map) Machine.exec_build(args[2]);
        Object stream;
        if ("read".equals(mode)) {
            throw Machine.make_error(new Store.Compound("resource_error",
                    new Object[]{"not_supported"}));
        } else if ("write".equals(mode)) {
            stream = open_write(url, false, opts);
        } else if ("append".equals(mode)) {
            stream = open_write(url, true, opts);
        } else {
            throw Machine.make_error(new Store.Compound("domain_error",
                    new Object[]{"io_mode", mode}));
        }
        return Machine.exec_unify(args[3], stream);
    }

    public static Object open_write(String url, boolean append, Map opts) {
        try {
            File file = findWrite(codebase, url);
            OutputStream outs = new FileOutputStream(file, append);
            Sink dst = new Sink();
            dst.buf = new StringBuilder();
            dst.data = new OutputStreamWriter(outs, get_encoding(opts));
            dst.send = runtime::file_write;
            dst.release = runtime::file_close;
            dst.notify = runtime::file_flush;
            return dst;
        } catch (IOException err) {
            throw Machine.make_error(map_file_error(err, url));
        }
    }

    public static Object file_write(Object data, String buf) {
        try {
            int len = buf.length();
            int pos = 0;
            char[] cbuf = null;
            while (len > 0) {
                int span = Math.min(len, 8192);
                if (cbuf == null)
                    cbuf = new char[span];
                buf.getChars(pos, span, cbuf, 0);
                ((Writer) data).write(cbuf, 0, span);
                pos += span;
                len -= span;
            }
            return data;
        } catch (IOException err) {
            throw Machine.make_error(map_stream_error(err));
        }
    }

    public static void file_flush(Object data) {
        try {
            ((Flushable) data).flush();
        } catch (IOException err) {
            throw Machine.make_error(map_stream_error(err));
        }
    }

    public static void file_close(Object data) {
        try {
            ((Closeable) data).close();
        } catch (IOException err) {
            throw Machine.make_error(map_stream_error(err));
        }
    }

    /**************************************************************/
    /* flush_output/1 and os_close_sync/1                         */
    /**************************************************************/

    /**
     * flush_output(S): [ISO 8.11.7]
     * The built-in succeeds. As a side effect, it flushes the stream S buffer.
     */
    private static boolean test_flush_output(Object[] args) {
        Object stream = Machine.exec_build(args[0]);
        check_sink(stream);
        flush(stream);
        return true;
    }

    public static void flush(Object obj) {
        Sink stream = (Sink)obj;
        flush_buffer(stream);
        stream.notify.accept(stream.data);
    }

    /**
     * close(S): [ISO 8.11.6]
     * The built-in succeeds. As a side effect, the stream S is closed.
     */
    private static boolean test_os_close_sync(Object[] args) {
        Object stream = Machine.exec_build(args[0]);
        if (stream instanceof Sink) {
            /* */
        } else {
            check_source(stream);
        }
        close(stream);
        return true;
    }

    public static void close(Object stream) {
        if (stream instanceof Sink) {
            flush_buffer((Sink) stream);
            ((Sink) stream).release.accept(((Sink) stream).data);
        } else {
            ((Consumer)((Source) stream).release).accept(((Source) stream).data);
        }
    }

    /*******************************************************************/
    /* os_cntrl_sync/2 and os_read_sync/1                              */
    /*******************************************************************/

    /**
     * os_cntrl_sync(F, P):
     * The predicate assigns the property P to the file F.
     */
    private static boolean test_os_cntrl_sync(Object[] args) {
        Object obj = Machine.exec_build(args[0]);
        special.check_atom(obj);
        String url = (String)obj;
        File file = findWrite(codebase, url);
        Object prop = Machine.exec_build(args[1]);
        if (!file.exists())
            throw Machine.make_error(new Store.Compound("existence_error",
                    new Object[]{"source_sink", url}));
        if (Store.is_structure(prop) &&
                ((Store.Structure) prop).functor.equals("last_modified") &&
                ((Store.Structure) prop).args.length == 1) {
            Object val = Store.deref(((Store.Structure) prop).args[0]);
            special.check_integer(val);
            if (eval.integer_signum((Number) val) < 0)
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"not_less_than_zero", val}));
            if (!is_long((Number) val))
                throw Machine.make_error(new Store.Compound("representation_error",
                        new Object[]{"long", val}));
            if (!file.setLastModified(((Number) val).longValue())) {
                throw Machine.make_error(new Store.Compound("resource_error",
                        new Object[]{"io_exception"}));
            }
        } else if (Store.is_structure(prop) &&
                ((Store.Structure) prop).functor.equals("can_write") &&
                ((Store.Structure) prop).args.length == 1) {
            Object val = Store.deref(((Store.Structure) prop).args[0]);
            special.check_atom(val);
            if (!"true".equals(val) && !"false".equals(val))
                throw Machine.make_error(new Store.Compound("domain_error",
                        new Object[]{"boolean", val}));
            if (!file.setWritable("true".equals(val))) {
                throw Machine.make_error(new Store.Compound("resource_error",
                        new Object[]{"io_exception"}));
            }
        } else {
            Store.check_nonvar(prop);
            prop = Store.copy_term(prop);
            throw Machine.make_error(new Store.Compound("domain_error",
                    new Object[]{"file_property", prop}));
        }
        return true;
    }

    private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
    private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);

    public static boolean is_long(Number num) {
        if (num instanceof Integer) {
            return true;
        } else {
            return MIN_LONG.compareTo((BigInteger) num) <= 0 &&
                    ((BigInteger) num).compareTo(MAX_LONG) <= 0;
        }
    }

    /**
     * os_read_sync(S):
     * The predicate succeeds. As a side effect the stream buffer is read.
     */
    private static boolean test_os_read_sync(Object[] args) {
        Source stream = (Source) Machine.exec_build(args[0]);
        stream.buf = ((Function<Object, String>) stream.receive).apply(stream.data);
        stream.pos = 0;
        return true;
    }

    /**************************************************************/
    /* ir_site_new/2, ir_is_site/1 and ir_site_name/2            */
    /**************************************************************/

    /**
     * ir_site_new(X, Y): internal only
     * The built-in succeeds in Y with a cacheable X.
     */
    private static boolean test_ir_site_new(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        if (Store.is_structure(alpha)) {
            if (Machine.is_atom(((Store.Structure) alpha).functor))
                alpha = new Store.Compound(new Store.Cache((String)
                        ((Store.Structure) alpha).functor),
                        ((Store.Structure) alpha).args);
        } else if (Machine.is_atom(alpha)) {
            alpha = new Store.Cache((String) alpha);
        }
        return Machine.exec_unify(args[1], alpha);
    }

    /**
     * ir_is_site(X): internal only
     * The built-in succeeds if X is cacheable.
     */
    private static boolean test_ir_is_site(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        if (Store.is_structure(alpha))
            return Store.is_cache(((Store.Structure) alpha).functor);
        return Store.is_cache(alpha);
    }

    /**
     * ir_site_name(X, Y): internal only
     * The built-in succeeds in Y with a cachefree X.
     */
    private static boolean test_ir_site_name(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        if (Store.is_structure(alpha)) {
            if (Store.is_cache(((Store.Structure) alpha).functor))
                alpha = new Store.Compound(((Store.Cache) ((Store.Structure)
                        alpha).functor).name, ((Store.Structure) alpha).args);
        } else if (Store.is_cache(alpha)) {
            alpha = ((Store.Cache) alpha).name;
        }
        return Machine.exec_unify(args[1], alpha);
    }

    /**************************************************************/
    /* ir_link_new/2 and ir_is_link/1                             */
    /**************************************************************/

    /**
     * ir_link_new(L, P): internal only
     * The built-in succeeds in P with an anonymous predicate
     * for the clauses L.
     */
    private static boolean test_ir_link_new(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Object[] rope = special.list_objects(alpha);
        return Machine.exec_unify(args[1], Store.make_defined(rope));
    }

    /**
     * ir_is_link(Q): internal only
     * The built-in succeeds if Q is a provable.
     */
    private static boolean test_ir_is_link(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        if (Store.is_structure(alpha))
            return Store.is_provable(((Store.Structure) alpha).functor);
        return Store.is_provable(alpha);
    }
    
    /**************************************************************/
    /* kb_clause_ref/3 and kb_pred_touch/3                        */
    /**************************************************************/

    private static final int MASK_FIND_MODIFY = 0x00000001;
    private static final int MASK_FIND_DYNAMIC = 0x00000002;
    private static final int MASK_FIND_REVERSE = 0x00000004;
    private static final int MASK_FIND_SIGNAL = 0x00000008;

    /**
     * kb_clause_ref(H, F, C): internal only
     * The built-in succeeds in C with the clause references
     * for the head H and the flags F.
     */
    private static Object special_kb_clause_ref(Object[] args) {
        Object head = Store.deref(args[0]);
        special.check_callable(head);
        Object beta = Store.deref(args[1]);
        special.check_integer(beta);
        int flags = ((Number) beta).intValue();
        Store.Provable peek = Machine.lookup_pred(head);
        if (peek == null) {
            if ((flags & MASK_FIND_SIGNAL) != 0) {
                throw Machine.make_error(new Store.Compound("existence_error",
                        new Object[]{"procedure", Machine.make_indicator_term(head)}));
            } else {
                return false;
            }
        }
        if ((flags & MASK_FIND_DYNAMIC) != 0) {
            if ((peek.flags & Store.MASK_PRED_DYNAMIC) == 0) {
                make_error_find(head, flags);
            }
        }
        if (!Store.is_logical(peek.func) && !Store.is_stick(peek.func)) {
            if ((flags & MASK_FIND_SIGNAL) != 0) {
                make_error_find(head, flags);
            } else {
                return false;
            }
        }
        Object[] temp;
        if (Store.is_structure(head)) {
            temp = ((Store.Structure) head).args;
        } else {
            temp = Machine.VOID_ARGS;
        }
        Store.Logical rope = Store.defined_pred(peek, temp);
        Store.Clause[] data = Store.snapshot_rope(rope);
        if ((flags & MASK_FIND_REVERSE) == 0) {
            return solve2_ref(args, data, 0, null);
        } else {
            return solve2_ref_reverse(args, data, data.length, null);
        }
    }

    private static void make_error_find(Object head, int flags) {
        if ((flags & MASK_FIND_MODIFY) != 0) {
            throw Machine.make_error(new Store.Compound("permission_error",
                    new Object[]{"modify", "static_procedure", Machine.make_indicator_term(head)}));
        } else {
            throw Machine.make_error(new Store.Compound("permission_error",
                    new Object[]{"access", "private_procedure", Machine.make_indicator_term(head)}));
        }
    }

    private static Object solve_ref(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_ref(((Store.Structure) goal).args, rope, at, choice);
    }

    private static Object solve_ref_reverse(Object rope, int at, Machine.Choice choice) {
        Object goal = Store.deref(((Store.Structure) Machine.call).args[0]);
        return solve2_ref_reverse(((Store.Structure) goal).args, rope, at, choice);
    }

    /**
     * Search a Prolog clause and return it.
     *
     * @param args   The current arguments.
     * @param data   The clause list.
     * @param at     The clause index.
     * @param choice The choice point for reuse or null.
     * @return boolean True if search succeeds, otherwise false.
     */
    private static Object solve2_ref(Object[] args, Object data, int at, Machine.Choice choice) {
        Store.Clause[] rope = (Store.Clause[]) data;
        Store.Variable mark = Machine.trail;
        while (at < rope.length) {
            Store.Clause clause = rope[at++];
            if (Machine.unify(args[2], clause)) {
                if (at < rope.length) {
                    if (choice == null) {
                        choice = new Machine.Choice(runtime::solve_ref, rope, at, mark);
                    } else {
                        choice.at = at;
                    }
                    Machine.more(choice);
                }
                Machine.cont(((Store.Structure) Machine.call).args[1]);
                return Boolean.TRUE;
            }
            Machine.unbind(mark);
        }
        return Boolean.FALSE;
    }

    /**
     * Search a Prolog clause backwards and return it.
     *
     * @param args   The current arguments.
     * @param data   The clause list.
     * @param at     The clause index.
     * @param choice The choice point for reuse or null.
     * @return boolean True if search succeeds, otherwise false.
     */
    private static Object solve2_ref_reverse(Object[] args, Object data, int at, Machine.Choice choice) {
        Store.Clause[] rope = (Store.Clause[]) data;
        Store.Variable mark = Machine.trail;
        while (at > 0) {
            Store.Clause clause = rope[--at];
            if (Machine.unify(args[2], clause)) {
                if (at > 0) {
                    if (choice == null) {
                        choice = new Machine.Choice(runtime::solve_ref_reverse, rope, at, mark);
                    } else {
                        choice.at = at;
                    }
                    Machine.more(choice);
                }
                Machine.cont(((Store.Structure) Machine.call).args[1]);
                return Boolean.TRUE;
            }
            Machine.unbind(mark);
        }
        return Boolean.FALSE;
    }

    /**
     * kb_pred_touch(F, N, O): internal only
     * The built-in succeeds. As a side effect the predicate
     * indicator F/N with options O is touched.
     */
    private static boolean test_kb_pred_touch(Object[] args) {
        Object functor = Machine.exec_build(args[0]);
        special.check_atom(functor);
        Object beta = Machine.exec_build(args[1]);
        special.check_integer(beta);
        Object gamma = Machine.exec_build(args[2]);
        special.check_integer(gamma);
        Store.pred_touch((String) functor, ((Number) beta).intValue(),
                ((Number) gamma).intValue());
        return true;
    }

    /**************************************************************/
    /* kb_clause_remove/2 and kb_pred_destroy/2                   */
    /**************************************************************/

    /**
     * kb_clause_remove(C, O): internal only
     * The built-in succeeds if the clause C could
     * be removed according to the options O.
     */
    private static boolean test_kb_clause_remove(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        special.check_clause(alpha);
        Object beta = Machine.exec_build(args[1]);
        special.check_integer(beta);
        return Store.remove_clause((Store.Clause) alpha, ((Integer) beta).intValue());
    }

    /**
     * kb_pred_destroy(F, N): internal only
     * The built-in succeeds. As a side effect the
     * predicate indicator F/N is destroyed.
     */
    private static boolean test_kb_pred_destroy(Object[] args) {
        Object functor = Machine.exec_build(args[0]);
        special.check_atom(functor);
        Object beta = Machine.exec_build(args[1]);
        special.check_integer(beta);
        int arity = ((Number) beta).intValue();
        Store.pred_destroy((String) functor, arity);
        return true;
    }

    /**************************************************************/
    /* kb_pred_link/3, kb_link_flags/2 and kb_pred_list/1         */
    /**************************************************************/

    /**
     * kb_pred_link(F, A, Q): internal only
     * The built-in succeeds in Q with the provable of the
     * predicate indicator F/A. Otherwise if no such provable
     * exists the built-in fails.
     */
    private static boolean test_kb_pred_link(Object[] args) {
        Object functor = Machine.exec_build(args[0]);
        special.check_atom(functor);
        Object beta = Machine.exec_build(args[1]);
        special.check_integer(beta);
        int arity = ((Number) beta).intValue();
        Store.Provable peek = Store.pred_link((String) functor, arity);
        if (peek == null)
            return false;
        return Machine.exec_unify(args[2], peek);
    }

    /**
     * kb_link_flags(Q, F): internal only
     * The built-in succeeds in F with the flags of the provable Q.
     */
    private static boolean test_kb_link_flags(Object[] args) {
        Object peek = Machine.exec_build(args[0]);
        check_provable(peek);
        int res = ((Store.Provable) peek).flags;
        return Machine.exec_unify(args[1], Integer.valueOf(res));
    }

    /**
     * Assure that the object is a provable.
     *
     * @param beta The object.
     */
    public static void check_provable(Object beta) {
        if (!Store.is_provable(beta)) {
            Store.check_nonvar(beta);
            beta = Store.copy_term(beta);
            throw Machine.make_error(new Store.Compound("type_error",
                    new Object[]{"provable", beta}));
        }
    }

    /**
     * kb_pred_list(L): internal only
     * The built-in succeeds in L with the current predicate indicators.
     */
    private static boolean test_kb_pred_list(Object[] args) {
        Object res = kb_pred_list();
        return Machine.exec_unify(args[0], res);
    }

    private static Object kb_pred_list() {
        Store.Structure back = null;
        Object res = null;
        Iterator it = Store.kb.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            List temp = (List)entry.getValue();
            for (int i = 0; i < temp.size(); i++) {
                Store.Provable peek = (Store.Provable)temp.get(i);
                if (peek == null || peek.remover != Store.UNDEF_INT)
                    continue;
                Store.Structure elem = new Store.Compound(".",
                        new Object[]{Machine.make_indicator(entry.getKey(), i),
                                Store.UNDEF_OBJ});
                if (back == null) {
                    res = elem;
                } else {
                    back.args[1] = elem;
                }
                back = elem;
            }
        }
        if (back == null) {
            res = "[]";
        } else {
            back.args[1] = "[]";
        }
        return res;
    }

    /**************************************************************/
    /* kb_clause_shard/2, kb_clause_head/2 and kb_clause_data/4   */
    /**************************************************************/

    /**
     * kb_clause_shard(C, S): internal only
     * The built-in succeeds in S with the shard of the clause C.
     */
    private static boolean test_kb_clause_shard(Object[] args) {
        Object clause = Machine.exec_build(args[0]);
        special.check_clause(clause);
        return Machine.exec_unify(args[1], ((Store.Clause) clause).shard);
    }

    /**
     * kb_clause_head(C, H): internal only
     * The built-in succeeds in H with the head of the clause C.
     */
    private static Object special_kb_clause_head(Object[] args) {
        Object beta = Store.deref(args[0]);
        special.check_clause(beta);
        Store.Clause clause = (Store.Clause) beta;
        Object head = Store.deref(args[1]);
        Object[] display;
        if (clause.size != 0) {
            display = new Object[clause.size];
        } else {
            display = null;
        }
        if (!Store.is_variable(head)) {
            Object[] paras;
            if (Store.is_structure(head)) {
                paras = ((Store.Structure) head).args;
            } else {
                paras = Machine.VOID_ARGS;
            }
            if (!Machine.exec_head(clause.head, display, paras))
                return Boolean.FALSE;
        } else {
            Object alpha = Machine.exec_frame(clause.functor, clause.head, display);
            Machine.bind(alpha, (Store.Variable)head);
        }
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    /**
     * kb_clause_data(C, H, O, L): internal only
     * The built-in succeeds in H, O and L with the head,
     * the cut var and the compiled body of the clause C.
     */
    private static Object special_kb_clause_data(Object[] args) {
        Object beta = Store.deref(args[0]);
        special.check_clause(beta);
        Store.Clause clause = (Store.Clause) beta;
        Object head = Store.deref(args[1]);
        Object[] display;
        if (clause.size != 0) {
            display = new Object[clause.size];
        } else {
            display = null;
        }
        if (!Store.is_variable(head)) {
            Object[] paras;
            if (Store.is_structure(head)) {
                paras = ((Store.Structure) head).args;
            } else {
                paras = Machine.VOID_ARGS;
            }
            if (!Machine.exec_head(clause.head, display, paras))
                return Boolean.FALSE;
        } else {
            Object alpha = Machine.exec_frame(clause.functor, clause.head, display);
            Machine.bind(alpha, (Store.Variable)head);
        }
        Object temp;
        int peek = clause.cutvar;
        if (peek != -1) {
            temp = new Store.Variable();
            display[peek] = temp;
            temp = new Store.Compound("just", new Object[]{temp});
        } else {
            temp = "nothing";
        }
        if (!Machine.unify(args[2], temp))
            return Boolean.FALSE;
        temp = Machine.exec_body(clause.body, display);
        if (!Machine.unify(args[3], temp))
            return Boolean.FALSE;
        Machine.cont(((Store.Structure) Machine.call).args[1]);
        return Boolean.TRUE;
    }

    /**************************************************************/
    /* Runtime Init                                               */
    /**************************************************************/

    public static void boot() {
        // stream specials, output
        Store.set("current_output", 1, special.make_check(runtime::test_current_output));
        Store.set("current_error", 1, special.make_check(runtime::test_current_error));
        Store.set("set_output", 1, special.make_check(runtime::test_set_output));
        Store.set("set_error", 1, special.make_check(runtime::test_set_error));
        Store.set("put_atom", 2, special.make_check(runtime::test_put_atom));

        // stream specials, input
        Store.set("current_input", 1, special.make_check(runtime::test_current_input));
        Store.set("set_input", 1, special.make_check(runtime::test_set_input));
        Store.set("os_get_code", 2, special.make_check(runtime::test_os_get_code));
        Store.set("os_peek_code", 2, special.make_check(runtime::test_os_peek_code));

        // stream specials, async
        Store.set("os_open_promise", 4, special.make_check(runtime::test_os_open_promise));
        Store.set("os_read_promise", 2, special.make_check(runtime::test_os_read_promise));
        Store.set("os_close_promise", 2, special.make_check(runtime::test_os_close_promise));
        Store.set("os_prop_promise", 3, special.make_check(runtime::test_os_prop_promise));

        // stream specials, sync
        Store.set("os_open_sync", 4, special.make_check(runtime::test_os_open_sync));
        Store.set("flush_output", 1, special.make_check(runtime::test_flush_output));
        Store.set("os_close_sync", 1, special.make_check(runtime::test_os_close_sync));
        Store.set("os_cntrl_sync", 2, special.make_check(runtime::test_os_cntrl_sync));
        Store.set("os_read_sync", 1, special.make_check(runtime::test_os_read_sync));

        // intermediate representation, Albufeira terms
        Store.set("ir_site_new", 2, special.make_check(runtime::test_ir_site_new));
        Store.set("ir_is_site", 1, special.make_check(runtime::test_ir_is_site));
        Store.set("ir_site_name", 2, special.make_check(runtime::test_ir_site_name));
        Store.set("ir_link_new", 2, special.make_check(runtime::test_ir_link_new));
        Store.set("ir_is_link", 1, special.make_check(runtime::test_ir_is_link));

        // knowledge base specials, dynamic database, internal only
        Store.set("kb_clause_ref", 3, special.make_special(runtime::special_kb_clause_ref));
        Store.set("kb_pred_touch", 3, special.make_check(runtime::test_kb_pred_touch));
        Store.set("kb_clause_remove", 2, special.make_check(runtime::test_kb_clause_remove));
        Store.set("kb_pred_destroy", 2, special.make_check(runtime::test_kb_pred_destroy));

        // knowledge base specials, linked provables, internal only
        Store.set("kb_pred_link", 3, special.make_check(runtime::test_kb_pred_link));
        Store.set("kb_link_flags", 2, special.make_check(runtime::test_kb_link_flags));
        Store.set("kb_pred_list", 1, special.make_check(runtime::test_kb_pred_list));

        // knowledge base specials, meta data, internal only
        Store.set("kb_clause_shard", 2, special.make_check(runtime::test_kb_clause_shard));
        Store.set("kb_clause_head", 2, special.make_special(runtime::special_kb_clause_head));
        Store.set("kb_clause_data", 4, special.make_special(runtime::special_kb_clause_data));
    }

}