:- module(test_reading, []).

:- use_module(library(apply_macros)).
:- use_module(library(debug), [debug/3, debug/1]).

thread_pool:create_pool(handler_pool) :-
    thread_pool_create(handler_pool, 20, []).

config_file(F) :- getenv('CONFIG_FILE', F), !.
config_file('../config.pl').

:- setting(port, integer, 8080, 'Port to run API server on').

%! main is det.
%  Non-interactive entry point for the server
main :-
    config_file(Conf),
    load_settings(Conf),
    setting(port, Port),
    go(Port),
    set_cleanup_handlers,
    syslog(notice, "Server started", []),
    thread_get_message(_). % wait

set_cleanup_handlers :-
    on_signal(term, _, shutdown),
    on_signal(int, _, shutdown),
    on_signal(usr2, _, reload_config).

shutdown(_) :-
    setting(port, Port),
    stop(Port),
    halt.

reload_config(_) :-
    syslog(debug, "Reloading config", []),
    config_file(Conf),
    load_settings(Conf).

%! go(+Port) is det.
%  Interactive entry point to start the server.
go(Port) :-
    debug(server), debug(notify),
    setting(use_syslog, UseSyslog),
    setting(log_topic, LogTopic),
    ( UseSyslog -> openlog(LogTopic, [], syslog) ; true ),
    set_prolog_flag(message_context, [thread, time('%Y-%m-%d %T')]),
    config_file(Conf),
    load_settings(Conf),
    http_server(http_dispatch, [port(Port)]),
    maybe_start_toplevel.

stop(Port) :-
    debug(server, "Stopping...", []),
    http_stop_server(Port, []),
    closelog.

maybe_start_toplevel :-
    setting(remote_toplevel_port, Port),
    Port \= 0, !,
    debug(server, "Starting remote toplevel on port ~w", [Port]),
    prolog_server(Port, [allow(ip(127, 0, 0, 1))]).
maybe_start_toplevel.

% Routes

:- multifile http:location/3.
:- dynamic http:location/3.

http:location(api, root(api), []).

:- http_handler('/', page_handler(home_page(_{})), [method(get), id(home_page),
                                               spawn(handler_pool)]).
:- http_handler('/info', page_handler(info_page), [method(get), id(info_page),
                                                   spawn(handler_pool)]).
:- http_handler('/do/thing', form_handler(greeting),
                [method(post), id(greeting_form), spawn(handler_pool)]).
:- http_handler('/counter/inc', form_handler(counter_inc), [method(post),
                                                            id(counter_inc_form),
                                                            spawn(handler_pool)]).

% Handlers

page_handler(PageDcg, _Request) :-
    reply_html_page(title('Hotwire.pl'), [\PageDcg]).

form_handler(Action, Request) :-
    http_read_data(Request, Data, [form_data(form)]),
    reply_html_page(title('Hotwire.pl'), [\form(Action, Data)]).

% content

user:body(default, Body) -->
    html(
        body([div('data-frame-id'(app), Body), \hotwire_script_tag])).

hotwire_script_tag -->
    js_script({|javascript(_)||
        const parser = new DOMParser();

        const handleResponse = (target, resp) => {
          const doc = parser.parseFromString(resp, "text/html");
          const id = target.getAttribute("data-frame-id");
          const elt = doc.querySelector(`*[data-frame-id="${id}"]`);
          if (target) { target.replaceWith(elt); }
        };

        const findTargetElement = (start_elt) => {
          let elt = start_elt;
          if (elt.getAttribute("data-target")) {
            const target_id = elt.getAttribute("data-target")
            const target = document.querySelector(`*[data-frame-id="${target_id}"]`);
            if (target) { return target; }
          }
          while (elt && !elt.getAttribute("data-frame-id")) {
              elt = elt.parentNode;
          }
          return elt;
        };

        document.body.addEventListener("submit", (evt) => {
          evt.preventDefault();
          evt.stopImmediatePropagation();
          const form = evt.target;
          const target = findTargetElement(form);
          const data = new FormData(form);
          fetch(form.action,
                {method: 'POST',
                 // explicitly not setting content-type
                 // so browser sets boundary properly
                 credentials: 'same-origin',
                 body: data})
          .then((resp) => resp.text())
          .then((resp) => {
              handleResponse(target, resp);
              form.reset();
          })
          .catch(err => {
              console.error("Error submitting form", err);
          })
        });

        const find_link = (element) => {
        let elt = element;
        while (elt &&
                ( elt.nodeType !== Node.ELEMENT_NODE ||
                !elt.getAttribute("href") )) {
            elt = elt.parentNode;
        }
        return elt;
        };

        document.body.addEventListener('click', e => {
          const any_key = e.metaKey || e.altKey || e.ctrlKey || e.shiftKey;
          const link_elt = find_link(e.target);
          const href = link_elt && link_elt.href;
          if (!link_elt || !href) { return; }
          const url = new URL(href);
          const target_elt = link_elt.getAttribute("target") === "_top" ?
                             document.body : findTargetElement(link_elt);
          if (!any_key &&
              ( link_elt.target === "" ||
                  link_elt.target === "_self" ) &&
              e.button === 0 &&
              window.location.hostname === url.hostname &&
              ( !url.port || window.location.port === url.port )) {
            e.preventDefault();
            const loc = window.location;
            const cur_href = loc.pathname + loc.search + loc.hash;
            const rel_href = url.pathname + url.search + url.hash;
            if (cur_href !== rel_href) {
              history.pushState({}, '', rel_href);
            }
            fetch(rel_href,
                  {method: 'GET',
                   credentials: 'same-origin'})
            .then(resp => resp.text())
            .then(resp => handleResponse(target_elt, resp))
            .catch(err => {
              console.error("Error fetching page", err);
            })
          }
        });

        window.onpopstate = (event) => {
            fetch(document.location.pathname,
                {method: 'GET',
                credentials: 'same-origin',
                headers: {'x-hotwire-partial': 1}})
            .then(resp => resp.text())
            .then(resp => handleResponse(resp))
            .catch(err => {
              console.error("Error fetching page", err);
            })
        };
    |}).

% Pages

home_page(Info) -->
    { ( get_dict(name, Info, Name) -> true ; Name = ""),
      ( get_dict(counter, Info, Counter) -> true ; Counter = 0 ) },
    html(
        [
         div('data-frame-id'(main),
             [\app_nav(home_page),
              div('data-frame-id'(greeting),
                  [h1("Hello ~w"-[Name]),
                   form([method(post), action(#(greeting_form))],
                        [input([type(text), name(name), placeholder(name)], []),
                         input([type(text), name(beep), placeholder(thing)], []),
                         button(hello)])
                  ]),

              div('data-frame-id'(counter),
                  [span(Counter),
                   form([action(#(counter_inc_form)), method(post)],
                        [input([type(hidden), value(Counter), name(counter)], []),
                         button("Increment")])
                  ])
             ])
        ]
    ).

info_page -->
    html(
        div('data-frame-id'(main),
            [\app_nav(info_page),
             h1("Info"),
             p("here's some stuff, some information"),
             a(href(/), "Back to home")
            ])
    ).

app_nav(Active) -->
    html(
        nav([class("bg-red-50 md:mt-1")],
            \nav_links(Active, [home_page-"Main Page", info_page-"Info"]))).

nav_links(_, []) --> [].
nav_links(Active, [Link-Title|Links]) -->
    {  Active == Link -> Cls = active ; Cls = '' },
    html(a([href(#(Link)), class([Cls, 'text-red-500', 'hover:bg-pink-100'])], Title)),
    nav_links(Active, Links).

% handlers

form(greeting, Body) -->
     { memberchk(name=Name, Body),
       Info = _{name: Name, counter: 0} % get counter from elsewhere?
     },
     home_page(Info).

form(counter_inc, Body) -->
    { memberchk(counter=ValS, Body),
      atom_number(ValS, Val),
      NewVal is Val + 1,
      Info = _{name: "", counter: NewVal} % get name from elsewhere?
    },
    home_page(Info).
