Require Import List Arith Lia.

From Undecidability.Shared.Libs.DLW
  Require Import utils gcd pos vec subcode sss.

From Undecidability.MinskyMachines.MMA
  Require Import mma_defs mma_utils.

From Undecidability.FRACTRAN
  Require Import FRACTRAN fractran_utils.

Set Implicit Arguments.

Set Default Proof Using "Type".

Tactic Notation "rew" "length" := autorewrite with length_db.

Local Notation "e #> x" := (vec_pos e x).
Local Notation "e [ v / x ]" := (vec_change e x v).
Local Notation ø := vec_nil.

Local Notation "P //ₐ s -+> t" := (sss_progress (@mma_sss _) P s t) (at level 70, no associativity).
Local Notation "P //ₐ s ->> t" := (sss_compute (@mma_sss _) P s t) (at level 70, no associativity).
Local Notation "P //ₐ s ~~> t" := (sss_output (@mma_sss _) P s t) (at level 70, no associativity).
Local Notation "P //ₐ s ↓" := (sss_terminates (@mma_sss _) P s) (at level 70, no associativity).

Local Notation "Q /F/ x ≻ y" := (fractran_step Q x y) (at level 70, no associativity).
Local Notation "Q /F/ x ⊁ *" := (fractran_stop Q x) (at level 70, no associativity).


Section Fractran_with_two_counters.

  Hint Resolve subcode_refl : core.
  Hint Rewrite mma_jump_length mma_null_length mma_transfert_length mma_incs_length mma_decs_copy_length
               mma_mult_cst_length mma_decs_length mma_mod_cst_length mma_div_cst_length : length_db.

  Notation JUMP := mma_jump.
  Notation NULL := mma_null.
  Notation TRANSFERT := mma_transfert.
  Notation INCS := mma_incs.
  Notation DECS := mma_decs.
  Notation DECS_COPY := mma_decs_copy.
  Notation MULT_CST := mma_mult_cst.
  Notation MOD_CST := mma_mod_cst.
  Notation DIV_CST := mma_div_cst.
  Notation LOOP := mma_loop.

  Let src : pos 2 := pos0.
  Let dst : pos 2 := pos1.

  Ltac dest x y := destruct (pos_eq_dec x y) as [ | ]; [ subst x | ]; rew vec.

  Let Hsrc_dst : src <> dst. Proof. discriminate. Qed.
  Let Hdst_src : dst <> src. Proof. discriminate. Qed.

  Section mma_fractran_one.


    Variables (p q : nat) (j i : nat).

    Notation i0 := i.
    Notation i1 := (5+p+i0).
    Notation i2 := (6+4*q+i1).
    Notation i3 := (5+3*q+i2).
    Notation i4 := (3+i3).
    Notation i5 := (2+i4).
    Notation i6 := (5+3*p+i5).

    Definition mma_fractran_one :=
           MULT_CST src dst p i0
        ++ MOD_CST dst src i2 i5 q i1
        ++ DIV_CST src dst q i2
        ++ TRANSFERT dst src i3
        ++ JUMP j dst
        ++ DIV_CST src dst p i5
        ++ TRANSFERT dst src i6.

    Fact mma_fractran_one_length : length mma_fractran_one = 29+4*p+7*q.
    Proof. unfold mma_fractran_one; rew length; lia. Qed.

    Hypothesis (Hq : q <> 0).


    Fact mma_fractran_one_ok_progress k v :
            k*q = p*(v#>src)
         -> v#>dst = 0
         -> (i0,mma_fractran_one) //ₐ (i0,v) -+> (j,v[k/src]).
    Proof using Hq.
      intros H1 H2; unfold mma_fractran_one.
      apply sss_progress_trans with (i1,v[0/src][(k*q)/dst]).
      { apply subcode_sss_progress with (P := (i,mma_mult_cst src dst p i)); auto.
        apply mma_mult_cst_progress; auto.
        rewrite H2, <- H1; do 2 f_equal; lia. }
      apply sss_progress_trans with (i2,v[(k*q)/src][0/dst]).
      { apply subcode_sss_progress with (P := (i1,mma_mod_cst dst src i2 i5 q i1)); auto.
        apply mma_mod_cst_divides_progress with k; rew vec; try lia.
        f_equal; apply vec_pos_ext; intros y; dest y dst; try lia; dest y src. }
      apply sss_progress_trans with (i3,v[0/src][k/dst]).
      { apply subcode_sss_progress with (P := (i2,mma_div_cst src dst q i2)); auto.
        apply mma_div_cst_progress with k; auto; rew vec; try lia.
        f_equal; try lia.
        apply vec_pos_ext; intros y; dest y dst; try lia; dest y src. }
      apply sss_progress_trans with (i4,v[k/src][0/dst]).
      { apply subcode_sss_progress with (P := (i3,mma_transfert dst src i3)); auto.
        apply mma_transfert_progress; auto.
        f_equal; try lia.
        apply vec_pos_ext; intros y; dest y dst; try lia; dest y src. }
      apply sss_progress_compute_trans with (j,v[k/src][0/dst]).
      { apply subcode_sss_progress with (P := (i4,JUMP j dst)); auto.
        apply mma_jump_progress; auto. }
      mma sss stop; f_equal.
      apply vec_pos_ext; intros y; dest y dst; try lia; dest y src.
    Qed.


    Fact mma_fractran_one_nok_progress v :
            ~ divides q (p*(v#>src))
         -> v#>dst = 0
         -> (i0,mma_fractran_one) //ₐ (i0,v) -+> (length mma_fractran_one+i0,v).
    Proof using Hq.
      rewrite mma_fractran_one_length.
      intros H1 H2; unfold mma_fractran_one.
      rewrite divides_rem_eq in H1.
      revert H1.
      generalize (div_rem_spec1 (p*(v#>src)) q)
                 (div_rem_spec2 (p*(v#>src)) Hq).
      generalize (div (p*(v#>src)) q) (rem (p*(v#>src)) q); intros x y H3 H4 H5.
      apply sss_progress_trans with (i1,v[0/src][(x*q+y)/dst]).
      { apply subcode_sss_progress with (P := (i0,mma_mult_cst src dst p i0)); auto.
        apply mma_mult_cst_progress; auto.
        rewrite H2, <- H3; do 2 f_equal; lia. }
      apply sss_progress_trans with (i5,v[(p*(v#>src))/src][0/dst]).
      { apply subcode_sss_progress with (P := (i1,mma_mod_cst dst src i2 i5 q i1)); auto.
        apply mma_mod_cst_not_divides_progress with x y; rew vec; try lia.
        f_equal; apply vec_pos_ext; intros c; dest c dst; try lia; dest c src; lia. }
      apply sss_progress_trans with (i6,v[0/src][(v#>src)/dst]).
      { apply subcode_sss_progress with (P := (i5,mma_div_cst src dst p i5)); auto.
        apply mma_div_cst_progress with (v#>src); auto; rew vec; try lia; try ring.
        f_equal; try lia.
        apply vec_pos_ext; intros c; dest c dst; try lia; dest c src. }
      apply subcode_sss_progress with (P := (i6,mma_transfert dst src i6)); auto.
      apply mma_transfert_progress; auto.
      f_equal; try lia.
      apply vec_pos_ext; intros c; dest c dst; try lia; dest c src.
    Qed.

  End mma_fractran_one.

  Notation FRAC_ONE := mma_fractran_one.

  Hint Rewrite mma_fractran_one_length : length_db.

  Section mma_fractran_step.


    Variable (j : nat).

    Fixpoint mma_fractran_step Q i { struct Q } :=
      match Q with
        | nil => nil
        | (p,q)::Q => let P := FRAC_ONE p q j i
                      in P ++ mma_fractran_step Q (length P+i)
      end.

    Notation FRAC_STEP := mma_fractran_step.

    Fact mma_fractran_step_success_progress Q i x y :
            fractran_regular Q
         -> Q /F/ x y
         -> (i,mma_fractran_step Q i) //ₐ (i,x##0##ø) -+> (j,y##0##ø).
    Proof.
      unfold fractran_regular.
      intros H1 H2; revert H2 i H1.
      induction 1 as [ p q ll x y H1 | p q ll x y H1 H2 IH2 ];
        intros i H3; rewrite Forall_cons_inv in H3; destruct H3 as (H3 & H4); simpl in H3, H4;
        unfold mma_fractran_step; fold mma_fractran_step.
      + apply subcode_sss_progress with (P := (i,mma_fractran_one p q j i)); auto.
        apply mma_fractran_one_ok_progress with (v := _##_##ø); auto.
        simpl; rewrite <- H1; ring.
      + apply sss_progress_trans with (length (mma_fractran_one p q j i)+i,x##0##ø).
        { apply subcode_sss_progress with (P := (i,mma_fractran_one p q j i)); auto.
          apply mma_fractran_one_nok_progress; auto. }
        { apply subcode_sss_progress
            with (P := (length (mma_fractran_one p q j i)+i,
                        mma_fractran_step ll (length (mma_fractran_one p q j i)+i))); auto. }
    Qed.

    Fact mma_fractran_step_failure_compute Q i x :
            fractran_regular Q
         -> Q /F/ x *
         -> (i,mma_fractran_step Q i) //ₐ (i,x##0##ø) ->> (length (mma_fractran_step Q i)+i,x##0##ø).
    Proof.
      unfold fractran_regular.
      intros H1 H2; revert H1 i H2.
      induction 1 as [ | (p,q) ll Hq Hll IH ]; intros i H4.
      + mma sss stop.
      + apply fractan_stop_cons_inv in H4 as (H4 & H5).
        unfold mma_fractran_step; fold mma_fractran_step.
        set (P := mma_fractran_one p q j i); rew length.
        apply sss_compute_trans with (length P+i,x##0##ø).
        * apply subcode_sss_compute with (P := (i,P)); auto.
          apply sss_progress_compute, mma_fractran_one_nok_progress; auto.
        * apply subcode_sss_compute with (P := (length P+i,mma_fractran_step ll (length P + i))); auto.
          replace (length P+length (mma_fractran_step ll (length P + i))+i)
          with (length (mma_fractran_step ll (length P + i)) + (length P+i)) by lia; auto.
    Qed.

  End mma_fractran_step.

  Section fractran_mma.

    Variables (Q : list (nat*nat)) (HQ : fractran_regular Q).

    Definition fractran_mma := mma_fractran_step 1 Q 1.

    Lemma fractran_mma_sound x y :
               fractran_compute Q x y
            -> Q /F/ y *
            -> (1,fractran_mma) //ₐ (1,x##0##ø) ->> (length fractran_mma+1,y##0##ø).
    Proof using HQ.
      intros (u & H2) H3.
      revert x y H2 H3.
      induction u as [ | u IHu ]; simpl; intros x y H2 H3.
      + rewrite H2; apply mma_fractran_step_failure_compute; auto.
      + destruct H2 as (z & H2 & H4).
        apply mma_fractran_step_success_progress
          with (1 := HQ) (j := 1) (i := 1) in H2; auto.
        apply IHu with (x := z) in H3; auto; rew vec.
        apply sss_compute_trans with (2 := H3).
        apply sss_progress_compute; auto.
    Qed.

    Lemma fractran_mma_complete x :
               (1,fractran_mma) //ₐ (1,x##0##ø)
            -> Q /F/ x .
    Proof using HQ.
      intros ((j,w) & (k & H2) & H3); simpl fst in H3.
      revert x H2; induction on k as IHu with measure k; intros x H2.
      destruct (fractran_step_dec Q x) as [ (y & Hy) | H4 ].
      2: { exists x; split; auto; exists 0; simpl; auto. }
      generalize Hy; intros H4.
      apply mma_fractran_step_success_progress
        with (1 := HQ) (j := 1) (i := 1)
        in H4; auto.
      fold fractran_mma in H4.
      destruct subcode_sss_progress_inv with (4 := H4) (5 := H2)
        as (r & H5 & H6); auto.
      1: apply mma_sss_fun.
      apply IHu in H6; auto; rew vec.
      destruct H6 as (z & (u & Hk) & Hz2).
      exists z; split; auto; exists (S u), y; split; auto.
    Qed.

    Theorem fractran_mma_reduction x :
        Q /F/ x <-> (1,fractran_mma) //ₐ (1,x##0##ø) .
    Proof using HQ.
      split; auto.
      + intros (y & H2 & H3).
        exists (length fractran_mma+1,y##0##vec_nil); split; simpl; auto; try lia.
        apply fractran_mma_sound; auto.
      + apply fractran_mma_complete; auto.
    Qed.

  End fractran_mma.

  Section fractran_mma0.

    Variables (Q : list (nat*nat)) (HQ : fractran_regular Q).

    Let P := fractran_mma Q.

    Definition fractran_mma0 := P ++ NULL src (length P + 1) ++ JUMP 0 src.

    Notation FRAC_MMA := fractran_mma0.

    Let fmma0_1 x : (1,FRAC_MMA) //ₐ (length P+1,x##0##ø) -+> (0,0##0##ø).
    Proof.
      unfold fractran_mma0.
      apply subcode_sss_progress with (P := (length P+1, NULL src (length P + 1) ++ JUMP 0 src)); auto.
      apply sss_progress_trans with (length (NULL src (length P + 1)) + length P+1,0##0##ø).
      + apply subcode_sss_progress with (P := (length P+1,NULL src (length P+1))); auto.
        apply mma_null_progress; auto.
      + apply subcode_sss_progress with (2 := mma_jump_progress _ src _ eq_refl); auto.
    Qed.

    Lemma fractran_mma0_sound x y :
               fractran_compute Q x y
            -> fractran_stop Q y
            -> (1,FRAC_MMA) //ₐ (1,x##0##ø) ->> (0,0##0##ø).
    Proof using HQ.
      intros H2 H3.
      unfold fractran_mma0.
      apply sss_compute_trans with (length P+1,y##0##ø).
      + apply subcode_sss_compute with (P := (1,P)); auto.
        apply fractran_mma_sound with (2 := H2); auto.
      + apply sss_progress_compute, fmma0_1; auto.
    Qed.

    Variable x : nat.

    Let Term1 := Q /F/ x .
    Let Term2 := (1,FRAC_MMA) //ₐ (1,x##0##ø) ~~> (0,0##0##ø).
    Let Term3 := (1,FRAC_MMA) //ₐ (1,x##0##ø) .

    Theorem fractran_mma0_reduction :
         (Term1 -> Term2) /\ (Term2 -> Term3) /\ (Term3 -> Term1).
    Proof using HQ.
      msplit 2; unfold Term1, Term2, Term3.
      + intros (y & H1 & H2); split; simpl; auto.
        apply fractran_mma0_sound with (1 := H1); auto.
      + now exists (0,0##0##ø).
      + intros H1; apply fractran_mma_reduction; auto.
        revert H1; apply subcode_sss_terminates.
        unfold fractran_mma0, P; auto.
    Qed.

  End fractran_mma0.

End Fractran_with_two_counters.

Theorem fractran_reg_mma0_reductions Q :
             fractran_regular Q
          -> (forall x, Q /F/ x <-> (1,fractran_mma0 Q) //ₐ (1,x##0##ø) )
          /\ (forall x, Q /F/ x <-> (1,fractran_mma0 Q) //ₐ (1,x##0##ø) ~~> (0,(0##0##ø))).
Proof.
  do 2 (split; intros); repeat (apply fractran_mma0_reduction; auto).
Qed.