open Graphics ;;
Random.self_init () ;;
exception Reset ;;

type operation =
  Val of int |
  Sum of operation * operation |
  Diff of operation * operation |
  Prod of operation * operation |
  Exp of operation * int ;;

let rec calculate = function
  | Val k -> k
  | Exp (op, k) -> Math.pw (calculate op) k
  | Sum (f, g) -> (calculate f) + (calculate g)
  | Diff (f, g) -> (calculate f) - (calculate g)
  | Prod (f, g) -> (calculate f) * (calculate g) ;;

let generate_random_calc ?plus_w:(pw=10) ?minus_w:(mw=8) ?prod_w:(uw=2) ?exp_w:(ew=0) (length : int) (inf : int) (sup : int) =
  let sum = pw + mw + uw + ew in
  let rec aux = function
    | 0 | 1 -> 
      Val (inf + Random.int (sup - inf))
    | k -> 
      let r = Random.int sum in 
      if r < pw then begin
        if Random.int 2 = 0 then
          Sum(aux 0, aux (k-1))
        else
          Sum(aux (k-1), aux 0)
      end
      else if r < pw+mw then begin
        if Random.int 2 = 0 then
          Diff(aux 0, aux (k-1))
        else
          Diff(aux (k-1), aux 0)
      end
      else if r < pw+mw+uw then begin
        if Random.int 2 = 0 then
          Prod(aux 0, aux (k-1))
        else
          Prod(aux (k-1), aux 0)
      end
      else begin
        Exp(aux (k-1), 2 + Random.int 2)
      end
  in
  aux length ;;

let draw_bracket_open ?retval:(rv=Drawing.fodder) (x : int) (y : int) (size : int) =
  moveto (x + size/4) (y+5*size/4) ;
  lineto (x) (y+size/2) ;
  lineto (x) (y-size/2) ;
  lineto (x + size/4) (y-5*size/4) ;
  rv := x + size/4 +6 ;;

let draw_bracket_close ?retval:(rv=Drawing.fodder) (x : int) (y : int) (size : int) =
  moveto (x) (y+5*size/4) ;
  lineto (x + size/4) (y+size/2) ;
  lineto (x + size/4) (y-size/2) ;
  lineto (x) (y-5*size/4) ;
  rv := x + size/4 +6 ;;

let draw_sign ?retval:(rv=Drawing.fodder) (x : int) (y : int) (len : int) = function
  | '+' ->
    draw_poly_line [|(x, y); (x + len, y)|] ;
    draw_poly_line [|(x+len/2, y + len/2); (x+len/2, y - len/2)|] ;
  | '-' ->
    draw_poly_line [|(x, y); (x + len, y)|] ;
  | 'x' ->
    Drawing.draw_letter x y 'x' len ;
  | _ -> failwith "incorrect\n" ;;

let require_bracket = function
    | Val _ -> false
    | _ -> true ;;

let int_of_bool = function
  | true -> 1
  | false -> 0 ;;

let fm_length (fm : operation) (size : int) =
  let rec aux = function
    | Val k -> size*11/7*(1 + Math.ln_b 10 k)
    | Exp(f, k) -> size*11/7 (* not implemented*)
    | Sum(f, g)
    | Diff(f, g)
    | Prod(f, g) ->
      size*10/7 + (int_of_bool (require_bracket f) + int_of_bool (require_bracket g))*(size/2+12) + aux f + aux g
      (* sign + brackets + recursive calls *)
  in aux fm ;;

let print_calc (f : operation) (x0 : int) (y0 : int) (size : int) =
  set_line_width 3 ;
  let fmlen = fm_length f size in
  let x = ref (x0)  in
  set_color (rgb 192 192 0) ;
  fill_rect (x0 -6) (y0 - size - 6) (fmlen+12) (2*size + 12) ;
  set_color black ;
  let rec aux = function
    | Val k -> 
      Drawing.draw_integer_alignedleft !x y0 k size ~retval:x ;
      (*Printf.printf "%d --> \n" !x ;*)
    | Exp (f, k) -> ()
    | Sum (f, g) ->
      if require_bracket f then draw_bracket_open !x y0 size ~retval:x ;
      aux f ;
      if require_bracket f then draw_bracket_close !x y0 size ~retval:x ;

      draw_sign !x y0 size '+' ~retval:x ;
      x := !x + size*10/7 ;
      
      if require_bracket g then draw_bracket_open !x y0 size ~retval:x ;
      aux g ;
      if require_bracket g then draw_bracket_close !x y0 size ~retval:x ;
    | Diff (f, g) ->
      if require_bracket f then draw_bracket_open !x y0 size ~retval:x ;
      aux f ;
      if require_bracket f then draw_bracket_close !x y0 size ~retval:x ;

      draw_sign !x y0 size '-' ~retval:x ;
      x := !x + size*10/7 ;

      if require_bracket g then draw_bracket_open !x y0 size ~retval:x ;
      aux g ;
      if require_bracket g then draw_bracket_close !x y0 size ~retval:x ;
    | Prod (f, g) ->
      if require_bracket f then draw_bracket_open !x y0 size ~retval:x ;
      aux f ;
      if require_bracket f then draw_bracket_close !x y0 size ~retval:x ;

      draw_sign !x y0 (size/2) 'x' ~retval:x ;
      x := !x + size*10/7 ;

      if require_bracket g then draw_bracket_open !x y0 size ~retval:x ;
      aux g ;
      if require_bracket g then draw_bracket_close !x y0 size ~retval:x ;
  in
  aux f ;;

let init_odds () = match !(Csts.difficulty) with
  | 1 ->
    Csts.plus_ch := 10 ;
    Csts.minus_ch := 0 ;
    Csts.prod_ch := 0 ;
  | 2 ->
    Csts.plus_ch := 0 ;
    Csts.minus_ch := 1 ;
    Csts.prod_ch := 0 ;
  | 3 -> 
    Csts.plus_ch := 7 ;
    Csts.minus_ch := 5 ;
    Csts.prod_ch := 0 ;
  | 4 ->
    Csts.plus_ch := 5 ;
    Csts.minus_ch := 5 ;
    Csts.prod_ch := 0 ;
  | 5 ->
    Csts.plus_ch := 5 ;
    Csts.minus_ch := 5 ;
    Csts.prod_ch := 1 ;
  | 6 ->
    Csts.plus_ch := 6 ;
    Csts.minus_ch := 4 ;
    Csts.prod_ch := 1 ;
  | 7 ->
    Csts.plus_ch := 7 ;
    Csts.minus_ch := 2 ;
    Csts.prod_ch := 2 ;
  | 8 ->
    Csts.plus_ch := 7 ;
    Csts.minus_ch := 2 ;
    Csts.prod_ch := 3 ;
  | 9 ->
    Csts.plus_ch := 6 ;
    Csts.minus_ch := 1 ;
    Csts.prod_ch := 4 ;
  | 10 ->
    Csts.plus_ch := 5 ;
    Csts.minus_ch := 0 ;
    Csts.prod_ch := 5 ;
  | _ -> failwith "unsupported\n" ;;

let sign = function
    | k when k < 0 -> -1
    | _ -> 1 ;;

let update_ans () =
  if key_pressed () then begin
    match Char.code (read_key ()) with
      | 45 (* minus *) ->
        Csts.ans := !Csts.ans * (-1)
      | 32 (* space *) | 8 (* del *) ->
        let s = sign !Csts.ans in
        Csts.ans := !Csts.ans * s ;
        Csts.ans := !Csts.ans / 10 ;
        Csts.ans := !Csts.ans * s ;
      | k when k >= 48 && k <= 57 (* digit *) ->
        let s = sign !Csts.ans in
        Csts.ans := !Csts.ans * s ;
        Csts.ans := 10 * !Csts.ans + k - 48 ;
        Csts.ans := !Csts.ans * s ;
      | x -> ()(*Printf.printf "%d -> %c\n" x (Char.chr x)*)
  end ;;

let grad_color (remt : float) (initt : float) =
  set_color (rgb (int_of_float (255. *. remt /. initt)) (int_of_float (255. *. (1. -. (remt /. initt)))) 0) ;;

let maeth_main () =
  let start = Unix.gettimeofday () in
  let cur = ref (Unix.gettimeofday ()) in
  let found = ref false in
  let equ = generate_random_calc ~plus_w:(!Csts.plus_ch) ~minus_w:(!Csts.minus_ch) ~prod_w:(!Csts.prod_ch) !Csts.calc_length !Csts.n_inf !Csts.n_sup in
  let resu = calculate equ in
  while not !found && (!cur -. start <= !Csts.time_to_ans_f) && (!cur -. !Csts.start_0) <= !Csts.total_time_f do
    auto_synchronize false ;
    clear_graph () ;

    set_color black ;
    print_calc equ (10) (Csts.__height__/2) 15 ;

    Drawing.draw_string_centered (Csts.__width__/2) (Csts.__height__/3) "Answer" 50 ;
    Drawing.draw_integer (Csts.__width__/2) (Csts.__height__/3 - 100) !Csts.ans 75 ;

    fill_rect (Csts.__width__ - 70) (Csts.__height__/4) 70 (Csts.__height__/2) ;
    set_color (rgb 129 255 0) ;
    fill_rect (Csts.__width__ - 60) (Csts.__height__/4 + 10) 50 (max 0 (Csts.__height__/2 - 20 - int_of_float (float_of_int (Csts.__height__/2 - 20) *. (!cur -. !Csts.start_0) /. !Csts.total_time_f))) ;

    grad_color (!cur -. start) (!Csts.time_to_ans_f) ;
    fill_rect 0 (7*Csts.__height__/8) (int_of_float ((float_of_int Csts.__width__) *. (!cur -. start) /. !Csts.time_to_ans_f)) (Csts.__height__/8+1) ;

    set_color (rgb 192 192 0) ;
    Drawing.draw_string 10 (Csts.__height__ - 40) "Score" 35 ;
    set_color (rgb 128 128 0) ;
    Drawing.draw_integer_alignedleft (10+(5+1)*35*10/7) (Csts.__height__ - 40) !Csts.score 35 ;

    set_color (rgb 0 128 128) ;
    Drawing.draw_integer_alignedright (Csts.__width__ - 20) (10+35) !Csts.combo 35 ;
    set_color (rgb 0 192 192) ;
    Drawing.draw_string (Csts.__width__ - 20 - 35*(5+1 + Math.ln_b 10 !Csts.combo)*11/7) (10+35) "Combo" 35 ;

    auto_synchronize true ;
    update_ans () ;
    if !Csts.ans = resu then
      found := true ;
    Unix.sleepf Csts.sleep_d ;
    cur := Unix.gettimeofday () ;
  done ;
  Csts.ans := 0 ;
  if (!cur -. !Csts.start_0) > !Csts.total_time_f then
    true
  else if !found then begin
    incr Csts.combo ;
    Csts.max_combo := max (!Csts.max_combo) !Csts.combo ;
    Csts.score := !Csts.score + int_of_float (
      (float_of_int !Csts.max_score) 
      *. (!Csts.time_to_ans_f -. (!cur -. start) /. 2.) /. !Csts.time_to_ans_f
      *. (1. +. Float.sqrt (float_of_int !Csts.combo))
    ) ;
    (* give 50% of max pts for correct answer + up to 50% more for speed *)
    true
  end
  else begin
    Csts.combo := 0 ;
    incr Csts.misses ;
    false
  end ;;

let result_screen () =
  try
    while true do
      auto_synchronize false ;
      clear_graph () ;

      set_color black ;
      Drawing.draw_string_centered (Csts.__width__/2) (Csts.__height__ - 60) "Press space to return to main" 22 ;

      set_color (rgb 192 192 0) ;
      Drawing.draw_string (Csts.__width__/6) (Csts.__height__/2 + 70) "Score" 20 ;
      set_color (rgb 255 255 0) ;
      Drawing.draw_integer_alignedleft (Csts.__width__/6+20*(5+1)*10/7) (Csts.__height__/2 + 70) !Csts.score 20 ;

      set_color (rgb 0 192 0) ;
      Drawing.draw_string (Csts.__width__/6) (Csts.__height__/2) "Max Combo" 20 ;
      set_color (rgb 0 255 0) ;
      Drawing.draw_integer_alignedleft (Csts.__width__/6+20*(9+1)*10/7) (Csts.__height__/2) !Csts.max_combo 20 ;

      set_color (rgb 0 192 32) ;
      Drawing.draw_string (Csts.__width__/6) (Csts.__height__/2 - 70) "Combo" 20 ;
      set_color (rgb 0 255 32) ;
      Drawing.draw_integer_alignedleft (Csts.__width__/6+20*(5+1)*10/7) (Csts.__height__/2 - 70) !Csts.combo 20 ;

      set_color (rgb 192 0 0) ;
      Drawing.draw_string (Csts.__width__/6) (Csts.__height__/2 - 140) "Misses" 20 ;
      set_color (rgb 255 0 0) ;
      Drawing.draw_integer_alignedleft (Csts.__width__/6+20*(6+1)*10/7) (Csts.__height__/2 - 140) !Csts.misses 20 ;

      if !Csts.misses = 0 then begin
        set_color (rgb 0 255 0) ;
        Drawing.draw_string_centered (Csts.__width__/2) (40) "FC" 35
      end ;

      auto_synchronize true ;
      Unix.sleepf Csts.sleep_d ;
      if key_pressed () && (read_key ()) == ' ' then
        raise Reset
    done; 
    ()
  with
    | Reset -> () ;;