/**
 * 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, exec_unify, add, check_atom, task_async,
    Sink, make_check, check_integer, Source, get_ctx,
    register_signal, get_encoding, map_stream_error,
    Context, check_clause, Compound
} from "../../nova/core.mjs";

let http;
if (typeof window === 'undefined') {
    http = await import("node:http");
} else {
    http = undefined;
}

/*********************************************************************/
/* HTTP Server                                                       */
/*********************************************************************/

/**
 * os_http_server_new(S):
 * The predicate succeeds in S with a new http server.
 */
function test_os_http_server_new(args) {
   let obj = http.createServer();
   return exec_unify(args[0], obj);
}

/**
 * os_http_server_on(S, T, C):
 * The predicate succeeds. As a side effect the stackfull handler C
 * is registered as listener to event T from server S.
 */
function test_os_http_server_on(args) {
    let obj = exec_build(args[0]);
    let typ = exec_build(args[1]);
    check_atom(typ);
    let clause = exec_build(args[2]);
    check_clause(clause);
    let buf = new Context();
    obj.on(typ, async (...paras) => await task_async(clause, buf, paras))
    return true;
}

/**
 * os_http_listen_promise(S, P, Q):
 * The predicate succeeds in Q with a promise to start
 * the server S listening on port P.
 */
function test_os_http_listen_promise(args) {
    let obj = exec_build(args[0]);
    let port = exec_build(args[1]);
    check_integer(port);
    let buf = get_ctx();
    let prom = http_listen_promise(buf, obj, port);
    return exec_unify(args[2], prom);
}

function http_listen_promise(buf, obj, port) {
    return new Promise(resolve => {
        obj.once('listening', err => {
            resolve();
        });
        obj.once('error', err => {
            register_signal(buf, map_stream_error(err));
            resolve();
        });
        try {
            obj.listen(port);
        } catch (err) {
            register_signal(buf, new Compound("resource_error",
                ["state_error"]));
            resolve();
        }
    });
}

/**
 * os_http_close_promise(S, Q):
 * The predicate succeeds in Q with a promise to close
 * the server S.
 */
function test_os_http_close_promise(args) {
    let obj = exec_build(args[0]);
    let buf = get_ctx();
    let prom = http_close_promise(buf, obj);
    return exec_unify(args[1], prom);
}

function http_close_promise(buf, obj) {
    return new Promise(resolve => {
        obj.close(err => {
            resolve();
        });
        obj.closeAllConnections();
    });
}

/*********************************************************************/
/* HTTP Request                                                      */
/*********************************************************************/

/**
 * http_current_method(S, P):
 * The predicate succeeds in M with the method of the HTTP request S.
 */
function test_http_current_method(args) {
    let obj = exec_build(args[0]);
    return exec_unify(args[1], obj.method);
}

/**
 * http_current_path(S, P):
 * The predicate succeeds in P with the path of the HTTP request S.
 */
function test_http_current_path(args) {
    let obj = exec_build(args[0]);
    return exec_unify(args[1], obj.url);
}

/**
 * os_http_current_headers(S, M):
 * The predicate succeeds in M with the headers of the HTTP request S.
 */
function test_os_http_current_headers(args) {
    let obj = exec_build(args[0]);
    return exec_unify(args[1], obj.headers);
}

/**
 * os_http_input_promise_opts(S, L, R, Q):
 * The predicate succeeds in Q with a promise for a new
 * text reader R on HTTP request S and option list L.
 */
function test_os_http_input_promise_opts(args) {
    let obj = exec_build(args[0]);
    let opts = exec_build(args[1]);
    let stream = new Source();
    if (!exec_unify(args[2], stream))
        return false;
    let buf = get_ctx();
    let prom = http_input_promise_opts(buf, stream, obj, opts);
    return exec_unify(args[3], prom);
}

function http_input_promise_opts(buf, stream, obj, opts) {
    return new Promise(resolve => {
        let res = [];
        let enc = get_encoding(opts);
        obj.setEncoding(enc);
        obj.on("data", chunk => {
            res.push(chunk);
        });
        obj.once("end", () => {
            stream.buf = res.join();
            stream.pos = 0;
            resolve();
        });
        obj.once("error", err => {
            register_signal(buf, map_stream_error(err));
            resolve();
        });
    });
}

/*********************************************************************/
/* HTTP Response                                                     */
/*********************************************************************/

/**
 * os_http_write_head(S, C, H):
 * The predicate succeeds. As a side effect it writes the status
 * code C and the headers map H to the HTTP response S.
 */
function test_os_http_write_head(args) {
    let obj = exec_build(args[0]);
    let code = exec_build(args[1]);
    check_integer(code);
    let headers = exec_build(args[2]);
    obj.writeHead(code, headers);
    return true;
}

/**
 * os_http_output_opts(S, L, W):
 * The predicate succeeds in W with a new text writer for
 * the HTTP response S and the option list L.
 */
function test_os_http_output_opts(args) {
    let obj = exec_build(args[0]);
    let opts = exec_build(args[1]);
    let dst = new Sink();
    let enc = get_encoding(opts);
    dst.data = obj;
    dst.send = (data, buf) => http_send(data, buf, enc);
    dst.release = http_release;
    return exec_unify(args[2], dst);
}

function http_send(data, buf, enc) {
    data.write(buf, enc);
    return data;
}

function http_release(res) {
    res.end();
}

/*********************************************************************/
/* HTTP Lib Init                                                     */
/*********************************************************************/

export function main() {
    add("os_http_server_new", 1, make_check(test_os_http_server_new));
    add("os_http_server_on", 3, make_check(test_os_http_server_on));
    add("os_http_listen_promise", 3, make_check(test_os_http_listen_promise));
    add("os_http_close_promise", 2, make_check(test_os_http_close_promise));
    add("http_current_method", 2, make_check(test_http_current_method));
    add("http_current_path", 2, make_check(test_http_current_path));
    add("os_http_current_headers", 2, make_check(test_os_http_current_headers));
    add("os_http_input_promise_opts", 4, make_check(test_os_http_input_promise_opts));
    add("os_http_write_head", 3, make_check(test_os_http_write_head));
    add("os_http_output_opts", 3, make_check(test_os_http_output_opts));
}