(* This file was generated by Ocsigen-start.
   Feel free to use it, modify it, and redistribute it as you wish. *)

(* Load Eliom client-side program after storing global data in
   localStorage. Compile as follos:

   ocamlfind ocamlc \
     -package js_of_ocaml,js_of_ocaml.ppx,lwt_ppx \
     -linkpkg -o eliom_loader.byte \
     eliom_loader.ml

   js_of_ocaml eliom_loader.byte
*)

module XmlHttpRequest = Js_of_ocaml_lwt.XmlHttpRequest

(* Debug mode. Set to true if you want to use the debug mode. Used by "log".
*)
let debug = false

(* If debug mode is activated, a paragraph is created and a message is printed
 * in the console.
 *)
let log =
  if debug
  then (fun s ->
    Js_of_ocaml.Firebug.console##log (Js_of_ocaml.Js.string s);
    let p = Js_of_ocaml.Dom_html.createP Js_of_ocaml.Dom_html.document in
    p##.style##.color := Js_of_ocaml.Js.string "#64b5f6";
    Js_of_ocaml.Dom.appendChild p
      (Js_of_ocaml.Dom_html.document##createTextNode (Js_of_ocaml.Js.string s));
    let container = Js_of_ocaml.Dom_html.getElementById "app-container" in
    Js_of_ocaml.Dom.appendChild container p)
  else fun s -> ()

(* Reference used by the binding to fetchUpdate to know if update has been done
 * or if it failed.
 *)
let update_failed = ref false
let data_upload_failed = ref false

(* Get the Eliom server URL where updates must be fetched. *)
let url =
  Js_of_ocaml.Js.Optdef.case
    Js_of_ocaml.Js.Unsafe.global##.___eliom_server_
    (fun () -> "127.0.0.1:8080/__global_data__")
    (fun server -> Js_of_ocaml.Js.to_string server ^ "/__global_data__")

(* Get the local storage object. Fail if local storage is not supported. *)
let storage () =
  Js_of_ocaml.Js.Optdef.case
    Js_of_ocaml.Dom_html.window##.localStorage
    (fun () -> failwith "Browser storage not supported")
    (fun v -> v)

(* This function is called when updating the files. It changes the class of the
 * main container in index.html and add a button with an error message if
 * something went wrong.
 *)
let rec add_retry_button wake msg =
  let container = Js_of_ocaml.Dom_html.getElementById "app-container" in
  let p = Js_of_ocaml.Dom_html.createP Js_of_ocaml.Dom_html.document in
  let btn = Js_of_ocaml.Dom_html.createButton Js_of_ocaml.Dom_html.document in
  (* Set error class *)
  container##.className := Js_of_ocaml.Js.string "app-error";
  (* Error message paragraph *)
  Js_of_ocaml.Dom.appendChild p
    (Js_of_ocaml.Dom_html.document##createTextNode (Js_of_ocaml.Js.string msg));
  p##.id := Js_of_ocaml.Js.string "retry-message";
  (* Retry button *)
  Js_of_ocaml.Dom.appendChild btn
    (Js_of_ocaml.Dom_html.document##createTextNode
       (Js_of_ocaml.Js.string "Retry"));
  btn##.onclick :=
    Js_of_ocaml.Dom_html.handler (fun _ ->
      Js_of_ocaml.Dom.removeChild container p;
      container##.className := Js_of_ocaml.Js.string "app blink";
      if !update_failed
      then (
        update_failed := false;
        ignore Js_of_ocaml.Js.Unsafe.global##.chcp##fetchUpdate);
      if !data_upload_failed
      then (
        data_upload_failed := false;
        Lwt.async (fun () -> get_data wake));
      Js_of_ocaml.Js._false);
  btn##.id := Js_of_ocaml.Js.string "retry-button";
  Js_of_ocaml.Dom.appendChild p btn;
  Js_of_ocaml.Dom.appendChild container p

and get_data wake =
  let%lwt {XmlHttpRequest.content; code} = XmlHttpRequest.get url in
  if code = 200
  then (
    log "Got global data";
    (storage ())##setItem
      (Js_of_ocaml.Js.string "__global_data")
      (Js_of_ocaml.Js.string content);
    Lwt.wakeup wake ())
  else (
    log "Could not get global data";
    if not (!update_failed || !data_upload_failed)
    then (
      data_upload_failed := true;
      add_retry_button wake
        "Cannot connect to the server. Please make sure that this app has access to a data connection."));
  Lwt.return_unit

(* Get the URL saved in the JavaScript variables "___eliom_html_url_" defined in
 * index.html and go this location.
 *)
let redirect () =
  Js_of_ocaml.Js.Optdef.iter Js_of_ocaml.Js.Unsafe.global##.___eliom_html_url_
    (fun url -> Js_of_ocaml.Dom_html.window##.location##replace url)

let _ =
  (* CHCP does not run in the background, so we check for updates on resume *)
  ignore
  @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document
       (Js_of_ocaml.Dom_html.Event.make "resume")
       (Js_of_ocaml.Dom.handler (fun _ ->
          log "Resume";
          ignore Js_of_ocaml.Js.Unsafe.global##.chcp##fetchUpdate;
          Js_of_ocaml.Js._true))
       Js_of_ocaml.Js._false;
  (* Create two threads for success callbacks and error callbacks. *)
  let wait_success, wake_success = Lwt.wait () in
  let wait_error, wake_error = Lwt.wait () in
  (* Callback when success.
   * [callback ev] will print the event if debug mode is activated.
   * Calls by the event chcp_nothingToUpdate.
   *)
  let callback ev =
    Js_of_ocaml.Dom.handler (fun _ ->
      log ev;
      update_failed := false;
      Lwt.wakeup wake_success ();
      Js_of_ocaml.Js._true)
  in
  (* Callback when errors.
   * Calls by the event chcp_nothingToUpdate.
   *)
  let error_callback name =
    Js_of_ocaml.Dom.handler (fun ev ->
      log
        (name ^ ": "
        ^ Js_of_ocaml.Js.to_string ev##.detail##.error##.description);
      update_failed := true;
      if not !data_upload_failed
      then
        add_retry_button wake_error
          (Js_of_ocaml.Js.to_string ev##.detail##.error##.description
          ^ ". Please try again later.");
      Js_of_ocaml.Js.bool true)
  in
  (* Callback to print a message *)
  let status_callback name =
    Js_of_ocaml.Dom.handler (fun ev -> log name; Js_of_ocaml.Js.bool true)
  in
  (* Binding to chcp_nothingToUpdate. Calls [callback ev]. *)
  List.iter
    (fun ev ->
       ignore
       @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document
            (Js_of_ocaml.Dom_html.Event.make ev)
            (callback ev) Js_of_ocaml.Js._false)
    ["chcp_nothingToUpdate"];
  (* Binding to chcp_updateLoadFailed, chcp_updateInstallFailed and
   * chcp_assetsInstallationError. It calls [error_callback ev].
   *)
  List.iter
    (fun ev ->
       ignore
       @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document
            (Js_of_ocaml.Dom_html.Event.make ev)
            (error_callback ev) Js_of_ocaml.Js._false)
    [ "chcp_updateLoadFailed"
    ; "chcp_updateInstallFailed"
    ; "chcp_assetsInstallationError" ];
  (* Binding to other chcp events. It calls [status_callback ev] which will only
   * print the event.
   *)
  List.iter
    (fun ev ->
       ignore
       @@ Js_of_ocaml.Dom.addEventListener Js_of_ocaml.Dom_html.document
            (Js_of_ocaml.Dom_html.Event.make ev)
            (status_callback ev) Js_of_ocaml.Js._false)
    [ "chcp_updateIsReadyToInstall"
    ; "chcp_beforeInstall"
    ; "chcp_nothingToInstall"
    ; "chcp_updateInstalled"
    ; "chcp_beforeAssetsInstalledOnExternalStorage"
    ; "chcp_assetsInstalledOnExternalStorage" ];
  Lwt.async @@ fun () ->
  let%lwt _ = Js_of_ocaml_lwt.Lwt_js_events.onload () in
  let%lwt _ = get_data wake_error in
  let%lwt _ = wait_error in
  let%lwt _ = wait_success in
  Lwt.return (redirect ())
