open Parser_entry
open Affichage
open Proof
open Types
open Hlam

type entry =
  Simple of (unit -> cmd)
  | Reduce of Lam.lam
  | AlphaEquiv of Lam.lam * Lam.lam

type interactive_state = (hlam * ty) option * proof

let parse_lam t =
  match Parser.main Lexer.token t with
  | Lam l -> l
  | Cmd _ -> failwith "entry must be a lam"

let parse_cmd t =
  match Parser.main Lexer.token t with
  | Cmd c -> c
  | Lam _ -> failwith "entry must be a cmd"

let show_beta_reduction e =
  let rec aux = function
    Some e ->
      print_expr e;
      print_newline ();
      aux (Lam.betastep e);
    | None -> ()
  in print_expr e;
  print_newline ();
  let e = Lam.alpha_convert ~readable:true e in
  print_expr e;
  print_newline ();
  aux (Lam.betastep e)

let rec beta_reduce (l : Lam.lam) =
  match Lam.betastep l with
  | None -> l
  | Some l' -> beta_reduce l'

let clean_state ((s, p) : interactive_state) =
  let assoc, new_p = clean_proof p in
  match s with
  | None -> None, new_p
  | Some (hl, ty) -> Some (clean_hlam assoc hl, ty), new_p

let alpha_get_lam where_from =
  let input_str = In_channel.input_all where_from in
  match Str.split (Str.regexp "&") input_str with
    [s1; s2] -> AlphaEquiv (
          parse_lam (Lexing.from_string (s1^"\n")),
          parse_lam (Lexing.from_string s2)
        )
    | _ -> failwith "Alpha-equivalence: nombre de delimiteurs incorrect"

(** Interactive loop
  - cg : current top goal : type and reference to lambda-term
  - g, gs : next goals
  - sq : previous states of the interactive loop
*)
let rec interactive (get_cmd : unit -> cmd) (sl : (interactive_state) list) : proof =
  let (cg, (g, gs)), sq = match sl with
    [] -> (None, (None, [])), []
    | s::sq -> s, sq
  in
  begin
    let _ = match g with
    None -> print_string "\n\027[1mNo more goals.\027[0m\n"
    | Some g' -> print_newline (); print_goal g'
    in

    try
      match get_cmd () with
      Goal ty ->
        let rh = Ref (ref Hole) in
        [Some (rh, ty), (Some (rh, ty, []), [])] |> interactive get_cmd
      | Undo -> interactive get_cmd sq
      | Qed -> begin match cg with
        None ->
          print_error "No current goal" "";
          (cg, (g, gs))::sq |> interactive get_cmd
        | Some (h, t) ->
          let l = lam_of_hlam h
          |> beta_reduce in
          if Typing.typecheck [] l t then begin
            print_string "Ok";
            [None, (None, [])] |> interactive get_cmd
          end else begin
            print_error "Typing failed" "";
            print_expr l;
            print_newline ();
            print_ty t;
            (cg, (g, gs))::sq |> interactive get_cmd
          end
        end
      | Tact t ->
        (cg, (apply_tactic (g, gs) t))::(clean_state (cg, (g, gs)))::sq |> interactive get_cmd
    with
      Parser.Error ->
        print_error "Invalid input" "";
        (cg, (g, gs))::sq |> interactive get_cmd
      | TacticFailed arg ->
        print_error "Tactic failed" arg;
        (cg, (g, gs))::sq |> interactive get_cmd
      | End_of_file | Lexer.Eof ->
        print_string "Bye!\n";
        (g, gs)
  end

let nom_fichier = ref ""
let reduce = ref false
let alpha = ref false
let equiv_fichier = ref ""


let recupere_entree () =
  let optlist = [
   ("-alpha",
   Arg.Set alpha,
   "Vérifie l'alpha équivalence de deux termes séparés par &");
   ("-reduce",
   Arg.Set reduce,
   "Affiche les réductions successives du lambda-terme")
  ] in

  let usage = "Bienvenue à bord." in  (* message d'accueil, option -help *)

  Arg.parse (* ci-dessous les 3 arguments de Arg.parse : *)
    optlist (* la liste des options definie plus haut *)

    (fun s -> nom_fichier := s) (* la fonction a declencher lorsqu'on recupere un string qui n'est pas une option : ici c'est le nom du fichier, et on stocke cette information dans la reference nom_fichier *)
    usage; (* le message d'accueil *)

  try
    let where_from = match !nom_fichier with
      | "" -> stdin
      | s -> open_in s in
    if !alpha
    then alpha_get_lam where_from
    else if !reduce
    then Reduce (Lexing.from_channel where_from |> parse_lam)
    else Simple begin
    let cmd_buff = ref [] in
      if !nom_fichier = "" then
        (
          fun () ->
          match !cmd_buff with
          | [] ->
            begin match (read_line ())^"\n"
            |> Lexing.from_string
            |> parse_cmd with
              [] -> raise Parser.Error
              | e::q -> cmd_buff := q; e
            end
          | e::q -> cmd_buff := q; e
        )
      else
        (
          fun () ->
          match !cmd_buff with
          | [] ->
            begin match (input_line where_from)^"\n"
            |> Lexing.from_string
            |> parse_cmd with
              [] -> raise End_of_file
              | e::q -> cmd_buff := q; e
              end
          | e::q -> cmd_buff := q; e
        )
    end
  with e -> (print_error "problème de saisie" ""; raise e)

(* la fonction principale *)
let run () =
  try
    match recupere_entree () with
     Simple get_cmd ->
      let _ = interactive get_cmd [None, (None, [])] in ()
    | Reduce l ->
      let _ = show_beta_reduction l in ()
    | AlphaEquiv (l1, l2) -> begin
        if ((Lam.(=~)) l1 l2) then
          print_string "true\n"
        else
          print_string "false\n"
      end;
    flush stdout
  with e -> raise e

let _ = run ()