open Js_of_ocaml
open Lwt.Infix
module NVG = Graphv_webgl

let _ =
  Logs.set_reporter (Human.Logs_reporter.console_reporter ());
  Logs.set_level (Some Debug)

module Log = (val Logs.src_log Logs.default : Logs.LOG)

(* This scales the canvas to match the DPI of the window,
   it prevents blurriness when rendering to the canvas *)
let scale_canvas (canvas : Dom_html.canvasElement Js.t) =
  let dpr = Dom_html.window##.devicePixelRatio in
  let rect = canvas##getBoundingClientRect in
  let width = rect##.right -. rect##.left in
  let height = rect##.bottom -. rect##.top in
  canvas##.width := width *. dpr |> int_of_float;
  canvas##.height := height *. dpr |> int_of_float;
  let width =
    Printf.sprintf "%dpx" (int_of_float width) |> Js.string
  in
  let height =
    Printf.sprintf "%dpx" (int_of_float height) |> Js.string
  in
  canvas##.style##.width := width;
  canvas##.style##.height := height

let webgl_initialize canvas =
  scale_canvas canvas;
  (* Graphv requires a stencil buffer to work properly *)
  let attrs = WebGL.defaultContextAttributes in
  attrs##.stencil := Js._true;
  match
    WebGL.getContextWithAttributes canvas attrs |> Js.Opt.to_option
  with
  | None ->
      print_endline "Sorry your browser does not support WebGL";
      raise Exit
  | Some ctx -> ctx

let graphv_initialize webgl_ctx =
  let open NVG in
  let vg =
    create
      ~flags:CreateFlags.(antialias lor stencil_strokes)
      webgl_ctx
  in
  (* File in this case is actually the CSS font name *)
  Text.create vg ~name:"sans" ~file:"sans" |> ignore;
  webgl_ctx##clearColor 0.3 0.3 0.32 1.;
  vg

let request_animation_frame () =
  let t, s = Lwt.wait () in
  let (_ : Dom_html.animation_frame_request_id) =
    Dom_html.window##requestAnimationFrame
      (Js.wrap_callback (fun (time : float) -> Lwt.wakeup s time))
  in
  t

let render_stream canvas webgl_ctx vg
    (render : NVG.t -> ?time:float -> Gg.p2 -> Human.I.t -> unit) :
    Human.I.t Lwt_stream.t -> unit Lwt.t =
  Lwt_stream.iter_n (fun i ->
      request_animation_frame () >>= fun time ->
      webgl_ctx##clear
        (webgl_ctx##._COLOR_BUFFER_BIT_
        lor webgl_ctx##._DEPTH_BUFFER_BIT_
        lor webgl_ctx##._STENCIL_BUFFER_BIT_);
      let device_ratio = Dom_html.window##.devicePixelRatio in
      NVG.begin_frame vg ~width:canvas##.width ~height:canvas##.height
        ~device_ratio;
      NVG.Transform.scale vg ~x:device_ratio ~y:device_ratio;
      render vg ~time Gg.P2.o i;
      NVG.end_frame vg;
      Lwt.return_unit)

open Human

let _ =
  let canvas =
    Js.Unsafe.coerce (Dom_html.getElementById_exn "canvas")
  in
  let webgl_ctx = webgl_initialize canvas in
  let vg = graphv_initialize webgl_ctx in
  let open Js_of_ocaml_lwt.Lwt_js_events in
  let open Nottui in
  let gravity_pad = Gravity.make ~h:`Negative ~v:`Negative in
  let gravity_crop = Gravity.make ~h:`Positive ~v:`Negative in
  let body = Lwd.var (Lwd.pure Ui.empty) in
  let wm = Nottui_widgets.window_manager (Lwd.join (Lwd.get body)) in
  let ui =
    Nottui_widgets.(
      edit_area
        ~table:
          (edit_area_of_string
             "edit me?\n\
              derp derp derp\n\
              herp herp derp\n\
              ding dong beep beep")
        ()
      (* vlist_of_text
         @@ Lwd.pure
              "navigate me?\n\
               derp derp derp\n\
               herp herp derp\n\
               ding dong beep beep" *)
      (* @@ Lwd_utils.pack Ui.pack_y
            [
              edit_field edit_me;
              string "derp derp derp";
              string "herp herp derp";
              string "ding dong beep beep";
              string "derp derp derp";
              string "herp herp derp";
              string "ding dong beep beep";
              string "derp derp derp";
              string "herp herp derp";
              string "ding dong beep beep";
              string "derp derp derp";
              string "herp herp derp";
              string "ding dong beep beep";
              main_menu_item wm "Quit" (fun () -> exit 0);
            ] *))
  in
  let root =
    Lwd.set body
      (Lwd.map ~f:(Ui.resize ~pad:gravity_pad ~crop:gravity_crop) ui);
    Nottui_widgets.window_manager_view wm
  in

  let events, push_event = Lwt_stream.create () in
  let images =
    Human.Nottui_lwt.render vg
      ~size:(Gg.P2.v canvas##.width canvas##.height)
      events root
  in
  async (fun () ->
      render_stream canvas webgl_ctx vg
        (fun vg ?(time = 0.) p i ->
          let _ = time in
          Log.debug (fun m ->
              m "Drawing image: p=%a n=%a" Gg.V2.pp p
                (I.Draw.pp ~attr:A.dark)
                i);
          let p' = I.Draw.node vg A.dark p i in
          Logs.debug (fun m ->
              m "Drawing finished: p'=%a" Gg.V2.pp p'))
        images);
  buffered_loop (make_event Dom_html.Event.keydown) Dom_html.document
    (fun ev _ ->
      Lwt.return
      @@ push_event (Some (`Key (Event_js.evt_of_jskey ev))))
