package doglet.misc;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import nova.*;

import java.io.*;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.function.BiFunction;

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

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

    /**
     * os_http_server_new(S):
     * The predicate succeeds in S with a new http server.
     */
    private static boolean test_os_http_server_new(Object[] args) {
        try {
            HttpServer obj = HttpServer.create(null, 0);
            return Machine.exec_unify(args[0], obj);
        } catch (IOException err) {
            throw Machine.make_error(runtime.map_stream_error(err));
        }
    }

    /**
     * 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.
     */
    private static boolean test_os_http_server_on(Object[] args) {
        HttpServer obj = (HttpServer) Machine.exec_build(args[0]);
        Object type = Machine.exec_build(args[1]);
        special.check_atom(type);
        Object clause = Machine.exec_build(args[2]);
        special.check_clause(clause);
        if ("request".equals(type)) {
            Machine.Context buf = new Machine.Context();
            obj.createContext("/", new BaseHandler(
                    (paras) -> Machine.task_async(clause, buf, paras)));
        }
        return true;
    }

    private static class BaseHandler implements HttpHandler {
        private final Tasklet func;

        public BaseHandler(Tasklet func) {
            this.func = func;
        }

        public void handle(HttpExchange t) {
            new Handler.Coroutine(() -> func.perform(new Object[]{t, t})).async();
        }
    }

    private static interface Tasklet {
        public abstract void perform(Object[] paras);
    }

    /**
     * os_http_listen_sync(S, P):
     * The predicate succeeds. As a side effect the server S
     * starts listening on port P.
     */
    private static boolean test_os_http_listen_sync(Object[] args) {
        try {
            HttpServer obj = (HttpServer) Machine.exec_build(args[0]);
            Object beta = Machine.exec_build(args[1]);
            special.check_integer(beta);
            int port = (!Machine.is_bigint(beta) ? ((Integer) beta).intValue() : -1);
            obj.bind(new InetSocketAddress(port), 0);
            obj.setExecutor(null);
            obj.start();
            return true;
        } catch (IOException err) {
            throw Machine.make_error(runtime.map_stream_error(err));
        }
    }

    /**
     * os_http_close_sync(S):
     * The predicate succeeds. As a side effect the server S is closed.
     */
    private static boolean test_os_http_close_sync(Object[] args) {
        HttpServer obj = (HttpServer) Machine.exec_build(args[0]);
        obj.stop(0);
        return true;
    }

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

    /**
     * http_current_method(S, P):
     * The predicate succeeds in M with the method of the HTTP request S.
     */
    private static boolean test_http_current_method(Object[] args) {
        HttpExchange exchange = (HttpExchange) Machine.exec_build(args[0]);
        String res = exchange.getRequestMethod();
        return Machine.exec_unify(args[1], res);
    }

    /**
     * http_current_path(S, P):
     * The predicate succeeds in P with the path of the HTTP request S.
     */
    private static boolean test_http_current_path(Object[] args) {
        HttpExchange exchange = (HttpExchange) Machine.exec_build(args[0]);
        String res = exchange.getRequestURI().toString();
        return Machine.exec_unify(args[1], res);
    }

    /**
     * os_http_current_headers(S, M):
     * The predicate succeeds in M with the headers of the HTTP request S.
     */
    private static boolean test_os_http_current_headers(Object[] args) {
        HttpExchange exchange = (HttpExchange) Machine.exec_build(args[0]);
        Map res = new HashMap();
        Map<String,List<String>> map = exchange.getRequestHeaders();
        Iterator it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            res.put(((String)entry.getKey()).toLowerCase(), ((List)entry.getValue()).get(0));
        }
        return Machine.exec_unify(args[1], res);
    }

    /**
     * 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.
     */
    private static boolean test_os_http_input_promise_opts(Object[] args) {
        HttpExchange exchange = (HttpExchange) Machine.exec_build(args[0]);
        Map opts = (Map) Machine.exec_build(args[1]);
        runtime.Source stream = new runtime.Source();
        if (!Machine.exec_unify(args[2], stream))
            return false;
        Object buf = Machine.ctx;
        Handler.Promise prom = http_input_promise_opts(buf, stream, exchange, opts);
        return Machine.exec_unify(args[3], prom);
    }

    private static Handler.Promise http_input_promise_opts(Object buf,
                   runtime.Source stream, HttpExchange exchange, Map opts) {
        return new Handler.Promise(() -> {
            InputStream input = exchange.getRequestBody();
            stream.data = new InputStreamReader(input, runtime.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 |= runtime.MASK_SRC_AREAD;
        });
    }

    /*******************************************************************/
    /* 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.
     */
    private static boolean test_os_http_write_head(Object[] args) {
        try {
            HttpExchange exchange = (HttpExchange) Machine.exec_build(args[0]);
            Object code = Machine.exec_build(args[1]);
            special.check_integer(code);
            Object assoc = Machine.exec_build(args[2]);
            int status = (!Machine.is_bigint(code) ? ((Integer) code).intValue() : -1);
            Headers headers = exchange.getResponseHeaders();
            Iterator it = ((Map) assoc).entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                headers.set((String)entry.getKey(), (String) entry.getValue());
            }
            if (!"HEAD".equals(exchange.getRequestMethod()) && status != 304) {
                exchange.sendResponseHeaders(status, 0);
            } else {
                exchange.sendResponseHeaders(status, -1);
            }
            return true;
        } catch (IOException err) {
            throw Machine.make_error(runtime.map_stream_error(err));
        }
    }

    /**
     * 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.
     */
    private static boolean test_os_http_output_opts(Object[] args) {
        HttpExchange exchange = (HttpExchange) Machine.exec_build(args[0]);
        Map opts = (Map) Machine.exec_build(args[1]);
        OutputStream output = exchange.getResponseBody();
        runtime.Sink dst = new runtime.Sink();
        dst.buf = new StringBuilder();
        dst.data = new OutputStreamWriter(output, runtime.get_encoding(opts));
        dst.send = runtime::file_write;
        dst.release = runtime::file_close;
        dst.notify = runtime::file_flush;
        dst.flags |= runtime.MASK_DST_LINE;
        return Machine.exec_unify(args[2], dst);
    }

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

    public static void main() {
        Store.set("os_http_server_new", 1, special.make_check(httplib::test_os_http_server_new));
        Store.set("os_http_server_on", 3, special.make_check(httplib::test_os_http_server_on));
        Store.set("os_http_listen_sync", 2, special.make_check(httplib::test_os_http_listen_sync));
        Store.set("os_http_close_sync", 1, special.make_check(httplib::test_os_http_close_sync));
        Store.set("http_current_method", 2, special.make_check(httplib::test_http_current_method));
        Store.set("http_current_path", 2, special.make_check(httplib::test_http_current_path));
        Store.set("os_http_current_headers", 2, special.make_check(httplib::test_os_http_current_headers));
        Store.set("os_http_input_promise_opts", 4, special.make_check(httplib::test_os_http_input_promise_opts));
        Store.set("os_http_write_head", 3, special.make_check(httplib::test_os_http_write_head));
        Store.set("os_http_output_opts", 3, special.make_check(httplib::test_os_http_output_opts));
    }

}