open Core.Std

open Codegen
open Utils

(* start and finish are the lines where the generated code should be copied *)
type t = {start: int;
          finish: int;
          file_text: string;
          template: string;
          suite_name_line: int option;
          suite_end: int option;
          suite_all_names_line: int option;
         } [@@deriving eq, show]

let find_template ~(template_text: string): t option =
  let open Option.Monad_infix in
  let str_contains pattern s = String.substr_index s ~pattern |> Option.is_some in
  let lines = String.split_lines template_text |> List.to_array in
  let suite_end = find_arrayi lines ~f:(str_contains "(* END SUITE *)") |> Option.map ~f:fst in
  let start_index = find_arrayi lines ~f:(str_contains "(* TEST") |> Option.map ~f:fst in
  let finish_index = (start_index >>= (fun start -> find_arrayi ~start lines ~f:(str_contains "END TEST *"))) |> Option.map ~f:fst in
  let template_lines = Option.map2 start_index finish_index (fun s -> Array.slice lines (s+1)) in
  let suite_name_line = find_arrayi lines ~f:(str_contains "(* SUITE *)$(suite_name)") |> Option.map ~f:fst in
  let suite_all_names_line = find_arrayi lines ~f:(str_contains "(* suite-all-names *)") |> Option.map ~f:fst in
  Option.map2 start_index template_lines ~f:(fun s l -> {start=s;
                                                         finish=(s + 1 + Array.length l);
                                                         file_text = template_text;
                                                         template=String.concat_array l ~sep:"\n";
                                                         suite_name_line = suite_name_line;
                                                         suite_end = suite_end;
                                                         suite_all_names_line = suite_all_names_line
                                                        })

let fill_tests (template: t) (substs: subst list): string =
  let lines = String.split_lines template.file_text |> List.to_array in
  let before = Array.slice lines 0 template.start in
  let subst = Array.of_list (List.map ~f:subst_to_string substs) in
  let after = Array.slice lines (template.finish + 1) (Array.length lines) in
  let join = String.concat_array ~sep:"\n" in
  String.concat [join before; join subst; join after] ~sep:"\n"

(* HACKS HERE *)
let fill_single_suite (template: t) (suite_substs: string * subst list): string =
  let (suite_name, substs) = suite_substs in
  let suite_name_line = Option.value_exn template.suite_name_line in
  let suite_end_line = Option.value_exn template.suite_end in
  let lines = String.split_lines template.file_text |> Fn.flip List.drop suite_name_line |> List.to_array in
  Array.replace lines 0 ~f:(String.substr_replace_all ~pattern:"(* SUITE *)$(suite_name)_tests" ~with_:(suite_name ^ "_tests"));
  let before = Array.slice lines 0 (template.start - suite_name_line) in
  let subst = Array.of_list (List.map ~f:subst_to_string substs) in
  let after = Array.slice lines (template.finish - suite_name_line + 1) (suite_end_line - suite_name_line) in
  let join = String.concat_array ~sep:"\n" in
  String.concat [join before; join subst; join after] ~sep:"\n"

(* HACKS HERE *)
let fill_suite (template: t) (suite_substs: (string * subst list) list): string =
  let fills = List.map suite_substs ~f:(fun x -> (fill_single_suite template x) ^ "\n") in
  let suite_name_line = Option.value_exn template.suite_name_line in
  let lines = String.split_lines template.file_text |> List.to_array in
  let before = Array.slice lines 0 suite_name_line in
  let subst = Array.of_list fills in
  let after = Array.slice lines (template.finish + 4) (Array.length lines) in
  let join = String.concat_array ~sep:"\n" in
  let generated = String.concat [join before; join subst; join after] ~sep:"\n" in
  let all_suite_names = String.concat ~sep:"; " @@ List.map ~f:(fun (s,_) -> s ^ "_tests") suite_substs in
  String.substr_replace_all generated ~pattern:"(* suite-all-names *)" ~with_:all_suite_names