open Graphics ;;
exception MenuExit ;;
exception MenuStart ;;

let current_button_index = ref 0
and current_interface_index = ref 0 ;;

type 'a value_changer = {
  ptr : 'a ref ;
  min : 'a ;
  max : 'a ;
  pad : 'a ;
  min_update : ('a ref) list ; (* after this value changes, these values will be set to min(their_value, this_value-1) *)
  max_update : ('a ref) list ; (* after this value changes, these values will be set to max(their_value, this_value+1) *)
} ;;

type action = Nothing | Tweak of int value_changer | Warp of int

and button = {
  id : int ;
  text : string ;
  pos : Csts.rect ;
  red : int ;
  green : int ;
  blue : int ;
  actn : action 
}

and interface = {
  index : int ;
  title : string ;
  red : int ;
  green : int ;
  blue : int ;
  mutable bts : button list ;
} ;; (* circular definitions goes brrr *)

let default_button =   {
  id = -1 ;
  text = "Empty" ;
  pos = Csts.default_rect ;
  red = 128 ;
  green = 128 ;
  blue = 128 ;
  actn = Nothing ;
} ;;

let default_interface = {
  index = -1 ;
  title = "Empty" ;
  red = 128 ;
  green = 128 ;
  blue = 128 ;
  bts = []
} ;;

(* --------------------------------------------------------------------------------------------------------------------------------------------- *)
let arr_buttons = Dynamic.dyn_create default_button ;;
let arr_interfaces = Dynamic.dyn_create default_interface ;;
(* --------------------------------------------------------------------------------------------------------------------------------------------- *)
(* init *)

let init_button () =
  let res = {
    id = !current_button_index ;
    text = "Empty" ;
    pos = Csts.default_rect ;
    red = 128 ;
    green = 128 ;
    blue = 128 ;
    actn = Nothing ;
  } in
  Dynamic.dyn_add arr_buttons res ;
  incr current_button_index ;;

let build_button (text : string) (r : Csts.rect) (red : int) (green : int) (blue : int) (ac : action) =
  let res = {
    id = !current_button_index ;
    text = text ;
    pos = r ;
    red = red ;
    green = green ;
    blue = blue ;
    actn = ac
  } in
  Dynamic.dyn_add arr_buttons res ;
  incr current_button_index ;;

let init_interface () =
  let res = {
    index = -1 ;
    title = "Empty" ;
    red = 128 ;
    green = 128 ;
    blue = 128 ;
    bts = []
  } in
  Dynamic.dyn_add arr_interfaces res ;
  incr current_interface_index ;;

let build_empty_interface (title : string) (red : int) (green : int) (blue : int) =
  let res =   {
    index = !current_interface_index ;
    title = title ;
    red = 128 ;
    green = 128 ;
    blue = 128 ;
    bts = []
  } in
  Dynamic.dyn_add arr_interfaces res ;
  incr current_interface_index ;;

let build_interface (title : string) (red : int) (green : int) (blue : int) (butts : button list) =
  let res =   {
    index = !current_interface_index ;
    title = title ;
    red = 128 ;
    green = 128 ;
    blue = 128 ;
    bts = butts ; (* please *)
  } in
  Dynamic.dyn_add arr_interfaces res ;
  incr current_interface_index ;;

let build_tweak (ptr : int ref) (m : int) (mx : int) (st : int) (mi : (int ref) list) (sp : (int ref) list) = {
    ptr = ptr ;
    min = m;
    max = mx ;
    pad = st ;
    min_update = mi ;
    max_update = sp ;
  } ;;

(* --------------------------------------------------------------------------------------------------------------------------------------------- *)
let current_it_id = ref 0 ;;
(* --------------------------------------------------------------------------------------------------------------------------------------------- *)
(* additions *)

let add_button_to_interface (it : int) (b_id : int) =
  let interf = Dynamic.dyn_mem arr_interfaces it in
  interf.bts <- (Dynamic.dyn_mem arr_buttons b_id)::(interf.bts) ;;

let add_button_to_current (b_id : int) =
  let interf = Dynamic.dyn_mem arr_interfaces !current_it_id in
  interf.bts <- (Dynamic.dyn_mem arr_buttons b_id)::(interf.bts) ;;

(* --------------------------------------------------------------------------------------------------------------------------------------------- *)
(* printing *)

let is_within (r : Csts.rect) (x : int) (y : int) =
  r.x <= x && r.y < y && x <= r.x + r.w && y <= r.y + r.h ;;

let draw_closest (r : int) (g : int) (b : int) =
  if r+g+b <= 384 then
    set_color white
  else
    set_color black ;;

let print_button (b : button) =
  let (mx, my) = mouse_pos () in
  if is_within b.pos mx my then begin
    set_color (rgb 0 0 255) ;
    fill_rect (b.pos.x-6) (b.pos.y-6) (b.pos.w+12) (b.pos.h+12) ;
  end;
  set_color (rgb b.red b.green b.blue) ;
  fill_rect b.pos.x b.pos.y b.pos.w b.pos.h ;
  draw_closest b.red b.green b.blue ;
  set_line_width 2 ;
  Drawing.draw_string (b.pos.x+3) (b.pos.y + b.pos.h/2) b.text (min (3*b.pos.h/4-2) ((7*((b.pos.w*3)/4-50)/(max 1 (String.length b.text)))/10)) ;
  match b.actn with
    | Tweak v ->
      Drawing.draw_integer_alignedright (b.pos.x + b.pos.w) (b.pos.y + b.pos.h/2) !(v.ptr) 15
    | _ -> () ;;

let print_interface (it : interface) =
  List.iter print_button it.bts ;
  set_color black ;
  Drawing.draw_string 20 (Csts.__height__ - 40) it.title 30 ;;

let print_current_interface () =
  let interf = Dynamic.dyn_mem arr_interfaces !current_it_id in
  List.iter print_button interf.bts ;
  set_color black ;
  Drawing.draw_string 20 (Csts.__height__ - 40) interf.title 30 ;;

(* --------------------------------------------------------------------------------------------------------------------------------------------- *)
(* actions 1/2 *)

let get1char () =
  if key_pressed () then
    read_key ()
  else
    '@' ;;

let change_other (reference : int) (mins : (int ref) list) (maxs : (int ref) list) =
  let rec aux = function
    | ([], []) -> 
      ()
    | (hmin::tmin, []) ->
      hmin := min (!hmin) (reference -1) ;
      aux (tmin, [])
    | ([], hmax::tmax) ->
      hmax := max (!hmax) (reference +1) ;
      aux ([], tmax)
    | (hmin::tmin, hmax::tmax) ->
      hmin := min (!hmin) (reference -1) ;
      hmax := max (!hmax) (reference +1) ;
      aux (tmin, tmax)
  in aux (mins, maxs) ;;

let offset_pm = 40 ;;
let size_pm = (Csts.__width__ - 2*offset_pm) /11 ;;

let draw_rect_pm (x : int) (y : int) (w: int) (h : int) (red : int) (green : int) (blue : int) =
  let (mx, my) = mouse_pos () in
  if is_within {x = x; y = y; w = w; h = h} mx my then begin
    set_color (rgb 0 255 0) ;
    fill_rect x y w h ;
    set_color (rgb red green blue) ;
    fill_rect (x+6) (y+6) (w-12) (h-12) 
  end
  else begin
    set_color (rgb red green blue) ;
    fill_rect x y w h 
  end ;;

let draw_incrdecr () =
  draw_rect_pm (offset_pm)                     (offset_pm) size_pm size_pm 255 0 0;
  draw_rect_pm (Csts.__width__/11 + offset_pm) (offset_pm) size_pm size_pm 255 0 0;
  draw_rect_pm (2*Csts.__width__/11 + offset_pm) (offset_pm) size_pm size_pm 255 0 0;
  draw_rect_pm (3*Csts.__width__/11 + offset_pm) (offset_pm) size_pm size_pm 255 0 0;
  draw_rect_pm (4*Csts.__width__/11 + offset_pm) (offset_pm) size_pm size_pm 255 0 0;
  draw_rect_pm (10*Csts.__width__/11 - offset_pm) (offset_pm) size_pm size_pm 0 0 255;
  draw_rect_pm (9*Csts.__width__/11 - offset_pm) (offset_pm) size_pm size_pm 0 0 255;
  draw_rect_pm (8*Csts.__width__/11 - offset_pm) (offset_pm) size_pm size_pm 0 0 255;
  draw_rect_pm (7*Csts.__width__/11 - offset_pm) (offset_pm) size_pm size_pm 0 0 255;
  draw_rect_pm (6*Csts.__width__/11 - offset_pm) (offset_pm) size_pm size_pm 0 0 255;;

let draw_digits_incrdecr () =
  set_color white ;
  Drawing.draw_string_centered (offset_pm + size_pm/2) (offset_pm + size_pm/2) "min" (size_pm/6) ;
  Drawing.draw_integer (offset_pm + Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) 1 (size_pm/3) ;
  Drawing.draw_integer (offset_pm + 2*Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) 10 (size_pm/3) ;
  Drawing.draw_integer (offset_pm + 3*Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) 100 (size_pm/3) ;
  Drawing.draw_integer (offset_pm + 4*Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) 500 (size_pm/3) ;
  
  Drawing.draw_string_centered (-offset_pm + 10*Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) "max" (size_pm/6) ;
  Drawing.draw_integer (-offset_pm + 9*Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) 1 (size_pm/3) ;
  Drawing.draw_integer (-offset_pm + 8*Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) 10 (size_pm/3) ;
  Drawing.draw_integer (-offset_pm + 7*Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) 100 (size_pm/3) ;
  Drawing.draw_integer (-offset_pm + 6*Csts.__width__/11 + size_pm/2) (offset_pm + size_pm/2) 500 (size_pm/3) ;;

(* --------------------------------------------------------------------------------------------------------------------------------------------- *)
(* actions 2/2 *)

let stall_pm = ref false ;;
let use_incrdecr (val_ptr : int value_changer) =
  if button_down () then begin
    if !stall_pm then 
      () 
    else begin
      stall_pm := true ;
      let (mox, moy) = mouse_pos () in
      if moy >= offset_pm && moy <= offset_pm + size_pm then begin
        match mox with
          | x when x >= offset_pm && x <= offset_pm + size_pm -> 
            val_ptr.ptr := val_ptr.min
          | x when x >= offset_pm + Csts.__width__/11 && x <= offset_pm + Csts.__width__/11 + size_pm -> 
            val_ptr.ptr := max val_ptr.min (!(val_ptr.ptr) - 1)
          | x when x >= offset_pm + 2*Csts.__width__/11 && x <= offset_pm + 2*Csts.__width__/11 + size_pm -> 
            val_ptr.ptr := max val_ptr.min (!(val_ptr.ptr) - 10)
          | x when x >= offset_pm + 3*Csts.__width__/11 && x <= offset_pm + 3*Csts.__width__/11 + size_pm -> 
            val_ptr.ptr := max val_ptr.min (!(val_ptr.ptr) - 100)
          | x when x >= offset_pm + 4*Csts.__width__/11 && x <= offset_pm + 4*Csts.__width__/11 + size_pm -> 
            val_ptr.ptr := max val_ptr.min (!(val_ptr.ptr) - 500)

          | x when x >= 6*Csts.__width__/11 - offset_pm && x <= 6*Csts.__width__/11 - offset_pm + size_pm -> 
            val_ptr.ptr := min val_ptr.max (!(val_ptr.ptr) + 500)
          | x when x >= 7*Csts.__width__/11 - offset_pm && x <= 7*Csts.__width__/11 - offset_pm + size_pm -> 
            val_ptr.ptr := min val_ptr.max (!(val_ptr.ptr) + 100)
          | x when x >= 8*Csts.__width__/11 - offset_pm && x <= 8*Csts.__width__/11 - offset_pm + size_pm -> 
            val_ptr.ptr := min val_ptr.max (!(val_ptr.ptr) + 10)
          | x when x >= 9*Csts.__width__/11 - offset_pm && x <= 9*Csts.__width__/11 - offset_pm + size_pm -> 
            val_ptr.ptr := min val_ptr.max (!(val_ptr.ptr) + 1)
          | x when x >= 10*Csts.__width__/11 - offset_pm && x <= 10*Csts.__width__/11 - offset_pm + size_pm -> 
            val_ptr.ptr := val_ptr.max
          | _ -> ()
      end
    end
  end 
  else
    stall_pm := false ;;

let move_interface (b : button) = match b.actn with
  | Nothing -> ()
  | Tweak val_ptr -> begin
    let halted = ref false in
    set_color black ;
    Drawing.draw_string_centered (Csts.__width__/2) (Csts.__height__ - 50) "press space to confirm" 15 ;
    set_line_width 3 ;
    while not !halted do
      auto_synchronize false ;
      draw_incrdecr () ;
      draw_digits_incrdecr () ;
      set_color black ;
      fill_rect (3*Csts.__width__/10) (3*Csts.__height__/10) (2*Csts.__width__/5) (2*Csts.__height__/5) ;
      set_color white ;
      Drawing.draw_string_centered (Csts.__width__/2) (13*Csts.__height__/20) b.text 20 ;
      Drawing.draw_integer (5+Csts.__width__/2) (Csts.__height__/2) !(val_ptr.ptr) 60 ;
      Drawing.draw_string (5+3*Csts.__width__/10) (11*Csts.__height__/20) "Min" 20 ;
      Drawing.draw_integer_alignedleft (5+3*Csts.__width__/10) (Csts.__height__/2) val_ptr.min 18 ;
      Drawing.draw_string (7*Csts.__width__/10-5-3*20*10/7) (11*Csts.__height__/20) "Max" 20 ;
      Drawing.draw_integer_alignedleft (7*Csts.__width__/10-5-3*20*10/7) (Csts.__height__/2) val_ptr.max 18 ;
      auto_synchronize true ;
      use_incrdecr val_ptr ;
      Unix.sleepf Csts.sleep_d ;
      match get1char () with
        | '+' -> val_ptr.ptr := min val_ptr.max (!(val_ptr.ptr) + val_ptr.pad)
        | '-' -> val_ptr.ptr := max val_ptr.min (!(val_ptr.ptr) - val_ptr.pad)
        | '\n' | ' ' -> halted := true
        | _ -> ()
    done ;
    change_other !(val_ptr.ptr) val_ptr.min_update val_ptr.max_update
  end
  | Warp ne ->
    if ne = -1 then 
      raise MenuExit 
    else if ne = -2 then
      raise MenuStart
    else
      current_it_id := ne ;;

let action_on_button (mox : int) (moy : int) (b : button) =
  if is_within b.pos mox moy then begin
    (*Printf.printf "clicked\n" ;*)
    move_interface b ; 
    true
  end 
  else 
    false ;;

let stall = ref false ;;

let action_on_interface () =
  if button_down () then begin
    if !stall then 
      () 
    else begin
      stall := true ;
      let (mox, moy) = mouse_pos () in
      let rec aux = function
        | [] -> ()
        | b::t ->
          if action_on_button mox moy b then () else aux t
      in
      aux (Dynamic.dyn_mem arr_interfaces !current_it_id).bts
    end
  end 
  else
    stall := false ;;
  
(* --------------------------------------------------------------------------------------------------------------------------------------------- *)
(* Define the menu there *)

build_empty_interface "Main Menu" 128 128 128 ;;
build_empty_interface "Options  " 32 32 32 ;;

build_button "Start"      {x = Csts.__width__/2 - 150; y = 270; w = 300; h = 100} 32 255 32 (Warp (-2)) ;;
build_button "Options"    {x = Csts.__width__/2 - 150; y = 160; w = 300; h = 100} 255 32 255 (Warp 1) ;;
build_button "Exit"       {x = Csts.__width__/2 - 150; y = 50; w = 300; h = 100} 1 1 1 (Warp (-1)) ;;

build_button "Minimum"    {x = Csts.__width__/2 - 200; y = 600; w = 400; h = 100}  255 128 32 (Tweak (build_tweak Csts.n_inf 1 1000 1 [] [Csts.n_sup])) ;;
build_button "Maximum"    {x = Csts.__width__/2 - 200; y = 490; w = 400; h = 100}  128 255 32 (Tweak (build_tweak Csts.n_sup 1 1000 1 [Csts.n_inf] [])) ;;
build_button "Length"     {x = Csts.__width__/2 - 200; y = 380; w = 400; h = 100}  255 32 32 (Tweak (build_tweak Csts.calc_length 2 20 1 [] [])) ;;
build_button "Time"       {x = Csts.__width__/2 - 200; y = 270; w = 400; h = 100} 32 255 32 (Tweak (build_tweak Csts.time_to_ans 3 30 1 [] [])) ;;
build_button "Difficulty" {x = Csts.__width__/2 - 200; y = 160; w = 400; h = 100} 32 32 255 (Tweak (build_tweak Csts.difficulty 1 10 1 [] [])) ;;
build_button "Back"       {x = Csts.__width__/2 - 200; y = 50; w = 400; h = 100} 32 32 32 (Warp 0) ;;

(*Printf.printf "(B : %d ; I : %d)\n" arr_buttons.len arr_interfaces.len ;;*)
add_button_to_interface 0 0 ;
add_button_to_interface 0 1 ;
add_button_to_interface 0 2 ;
add_button_to_interface 1 3 ;
add_button_to_interface 1 4 ;
add_button_to_interface 1 5 ;
add_button_to_interface 1 6 ;
add_button_to_interface 1 7 ;
add_button_to_interface 1 8 ;