let __prefixes__ = [|"let"; "rec"; "and"; "for"; "mutable"|] ;;
let __keywords__ = [|"let"; "while"; "do"; "done"; "for"; "to"; "begin"; "end"; "try"; "with"; "raise"; "in"|] ;;
let fbd = [|' '; '\n'; '('; ')'; '['; ']'; '{'; '}'; ';'; ','; '.'; ':'; '*'; '|'; '-'; '+'; '*'; '/'; '='; '<'; '>'; '!'|] ;;

let concat_str (str : string ref) nstr =
  let n = String.length nstr in
  let i = ref 0 in
  while !i < n && nstr.[!i] = ' ' do
    incr i
  done;
  if !i <> n then begin
    let cct = String.init (n - !i) (fun k -> nstr.[k + !i]) in
    str := (!str)^cct^"\n" 
  end;;

let parse_the_whole_thing filename =
  let ptr = open_in filename in
  let res = ref "" in
  try
    while true do
      let line = input_line ptr in
      concat_str res line
      (*res := (!res)^line^"\n"*)
    done;
    "0 factorielle"
  with
    | End_of_file ->
      close_in ptr;
      !res ;;

let is_an_integer ch =
  Char.code ch >= 48 && Char.code ch <= 57 ;;

let to_list str =
  let n = String.length str in
  let rec aux acc i = match i with
    | k when k >= n -> acc
    | k ->
      let k1 = ref k
      and k2 = ref k in
      while !k2 < String.length str && (Array.mem str.[!k2] fbd || is_an_integer str.[!k2]) do
        incr k2;
        incr k1
      done;
      while !k2 < String.length str && not (Array.mem str.[!k2] fbd) do
        incr k2
      done;
      aux (((0, String.init (!k2 - !k1) (fun i -> str.[!k1 + i])), (!k1, !k2))::acc) (!k2+1)
  in
  List.rev (aux [] 0) ;;

let print_to_list (res : ((int * string) * (int * int)) list) =
  let rec aux = function
    | [] -> ()
    | ((b, str), (i, j))::t ->
      Printf.printf "[%d] %s --> (%d <-> %d)\n" b str i j ;
      aux t
  in
  aux res ;;

let random_string nmin nmax =
  String.init (Random.int (nmax - nmin) + nmin) (fun i -> Char.chr (97 + Random.int 25)) ;;

let detect_names (res : ((int * string) * (int * int)) list) = 
  let rec aux isname = function
    | [] -> []
    | ((status, str), (i, j))::t when isname ->
      if Array.mem str __prefixes__ then
        ((status, str), (i, j))::(aux true t)
      else
        ((1, str), (i, j))::(aux false t)
    | ((status, str), (i, j))::t ->
      ((status, str), (i, j))::(aux (Array.mem str __prefixes__) t)
  in
  aux false res ;;

let str_equal s1 s2 =
  if (String.length s1) <> (String.length s2) then
    false
  else
    Array.fold_left (fun acc v -> acc && fst v = snd v) true (Array.init (String.length s1) (fun i -> (s1.[i], s2.[i]))) ;;

let generate_conversion_hash (res : ((int * string) * (int * int)) list) =
  let hash = Hashtbl.create (List.length res +2) in
  let rec aux = function
    | [] -> ()
    |((isname, str), (i, j))::t ->
      if Hashtbl.find_opt hash str = None then begin
        if isname = 1 then
          Hashtbl.add hash str (random_string 10 11)
        else
          Hashtbl.add hash str str ;
      end;
      (*if Hashtbl.find_opt hash str = None then begin
        if not (Array.mem str __prefixes__) then
          Hashtbl.add hash str (random_string 10 11)
        else
          Hashtbl.add hash str str ;
      end;*)
      aux t
  in
  aux res ;
  hash ;;

let list_to_array (l : 'a list) =
  let hd = List.hd l in
  let n = List.length l in
  let res = Array.make n hd in
  let rec aux i = function
    | [] -> ()
    | h::t ->
      res.(i) <- h ;
      aux (i+1) t
  in
  aux 0 l;
  res ;;

let write_out (filename : string) str (ext : string) (words : ((int * string) * (int * int)) list) (hash : (string, string) Hashtbl.t) = 
  let ptr = open_out ("tests/"^filename^"_improved."^ext) in

  let n = String.length str in
  let i = ref 0 in

  let cwords = list_to_array words in
  let cindex = ref 0 in
  try
    while true do
      while !i < fst (snd cwords.(!cindex)) do (* write normally *)
        Printf.fprintf ptr "%c" str.[!i] ;
        incr i;
      done;
      Printf.fprintf ptr "%s" (Hashtbl.find hash (snd (fst cwords.(!cindex)))) ;
      i := (snd (snd cwords.(!cindex))) ;
      incr cindex ;
    done;
    close_out ptr ;
  with
    | Invalid_argument _ ->
      while !i < n do
        Printf.fprintf ptr "%c" str.[!i] ;
        incr i;
      done;
      close_out ptr ;;

let convert filename ext =
  let whole = parse_the_whole_thing (filename^"."^ext) in
  (*Printf.printf "%s" whole ;*)
  let words = to_list whole in
  print_to_list words ;
  let fnames = detect_names words in
  (*print_to_list fnames ;*)
  let conversion_hash = generate_conversion_hash fnames in
  (*Hashtbl.iter (fun k v -> Printf.printf "%s ----> %s\n" k v) conversion_hash ;*)
  write_out filename whole ext fnames conversion_hash ;;
  

let main () =
  if Array.length Sys.argv <> 3 then begin
    Printf.fprintf stderr "Usage : ./a.out <filename> <extension>\nNote : filename should not include the <.extension> (e.g. enter 'main' and not 'main.c' or 'main.ml')\n" ;
    assert false ;
  end else
    convert Sys.argv.(1) Sys.argv.(2) ;;

main () ;;