/**
 * Modern Albufeira Prolog Interpreter
 *
 * 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 {
    exec_build, is_structure, add, deref, make_check,
    assoc_set, assoc_get, exec_unify, Compound,
    Variable, VAR_MASK_SEEN, VAR_MASK_SERNO, stack_push,
    stack_peek, stack_pop, is_compound, Item
} from "../nova/core.mjs";

/*********************************************************************/
/* 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.
 */
function test_acyclic_term(args) {
    let alpha = exec_build(args[0]);
    let res = walk_acyclic(alpha, 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 complete, otherwise false.
 */
export function walk_acyclic(first, state) {
    let stack = null;
    for (; ; ) {
        first = deref(first);
        if (is_compound(first)) {
            if ((first.walk & VAR_MASK_SEEN) !== state) {
                first.walk = (first.walk & ~VAR_MASK_SEEN) | state;
                first.walk &= ~VAR_MASK_SERNO;
                stack = stack_push(stack, first);
                first = first.args[0];
                continue;
            } else if ((first.walk & VAR_MASK_SERNO)
                     !== first.args.length) {
                return true;
            }
        }
        let item = stack_peek(stack);
        while (item !== null &&
                (item.walk & VAR_MASK_SERNO) === item.args.length - 1) {
            item.walk++;
            stack_pop(stack);
            item = stack_peek(stack);
        }
        if (item === null) {
            return false;
        } else {
            item.walk++;
            first = item.args[item.walk & VAR_MASK_SERNO];
        }
    }
}

/******************************************************************/
/* sys_acyclic_decompose/2                                        */
/******************************************************************/

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

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

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

export function main() {
    add("acyclic_term", 1, make_check(test_acyclic_term));
    add("sys_acyclic_decompose", 2, make_check(test_sys_acyclic_decompose));
}
