/**
 * 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 {
    set, check_atom, check_nonvar, Compound, check_integer,
    copy_term, get_ctx, make_error, exec_build, callback,
    norm_smallint, narrow_float, exec_unify, check_clause,
    make_check, register_interrupt
} from "../../nova/core.mjs";
import {
    check_element
} from "./../text/domlib.mjs";

/*****************************************************************/
/* DOM API II                                                    */
/*****************************************************************/

/**
 * ir_cell_goto(I, E):
 * The predicate succeeds in E with the element of id I.
 */
function test_ir_cell_goto(args) {
    let id = exec_build(args[0]);
    check_atom(id);
    return exec_unify(args[1], document.getElementById(id));
}

/*****************************************************************/
/* Event Handling                                                */
/*****************************************************************/

/**
 * sys_cell_add_listener(E, T, N, F):
 * The predicate adds a type T event handler compiled clause
 * N to the element E with capture flag F.
 */
function test_sys_cell_add_listener(args) {
    let at = exec_build(args[0]);
    check_element(at);
    let type = exec_build(args[1]);
    check_atom(type);
    let clause = exec_build(args[2]);
    check_clause(clause);
    let flag = exec_build(args[3]);
    check_integer(flag);
    let buf = get_ctx();
    at.addEventListener(type, event => callback(clause, buf, [event]),
        ((flag & 1) !== 0));
    return true;
}

/**
 * sys_cell_wait_promise(E, T, N, F, Q):
 * The predicate succeeds in Q with a promise that adds a type T
 * event handler compiled clause N to the element E with capture
 * flag F. If the event handler succeeds the promise resolves
 * and then the event handler gets removed.
 */
function test_sys_cell_wait_promise(args) {
    let buf = get_ctx();
    let at = exec_build(args[0]);
    check_element(at);
    let type = exec_build(args[1]);
    check_atom(type);
    let clause = exec_build(args[2]);
    check_clause(clause);
    let flag = exec_build(args[3]);
    check_integer(flag);
    let prom = sys_cell_wait_promise(buf, at, type, clause, flag);
    return exec_unify(args[4], prom);
}

function sys_cell_wait_promise(buf, at, type, clause, flag) {
    return new Promise(resolve => {
        function handler(event) {
            if (callback(clause, buf, [event])) {
                at.removeEventListener(type, handler);
                register_interrupt(buf, () => {});
                resolve();
            }
        }
        at.addEventListener(type, handler, ((flag & 1) !== 0));
        register_interrupt(buf, () => {
            at.removeEventListener(type, handler);
            register_interrupt(buf, () => {});
            resolve();
        });
    });
}

/*****************************************************************/
/* Browser Behavior                                              */
/*****************************************************************/

/**
 * dom_prevent_default(E):
 * The predicate prevents default behaviour and stops propagation of the event E.
 */
function test_dom_prevent_default(args) {
    let event = exec_build(args[0]);
    check_event(event);
    event.preventDefault();
    return true;
}

/**
 * dom_stop_propagation(E):
 * The predicate prevents default behaviour and stops propagation of the event E.
 */
function test_dom_stop_propagation(args) {
    let event = exec_build(args[0]);
    check_event(event);
    event.stopPropagation();
    return true;
}

function check_event(beta) {
    if (!(beta instanceof Event)) {
        check_nonvar(beta);
        beta = copy_term(beta);
        throw make_error(new Compound("type_error", ["event", beta]));
    }
}

/*********************************************************************/
/* Window Selection                                                  */
/*********************************************************************/

/**
 * ir_selection_current(E, A, F):
 * The predicate succeeds in S with the anchor A and focus F.
 */
function test_ir_selection_current(args) {
    let at = exec_build(args[0]);
    check_element(at);
    let sel = window.getSelection();
    if (sel == null || !at.contains(sel.anchorNode) || !at.contains(sel.focusNode))
        return false;
    let anchor = get_offset(at, sel.anchorNode) + sel.anchorOffset;
    let focus = get_offset(at, sel.focusNode) + sel.focusOffset;
    return exec_unify(args[1], norm_smallint(anchor)) &&
        exec_unify(args[2], norm_smallint(focus));
}

function get_offset(at, alpha) {
    let offset = 0;
    let walker = document.createTreeWalker(at, NodeFilter.SHOW_ALL);
    let node = walker.nextNode();
    while (node !== alpha) {
        if (node instanceof Text)
            offset += node.nodeValue.length;
        node = walker.nextNode();
    }
    return offset;
}

/**
 * ir_selection_set(E, A, F):
 * The predicate succeeds. As a side effect it sets the anchor A and focus F in S.
 */
function test_ir_selection_set(args) {
    let at = exec_build(args[0]);
    check_element(at);
    let anchor = exec_build(args[1]);
    check_integer(anchor);
    let focus = exec_build(args[2]);
    check_integer(focus);
    let first = get_point(at, narrow_float(anchor));
    let second = get_point(at, narrow_float(focus));
    try {
       let sel = window.getSelection();
       sel.collapse(first[0], first[1]);
       sel.extend(second[0], second[1]);
    } catch (err) {
       throw make_error(new Compound("permission_error",
           ["selection", "modify", new Compound("-",
               [anchor, focus])]));
    }
    return true;
}

function get_point(at, offset) {
   let walker = document.createTreeWalker(at, NodeFilter.SHOW_TEXT);
   let node = walker.nextNode();
   while (node !== null) {
       let len = node.nodeValue.length;
       if (len >= offset)
           return [node, offset];
       offset -= len;
       node = walker.nextNode();
   }
   return null;
}

/*********************************************************************/
/* Emit Lib Init                                                     */
/*********************************************************************/

export function main() {
    set("ir_cell_goto", 2, make_check(test_ir_cell_goto));
    set("sys_cell_add_listener", 4, make_check(test_sys_cell_add_listener));
    set("sys_cell_wait_promise", 5, make_check(test_sys_cell_wait_promise));
    set("dom_prevent_default", 1, make_check(test_dom_prevent_default));
    set("dom_stop_propagation", 1, make_check(test_dom_stop_propagation));
    set("ir_selection_current", 3, make_check(test_ir_selection_current));
    set("ir_selection_set", 3, make_check(test_ir_selection_set));
}