From Undecidability.Shared.Libs.PSL Require Import FinTypes Vectors.
From Undecidability.L Require Import L_facts LM_heap_def.

From Coq Require Import List.
Import Vector.VectorNotations ListNotations.

From Undecidability.TM Require Import TM_facts ProgrammingTools WriteValue CaseList Copy ListTM Hoare.
From Undecidability.TM.L Require Import Alphabets Eval.
From Undecidability.TM.L.CompilerBoolFuns Require Import Compiler_spec Compiler_facts ClosedLAdmissible.

Set Default Proof Using "Type".

Section APP_right.

  Definition APP_right : pTM (sigPro)^+ unit 2 :=
    App _;;
    WriteValue ( [appT]%list) @ [||];;
    App _.

  Lemma APP_right_Spec :
    { f & s1 s2 : term,
    TripleT ≃≃([],[|Contains _ (compile );Contains _ (compile )|])
    (f ) APP_right ( _ ≃≃([],[|Contains _ (compile (L.app ));Void|])) }.
  Proof.
    evar (f : term term ).
    eexists f. [f]:intros .
    intros .
    unfold APP_right.
    hsteps_cbn.
    -rewrite app_assoc. reflexivity.
    -reflexivity.
    -unfold f. reflexivity.
  Qed.


End APP_right.

Lemma tabulate_nth [X : Type] [n : ] (v : Vector.t X n): tabulate ( i v[@i]) = v.
Proof. induction v;cbn. all:congruence. Qed.

Global Hint Rewrite Nat.eqb_refl tabulate_nth nth_tabulate: cleanupParamTM.

Ltac cleanupParamTM :=
  try fold Nat.add;rewrite ?Nat.eqb_refl, ?tabulate_nth, ?nth_tabulate;cbn.

Section MK_isVoid.

  Context {Σ : finType}.

  Definition MK_isVoid : pTM Σ^+ unit 1 :=
    Write (inl UNKNOWN).

  Lemma MK_isVoid_Sem :
    RealiseIn MK_isVoid ( t '(r, t') t = [| niltape|] isVoid (t'[@])) 1.
  Proof.
    eapply RealiseIn_monotone.
    { unfold MK_isVoid. TM_Correct. }
    easy.
    intros ? [] H . hnf in H. cbn in *. rewrite H. hnf. eauto.
  Qed.


  Lemma MK_isVoid_Spec :
   TripleT ≃≃([],[|Custom (eq niltape)|]) 1 MK_isVoid ( _ ≃≃([],[|Void|])).
  Proof.
    eapply RealiseIn_TripleT. now apply MK_isVoid_Sem. cbn. intros ? ? ? ? [_ H']%tspecE.
    specialize (H' ). eapply tspecI. easy. intros i; destruct_fin i;cbn. apply H. now vector_destruct tin.
  Qed.


  Fixpoint MK_isVoid_multi m' : pTM (Σ) ^+ unit m' :=
    match m' with
    0 Nop
    | S m' MK_isVoid @ [||];;MK_isVoid_multi (tabulate ( i Fin.FS i))
    end.

  Lemma helper n m i f:
    injective f
      lookup_index_vector (m:=m) (n:=n)
    (tabulate f)
    (f i) = Some i.
  Proof.
    revert n i f. induction m;cbn. easy.
    intros n i f Hinj. specialize (Fin.eqb_eq _ (f i) (f )) as H'.
    destruct Fin.eqb.
    -destruct H' as [H' _]. specialize (H' eq_refl). apply Hinj in H' as . easy.
    -destruct H' as [_ H']. destruct (fin_destruct_S i) as [[i' ]| ]. 2:now discriminate H'.
     specialize IHm with (f:= m f (Fin.FS m) ). cbn in IHm. rewrite IHm. easy. intros ? ? ?%Hinj. now apply Fin.FS_inj.
  Qed.


  Instance Proper_tabulate X n: Proper (pointwise_relation _ eq eq) (@tabulate X n).
  Proof.
    unfold pointwise_relation.
    induction n;hnf;intros f g H;cbn. reflexivity. f_equal. apply H. now apply IHn.
  Qed.


  Theorem MK_isVoid_multi_SpecT m' :
  TripleT ≃≃([],Vector.const (Custom (eq niltape)) m')
    (m'*2) (MK_isVoid_multi m')
    ( _ ≃≃([],Vector.const Void m')).
  Proof using All.
    induction m';cbn. now hsteps_cbn.
    hstep. {hsteps_cbn;cbn. apply MK_isVoid_Spec. }
    cbn;intros _. rewrite Nat.eqb_refl,tabulate_nth;cbn.
    eapply ConsequenceT.
    {
      eapply LiftTapes_SpecT with (Q:= _ _)(Q':= _ _) (P:= Void:::Vector.const (Custom (eq niltape)) _). now eapply dupfree_tabulate_injective,Fin.FS_inj.
      eapply ConsequenceT . eassumption. 2:cbn;intro;reflexivity. 2:reflexivity.
      unfold Downlift,select. rewrite Vector_map_tabulate. cbn. now rewrite tabulate_nth.
    }
    1,3,4:reflexivity. cbn. intros _.
    rewrite lookup_index_vector_None. 2:{rewrite in_tabulate. intros (?&?). congruence. }
    eapply EntailsI. intros ? [_ H]%tspecE. eapply tspecI. easy. intros i. cbn.
    specialize (H i);cbn in H. destruct (fin_destruct_S i) as [[i' ]| ];cbn in H|-*. 2:easy.
    rewrite nth_tabulate in H. rewrite helper in H. easy. hnf. eapply Fin.FS_inj.
  Qed.


End MK_isVoid.
Opaque MK_isVoid_multi.

From Undecidability Require Import TM.L.Transcode.Boollist_to_Enc.
From Undecidability Require Import EncBoolsTM_boolList.

Section mk_init_one.

  Variable Σ : finType.

  Variable s b : Σ^+.
  Variable (H_disj : s b).

  Variable (retr_pro : Retract sigPro Σ).
  Variable (retr_list : Retract (sigList bool) Σ).


  Definition M_init_one : pTM (Σ) ^+ unit 6:=
    encBoolsTM2boollist.M s retr_list @[|;|];;
    Rev _ retr_list @ [|;;|];;
    BoollistToEnc.M retr_list retr_pro @[|;;;|];;
    APP_right retr_pro @[|;|].


  Lemma M_init_one_Spec :
    { f & (bs:list bool) (ter : L.term),
    TripleT ≃≃([],[|Custom (eq (encBoolsTM s b bs));Contains _ (compile ter);Void;Void;Void;Void|])
    (f bs ter) M_init_one ( _ ≃≃([],[|Custom (eq (encBoolsTM s b bs));Contains _ (compile (L.app ter (encBoolsL bs)));Void;Void;Void;Void|])) }.
  Proof using H_disj.
    evar (f : list bool term ).
    eexists f. [f]:intros bs ter.
    intros bs ter.
    unfold M_init_one.
    hstep. { hsteps_cbn. cbn. eapply ( (encBoolsTM2boollist.SpecT _ H_disj)). }
    cbn. intros _. hstep. { hsteps_cbn. cbn. tspec_ext. } 2:reflexivity.
    cbn. intros _. hstep.
    {
      hsteps_cbn. cbn. eapply ConsequenceT. eapply ( (@BoollistToEnc.SpecT _ _ _) (rev bs)).
      all:cbn. now tspec_ext. now rewrite rev_involutive. reflexivity.
    } 2:reflexivity.
    cbn. intros _. hsteps_cbn.
     now eapply ( (APP_right_Spec)). 1,2:now cbn;tspec_ext.
     subst f. cbn . reflexivity.
  Qed.


End mk_init_one.

Section mk_init.

  Variable Σ : finType.
  Variable s b : Σ^+.
  Variable (H_disj : s b).

  Context (retr_pro : Retract sigPro Σ) (retr_bools : Retract (sigList bool) Σ).

  Variable m k : .

  Variable sim : term.

  Notation aux n := (Fin.L k (Fin.L m n)) (only parsing).
  Notation auxm n := (Fin.L k (Fin.R 6 n)) (only parsing).
  Notation auxk n := (Fin.R (6 + m) n) (only parsing).

  Fixpoint M_init' k' : (Vector.t (Fin.t k) k') pTM (Σ) ^+ unit ((6 + m)+ k).
  Proof using m s retr_bools sim retr_pro.
    simple notypeclasses refine (match k' with
    0 _ MK_isVoid_multi _ @ [|aux ;aux ;aux ;aux ; aux |];;
        WriteValue ( (compile sim)) retr_pro @ [|aux |]
    | S k'
     ren
      _;;M_init_one s retr_pro retr_bools @ [|auxk (ren[@]);aux ;aux ;aux ;aux ;aux |]
    end). all:try exact _.
    2:{apply (M_init' _ (Vector.tl ren)). }
  Defined.

  Theorem M_init'_SpecT k' (ren :Vector.t (Fin.t k) k') (v:Vector.t (list bool) k):
  { k &
  TripleT ≃≃([],Vector.const (Custom (eq niltape)) (6+m) Vector.map ( bs Custom (eq (encBoolsTM s b bs))) v)
    k (M_init' ren)
    ( _ ≃≃([],
      ([|Custom (eq niltape);
        Contains retr_pro (compile (Vector.fold_right ( l_i s L.app s (encBoolsL l_i)) (select ren v) sim))
         ;Void;Void;Void;Void|]Vector.const (Custom (eq niltape)) m) Vector.map ( bs Custom (eq (encBoolsTM s b bs))) v))}.
  Proof using All.
    induction ren. all:cbn [compile Vector.fold_left M_init' Vector.tl Vector.caseS].
    {
      eexists. cbn.
      hstep. hsteps_cbn;cbn. exact (MK_isVoid_multi_SpecT 5). cbn;cleanupParamTM. 2:reflexivity.
      hsteps_cbn. reflexivity.
      cleanupParamTM.
      apply EntailsI. intros t H. eapply tspec_ext. eassumption. easy.
      intros i. clear - i.
      repeat (destruct (fin_destruct_S i) as [(i'&) | ];[rename i' into i;cbn| ]);try (intros H;exact H).
    }
    {
      eexists. cbn. hstep.
      3:reflexivity. now apply ( (IHren)). clear IHren.
      cbn. intros _. hstep. { cbn. rewrite Vector_nth_R,nth_map'. cbn. eapply ( (M_init_one_Spec H_disj _ _)). }
      cbn;fold Nat.add;rewrite Nat.eqb_refl;cbn. intros _.
      apply EntailsI. intros t H. eapply tspec_ext. eassumption. easy.
      intros i. clear - i.
      repeat (destruct (fin_destruct_S i) as [(i'&) | ];[rename i' into i;cbn| ]);try (intros H;exact H).
      rewrite nth_tabulate. destruct (Fin.eqb _ _) eqn:H'. 2:tauto.
      cbn. eapply Fin.eqb_eq in H' as . rewrite Vector_nth_R,nth_map'. cbn. tauto.
    }
  Qed.


  Program Definition startRen := Vectors.tabulate (n:=k) ( i Fin.of_nat_lt (n:=k) (p:=k - 1 -proj1_sig (Fin.to_nat i)) _).
  Next Obligation.
  destruct Fin.to_nat;cbn. nia.
  Defined.
  Lemma startRen_spec A (v:Vector.t A _): select startRen v = Vector.rev v.
  Proof.
    unfold select.
    apply eq_nth_iff'. intros i. rewrite nth_map'.
    unfold startRen.
    unshelve erewrite nth_tabulate, vector_nth_rev. 1:abstract (inv i;nia).
    f_equal. eapply Fin.of_nat_ext.
  Qed.



  Import CasePair Code.CaseList.


  Definition M_init : pTM (Σ) ^+ unit ((6 + m)+ k) :=
    M_init' startRen.


  Theorem M_init_SpecT (v:Vector.t (list bool) k):
  { k &
  TripleT ≃≃([],Vector.const (Custom (eq niltape)) (6+m) Vector.map ( bs Custom (eq (encBoolsTM s b bs))) v)
    k M_init
    ( _ ≃≃([],
      ([|Custom (eq niltape);
        Contains retr_pro (compile (Vector.fold_left ( s l_i L.app s (encBoolsL l_i)) sim v));
         Void;Void;Void;Void|]
         Vector.const (Custom (eq niltape)) m) Vector.map ( bs Custom (eq (encBoolsTM s b bs))) v))}.
  Proof using H_disj.
    eexists. unfold M_init.
    eapply ConsequenceT. eapply ( (M_init'_SpecT _ _)). reflexivity. 2:reflexivity.
    cbn. intros _.
    rewrite vector_fold_left_right with (v:=v), (startRen_spec v).
    apply EntailsI. intros t H. eapply tspec_ext. eassumption. easy.
    intros i. clear - i.
    repeat (destruct (fin_destruct_S i) as [(i'&) | ];[rename i' into i;cbn| ]);try (intros H;exact H).
  Qed.


End mk_init.

From Undecidability Require Import Enc_to_Boollist.

Section conv_output.

  Variable Σ : finType.
  Variable s b : Σ^+.
  Variable (retr_pro : Retract sigPro Σ).
  Variable (retr_list : Retract (sigList bool) Σ).


  Definition M_out : pTM (Σ) ^+ unit 4 :=
    EncToBoollist.M _ _ @ [|;;|];;
    Boollist2encBoolsTM.M s b _ @ [|;;|].


  Theorem M_out_SpecT bs:
    { k &
    TripleT ≃≃([],[|Contains _ (compile (encBoolsL bs));Custom (eq niltape);Void;Void|])
      k M_out
      ( _ ≃≃([],
        ([|Custom ( _ True); Custom (eq (encBoolsTM s b bs)); Void;Void|])))}.
  Proof.
    eexists. unfold M_out. hsteps_cbn;cbn.
    -eapply ( (@EncToBoollist.SpecT _ _ _)).
    -eapply ( (Boollist2encBoolsTM.SpecT _ _ _)).
    - cbn. rewrite rev_involutive. tspec_ext. easy.
    -reflexivity.
  Qed.


End conv_output.

Section main.

  Variable k : .
  Variable (R : Vector.t (list bool) k (list bool) Prop).

  Variable s : term.

  Hypothesis Hscl : closed s.

  Variable : ( v, m : list bool,
   R v m
   L.eval (Vector.fold_left ( (s0 : term) (n : list bool) L.app (encBoolsL n)) s v) (encBoolsL m)).

  Variable : ( v, o : term,
                     L.eval (Vector.fold_left (n := k) ( (s0 : term) (n : list bool) L.app (encBoolsL n)) s v) o
                      m : list bool, o = encBoolsL m).

  Definition n_main := 14.

  Definition Σ : Type := sigList bool + EvalL.Σintern.

  Definition retr_bools : Retract (sigList bool) Σ := (Retract_inl _ _).
  Definition retr_intern : Retract EvalL.Σintern Σ := (Retract_inr _ _).

  Definition retr_closs : Retract (sigList sigHClos) Σ
  := ComposeRetract retr_intern _.
  Definition retr_clos : Retract sigHClos Σ := ComposeRetract retr_closs _.
  Definition retr_pro : Retract sigPro Σ := ComposeRetract retr_clos _.

  Definition sym_s : Σ^+ := inr (inl (sigList_X true)).
  Definition sym_b : Σ^+ := inr (inl (sigList_X false)).


  Notation aux n := (Fin.L k (Fin.R 1 n)).

  Definition garb : Σ^+ := inl UNKNOWN.

  Definition M_main : pTM (Σ ^+) unit (1 + n_main + k):=
        M_init sym_s retr_pro retr_bools (1 + n_main - 6) k s ;;
        MK_isVoid_multi _ @ [|aux ;aux ;aux ;aux ;aux ;aux ;aux ;aux ;aux |] ;;
        EvalL.M retr_intern retr_pro @ [| aux ; aux ; aux ; aux ; aux ; aux ; aux ; aux ; aux ; aux ; aux |] ;;
        M_out sym_s sym_b retr_pro retr_bools @ [|aux ;Fin.L k ;aux ;aux |].

  Lemma syms_diff : sym_s sym_b. Proof. cbv. congruence. Qed.

  Theorem M_main_Spec v:
    Triple ≃≃([],Vector.const (Custom (eq niltape)) (S n_main) Vector.map ( bs Custom (eq (encBoolsTM sym_s sym_b bs))) v)
       M_main
      ( _ t m, t ≃≃ ([R v m]%list,
        (Custom (eq (encBoolsTM sym_s sym_b m)):::Vector.const (Custom ( _ True)) _))).
  Proof using Hscl .
    unfold M_main.
    hstep. { eapply TripleT_Triple,( (M_init_SpecT syms_diff _ _ _ _ _)). }
    cbn. intros _.
    start_TM.
    hstep. {hsteps_cbn;cbn. eapply TripleT_Triple. exact (MK_isVoid_multi_SpecT 9). }
    cbn;cleanupParamTM. intros _.
    hstep. {eapply LiftTapes_Spec_ex. now smpl_dupfree. cbn. refine (EvalL.Spec _ _ _). now eapply closed_compiler_helper. }
    cbn;cleanupParamTM.
    intros _. eapply Triple_exists_pre. intros s'.
    change ( x ?f x) with f.
    hintros Heval%eval_iff. specialize ( Heval) as (res&).
    unfold_abbrev.
    eapply Triple_exists_con. eexists res.
    hstep. { eapply Consequence_pre. eapply TripleT_Triple. refine ( (M_out_SpecT _ _ _ _ res)). cbn. reflexivity. }
    cbn;cleanupParamTM. intros _.
    apply in Heval.
    eapply EntailsI. intros tin [[] Hin]%tspecE.
    eapply tspecI;cbn. easy.
    intros i. specialize (Hin i). destruct_fin i;try exact Logic.I;cbn in *.
    now simpl_vector. eassumption.
  Qed.


Theorem M_main_SpecT v res:
  R v res k__steps,
  TripleT ≃≃([],Vector.const (Custom (eq niltape)) (S n_main) Vector.map ( bs Custom (eq (encBoolsTM sym_s sym_b bs))) v)
  k__steps M_main
    ( _ ≃≃([],Custom (eq (encBoolsTM sym_s sym_b res)):::Vector.const (Custom ( _ True)) _)).
Proof using Hscl .
  unfold M_main. intros ?.
  apply in H as H%eval_iff. eapply eval_evalIn in H as [? H].
  eexists.
  hstep. { eapply ( (M_init_SpecT syms_diff _ _ _ _ _)). } 2:reflexivity.
  cbn. intros _.
  start_TM.
  hstep. {hsteps_cbn;cbn. exact (MK_isVoid_multi_SpecT 9). } 2:reflexivity.
  cbn;cleanupParamTM. intros _.
  hstep. { eapply LiftTapes_SpecT. now smpl_dupfree. cbn. apply @EvalL.SpecT. }
  cbn;cleanupParamTM.
  intros _.
  hstep. now refine ( (M_out_SpecT _ _ _ _ res)).
  cbn;cleanupParamTM. intros _.
  eapply EntailsI. intros tin [[] Hin]%tspecE.
  eapply tspecI;cbn. easy.
  intros i. specialize (Hin i). destruct_fin i;try exact Logic.I;cbn in *.
  now simpl_vector. eassumption. reflexivity.
  Unshelve. 3:easy. now eapply closed_compiler_helper.
Qed.


End main.

Theorem compiler_correct {k} (R : Vector.t (list bool) k (list bool) Prop) :
  L_bool_computable_closed R TM_bool_computable_hoare' R.
Proof.
  intros H.
  destruct H as [sim [cs Hsim]].
  hnf.
  eexists _, _, sym_s, sym_b. split. eapply syms_diff. exists (M_main k sim).
  intros v. split.
  - eapply M_main_Spec. easy. all:firstorder.
  - eapply M_main_SpecT. easy. all:firstorder.
Qed.