package foreign;

import nova.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 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 cyclib {

    /******************************************************************/
    /* acyclic_term/1                                                 */
    /******************************************************************/

    /**
     * acyclic_term(T): [TC2 8.3.11]
     * The predicate succeeds when the Prolog term T is an acyclic term,
     * otherwise the predicate fails.
     */
    private static boolean test_acyclic_term(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        boolean res = walk_acyclic(alpha, Store.VAR_MASK_SEEN);
        walk_acyclic(alpha, 0);
        return !res;
    }

    /**
     * Walk a Prolog term completely or partially.
     *
     * @param first The Prolog term.
     * @param state The desired state.
     * @return True if the walk was stopped, otherwise false.
     */
    private static boolean walk_acyclic(Object first, int state) {
        List stack = null;
        for (; ; ) {
            first = Store.deref(first);
            if (Store.is_compound(first)) {
                if ((((Store.Compound) first).walk & Store.VAR_MASK_SEEN) != state) {
                    ((Store.Compound)first).walk = (((Store.Compound)first).walk & ~Store.VAR_MASK_SEEN) | state;
                    ((Store.Compound) first).walk &= ~Store.VAR_MASK_SERNO;
                    stack = Store.stack_push(stack, first);
                    first = ((Store.Structure) first).args[0];
                    continue;
                } else if ((((Store.Compound)first).walk & Store.VAR_MASK_SERNO)
                        != ((Store.Structure) first).args.length) {
                    return true;
                }
            }
            Store.Compound item = (Store.Compound)Store.stack_peek(stack);
            while (item != null &&
                    (item.walk & Store.VAR_MASK_SERNO) == item.args.length - 1) {
                item.walk++;
                Store.stack_pop(stack);
                item = (Store.Compound)Store.stack_peek(stack);
            }
            if (item == null) {
                return false;
            } else {
                item.walk++;
                first = item.args[item.walk & Store.VAR_MASK_SERNO];
            }
        }
    }

    /******************************************************************/
    /* sys_acyclic_decompose/3                                        */
    /******************************************************************/

    /**
     * sys_acyclic_decompose(S, T):
     * The built-in succeeds in R with skeleton and substitution
     * list cells so that the substitions applied to the skeletons
     * gives identical the elements of the Prolog list L.
     */
    private static boolean test_sys_acyclic_decompose(Object[] args) {
        Object alpha = Machine.exec_build(args[0]);
        Map map = new HashMap();
        Store.Structure back = null;
        Object res = null;
        while (Store.is_structure(alpha) &&
                ".".equals(((Store.Structure) alpha).functor) &&
                ((Store.Structure) alpha).args.length == 2) {
            Object temp = Store.deref(((Store.Structure) alpha).args[0]);
            temp = walk_decom(map, temp);
            temp = new Store.Compound(".",
                    new Object[]{temp, Store.UNDEF_OBJ});
            if (back == null) {
                res = temp;
            } else {
                back.args[1] = temp;
            }
            back = (Store.Structure)temp;
            alpha = Store.deref(((Store.Structure) alpha).args[1]);
        }
        if (back == null) {
            res = "[]";
        } else {
            back.args[1] = "[]";
        }
        return Machine.exec_unify(args[1], res);
    }

    /**
     * Walk a Prolog term completely.
     *
     * @param map The compound map.
     * @param first The Prolog term.
     * @return The decomp.
     */
    private static Object walk_decom(Map map, Object first) {
        List stack = null;
        Object subst = "[]";
        for (; ; ) {
            first = Store.deref(first);
            if (Store.is_compound(first)) {
                Object peek = Store.assoc_get(map, first);
                if (peek == null) {
                    map = Store.assoc_set(map, first, Store.UNDEF_OBJ);
                    peek = new Store.Compound(((Store.Structure) first).functor,
                            new Object[((Store.Structure) first).args.length]);
                    Machine.Item item = new Machine.Item((Store.Structure)first, peek, 0);
                    stack = Store.stack_push(stack, item);
                    first = ((Store.Structure) first).args[0];
                    continue;
                } else {
                    if (peek == Store.UNDEF_OBJ) {
                        peek = new Store.Variable();
                        map = Store.assoc_set(map, first, peek);
                    }
                    first = peek;
                }
            }
            Machine.Item item = (Machine.Item)Store.stack_peek(stack);
            while (item != null &&
                    item.idx  == item.first.args.length - 1) {
                ((Store.Structure)item.second).args[item.idx] = first;
                first = item.second;
                Object peek = Store.assoc_get(map, item.first);
                if (peek == Store.UNDEF_OBJ) {
                    map = Store.assoc_set(map, item.first, first);
                } else {
                    first = new Store.Compound("=", new Object[]{peek, first});
                    subst = new Store.Compound(".", new Object[]{first, subst});
                    first = peek;
                }
                Store.stack_pop(stack);
                item = (Machine.Item)Store.stack_peek(stack);
            }
            if (item == null) {
                return new Store.Compound(".", new Object[]{first,subst});
            } else {
                ((Store.Structure)item.second).args[item.idx] = first;
                item.idx++;
                first = item.first.args[item.idx];
            }
        }
    }

    /******************************************************************/
    /* Cyc Lib Init                                                   */
    /******************************************************************/

    public static void main() {
        Store.add("acyclic_term", 1, special.make_check(cyclib::test_acyclic_term));
        Store.add("sys_acyclic_decompose", 2, special.make_check(cyclib::test_sys_acyclic_decompose));
    }

}
