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.

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 "P // s -[ k ]-> t" := (sss_steps (@mma_sss _) P k s t).
Local Notation "P // s -+> t" := (sss_progress (@mma_sss _) P s t).
Local Notation "P // s ->> t" := (sss_compute (@mma_sss _) P s t).
Local Notation "P // s ↓" := (sss_terminates (@mma_sss _) P s).


Section Minsky_Machine_alt_utils.

  Variable (n : nat).

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

  Hint Resolve subcode_refl : core.

  Section mma_jump.

    Variable (j : nat) (x : pos n).

    Definition mma_jump := INC x :: DEC x j :: nil.

    Fact mma_jump_length : length mma_jump = 2.
    Proof. auto. Qed.

    Fact mma_jump_progress i v : (i,mma_jump) // (i,v) -+> (j,v).
    Proof.
      unfold mma_jump.
      mma sss INC with x.
      mma sss DEC S with x j (v#>x); rew vec.
      mma sss stop.
    Qed.

  End mma_jump.

  Section mma_null.


    Variable (dst : pos n).

    Definition mma_null i := DEC dst i :: nil.

    Fact mma_null_length i : length (mma_null i) = 1.
    Proof. auto. Qed.

    Let mma_null_spec i k v w : v#>dst = k
                             -> w = v[0/dst]
                             -> (i,mma_null i) // (i,v) -+> (1+i,w).
    Proof.
      unfold mma_null.
      revert v w.
      induction k as [ | k IHk ]; intros v w H1 H2; subst w.
      + mma sss DEC 0 with dst i.
        mma sss stop; f_equal.
        apply vec_pos_ext; intros z; dest z dst.
      + mma sss DEC S with dst i k.
        apply sss_progress_compute.
        apply IHk; rew vec.
    Qed.

    Fact mma_null_progress i v st :
             st = (1+i,v[0/dst])
          -> (i,mma_null i) // (i,v) -+> st.
    Proof.
      intros; subst.
      apply mma_null_spec with (1 := eq_refl); auto.
    Qed.

  End mma_null.

  Hint Rewrite mma_null_length : length_db.

  Section mma_transfert.


    Variables (src dst : pos n) (Hsd : src <> dst).

    Definition mma_transfert i := INC dst :: DEC src i :: DEC dst (3+i) :: nil.

    Fact mma_transfert_length i : length (mma_transfert i) = 3.
    Proof. reflexivity. Qed.

    Let mma_transfert_spec i v w k x : v#>src = k
                                     -> v#>dst = x
                                     -> w = v[0/src][(1+k+x)/dst]
                                     -> (i,mma_transfert i) // (i,v) -+> (2+i,w).
    Proof.
      unfold mma_transfert.
      revert v w x.
      induction k as [ | k IHk ]; intros v w x H1 H2 H3; subst w.
      + mma sss INC with dst.
        mma sss DEC 0 with src i; rew vec.
        mma sss stop; f_equal; auto.
        apply vec_pos_ext; intros z; dest z dst; dest z src.
      + mma sss INC with dst.
        mma sss DEC S with src i k; rew vec.
        apply sss_progress_compute, IHk with (x := 1+x); rew vec.
        apply vec_pos_ext; intros p.
        dest p dst; try lia; dest p src.
    Qed.

    Fact mma_transfert_progress i v st :
           st = (3+i,v[0/src][((v#>src)+(v#>dst))/dst])
        -> (i,mma_transfert i) // (i,v) -+> st.
    Proof using Hsd.
      intros ?; subst.
      apply sss_progress_trans with (2+i, v[0/src][(1+(v#>src)+(v#>dst))/dst]).
      + apply mma_transfert_spec with (1 := eq_refl) (2 := eq_refl); auto.
      + unfold mma_transfert.
        mma sss DEC S with dst (3+i) ((v#>src)+(v#>dst)); rew vec.
        mma sss stop.
    Qed.

  End mma_transfert.

  Hint Rewrite mma_transfert_length : length_db.

  Section mma_incs.


    Variable (dst : pos n).

    Fixpoint mma_incs k :=
      match k with
        | 0 => nil
        | S k => INC dst :: mma_incs k
      end.

    Fact mma_incs_length k : length (mma_incs k) = k.
    Proof. induction k; simpl; f_equal; auto. Qed.

    Fact mma_incs_compute k i v st :
             st = (k+i,v[(k+(v#>dst))/dst])
          -> (i,mma_incs k) // (i,v) ->> st.
    Proof.
      revert i v st; induction k as [ | k IHk ]; intros i v st ?; subst.
      + mma sss stop; f_equal; auto.
        apply vec_pos_ext; intros p; dest p dst.
      + simpl; mma sss INC with dst.
        apply subcode_sss_compute with (P := (1+i,mma_incs k)); auto.
        { subcode_tac; rewrite <- app_nil_end; auto. }
        apply IHk; f_equal; try lia.
        apply vec_pos_ext; intros p; dest p dst.
    Qed.

  End mma_incs.

  Section mma_decs.


    Variable (dst : pos n) (p q : nat).

    Fixpoint mma_decs k i :=
      match k with
        | 0 => INC dst :: DEC dst p :: nil
        | S k => DEC dst (3+i) :: INC dst :: DEC dst q :: mma_decs k (3+i)
      end.

    Fact mma_decs_length k i : length (mma_decs k i) = 2+3*k.
    Proof.
      revert i; induction k as [ | ? IHk ]; intros i; simpl; auto.
      rewrite IHk; lia.
    Qed.

    Let mma_decs_spec_lt k i v w :
            v#>dst < k
         -> w = v[0/dst]
         -> (i,mma_decs k i) // (i,v) -+> (q,w).
    Proof.
      revert i v w; induction k as [ | k IHk ]; intros i v w H1 ?; subst w.
      + lia.
      + unfold mma_decs; fold mma_decs.
        case_eq (v#>dst).
        * intros H2.
          mma sss DEC 0 with dst (3+i).
          mma sss INC with dst.
          mma sss DEC S with dst q (v#>dst); rew vec.
          mma sss stop; f_equal.
          apply vec_pos_ext; intros x; dest x dst.
        * intros d Hd.
          mma sss DEC S with dst (3+i) d.
          apply subcode_sss_compute with (P := (3+i,mma_decs k (3+i))); auto.
          { subcode_tac; rewrite <- app_nil_end; auto. }
          apply sss_progress_compute, IHk; rew vec; try lia.
    Qed.

    Let mma_decs_spec_le k i v w :
            k <= v#>dst
         -> w = v[((v#>dst)-k)/dst]
         -> (i,mma_decs k i) // (i,v) -+> (p,w).
    Proof.
      revert i v w; induction k as [ | k IHk ]; intros i v w H1 ?; subst w.
      + simpl.
        mma sss INC with dst.
        mma sss DEC S with dst p (v#>dst); rew vec.
        mma sss stop; f_equal.
        apply vec_pos_ext; intros x; dest x dst; try lia.
      + unfold mma_decs; fold mma_decs.
        mma sss DEC S with dst (3+i) ((v#>dst) - 1); try lia.
        apply subcode_sss_compute with (P := (3+i,mma_decs k (3+i))); auto.
        { subcode_tac; rewrite <- app_nil_end; auto. }
        apply sss_progress_compute, IHk; rew vec; try lia.
        apply vec_pos_ext; intros x; dest x dst; lia.
    Qed.

    Fact mma_decs_lt_progress k i v st :
             v#>dst < k
          -> st = (q,v[0/dst])
          -> (i,mma_decs k i) // (i,v) -+> st.
    Proof.
      intros H1 ?; subst st.
      apply mma_decs_spec_lt; auto.
    Qed.

    Fact mma_decs_le_progress k i v st :
             k <= v#>dst
          -> st = (p,v[((v#>dst)-k)/dst])
          -> (i,mma_decs k i) // (i,v) -+> st.
    Proof.
      intros H1 ?; subst st.
      apply mma_decs_spec_le; auto.
    Qed.

  End mma_decs.

  Section mma_decs_copy.


    Variable (dst tmp : pos n) (Hdt : dst <> tmp) (p q : nat).

    Fixpoint mma_decs_copy k i :=
      match k with
        | 0 => INC dst :: DEC dst p :: nil
        | S k => DEC dst (3+i) :: INC dst :: DEC dst q :: INC tmp :: mma_decs_copy k (4+i)
      end.

    Fact mma_decs_copy_length k i : length (mma_decs_copy k i) = 2+4*k.
    Proof.
      revert i; induction k as [ | ? IHk ]; intros i; simpl; auto.
      rewrite IHk; lia.
    Qed.

    Let mma_decs_copy_spec_lt k i v w :
            v#>dst < k
         -> w = v[0/dst][((v#>dst)+(v#>tmp))/tmp]
         -> (i,mma_decs_copy k i) // (i,v) -+> (q,w).
    Proof.
      revert i v w; induction k as [ | k IHk ]; intros i v w H1 ?; subst w.
      + lia.
      + unfold mma_decs_copy; fold mma_decs_copy.
        case_eq (v#>dst).
        * intros H2.
          mma sss DEC 0 with dst (3+i).
          mma sss INC with dst.
          mma sss DEC S with dst q (v#>dst); rew vec.
          mma sss stop; f_equal.
          apply vec_pos_ext; intros x; dest x tmp; dest x dst.
        * intros d Hd.
          mma sss DEC S with dst (3+i) d.
          mma sss INC with tmp.
          apply subcode_sss_compute with (P := (4+i,mma_decs_copy k (4+i))); auto.
          { subcode_tac; rewrite <- app_nil_end; auto. }
          apply sss_progress_compute; rewrite plus_assoc.
          apply IHk; rew vec; try lia.
          apply vec_pos_ext; intros x; dest x tmp; try lia; dest x dst.
    Qed.

    Let mma_decs_copy_spec_le k i v w :
            k <= v#>dst
         -> w = v[((v#>dst)-k)/dst][(k+(v#>tmp))/tmp]
         -> (i,mma_decs_copy k i) // (i,v) -+> (p,w).
    Proof.
      revert i v w; induction k as [ | k IHk ]; intros i v w H1 ?; subst w.
      + simpl.
        mma sss INC with dst.
        mma sss DEC S with dst p (v#>dst); rew vec.
        mma sss stop; f_equal.
        apply vec_pos_ext; intros x; dest x dst; try lia; dest x tmp.
      + unfold mma_decs_copy; fold mma_decs_copy.
        mma sss DEC S with dst (3+i) ((v#>dst) - 1); try lia.
        mma sss INC with tmp.
        apply subcode_sss_compute with (P := (4+i,mma_decs_copy k (4+i))); auto.
        { subcode_tac; rewrite <- app_nil_end; auto. }
        apply sss_progress_compute, IHk; rew vec; try lia.
        apply vec_pos_ext; intros x; dest x tmp; try lia; dest x dst; lia.
    Qed.

    Fact mma_decs_copy_lt_progress k i v st :
             v#>dst < k
          -> st = (q,v[0/dst][((v#>dst)+(v#>tmp))/tmp])
          -> (i,mma_decs_copy k i) // (i,v) -+> st.
    Proof using Hdt.
      intros H1 ?; subst st.
      apply mma_decs_copy_spec_lt; auto.
    Qed.

    Fact mma_decs_copy_le_progress k i v st :
             k <= v#>dst
          -> st = (p,v[((v#>dst)-k)/dst][(k+(v#>tmp))/tmp])
          -> (i,mma_decs_copy k i) // (i,v) -+> st.
    Proof using Hdt.
      intros H1 ?; subst st.
      apply mma_decs_copy_spec_le; auto.
    Qed.

  End mma_decs_copy.

  Hint Rewrite mma_incs_length mma_decs_copy_length : length_db.

  Section mma_mult_cst.


    Variable (src dst : pos n) (Hsd : src <> dst) (k i : nat).

    Definition mma_mult_cst :=
           DEC src (3+i) :: INC src :: DEC src (5+k+i)
        :: mma_incs dst (S k) ++ DEC dst i :: nil.

    Fact mma_mult_cst_length : length mma_mult_cst = 5+k.
    Proof. unfold mma_mult_cst; rew length; lia. Qed.

    Let mma_mult_cst_spec x v st :
             v#>src = x
          -> st = (5+k+i,v[0/src][(x*k+(v#>dst))/dst])
          -> (i,mma_mult_cst) // (i,v) -+> st.
    Proof.
      unfold mma_mult_cst.
      revert v st; induction x as [ | x IHx ]; intros v st Hv ?; subst.
      + mma sss DEC 0 with src (3+i).
        mma sss INC with src.
        mma sss DEC S with src (5+k+i) 0; rew vec.
        mma sss stop; f_equal.
        apply vec_pos_ext; intros y; dest y dst; lia.
      + mma sss DEC S with src (3+i) x.
        apply sss_compute_trans with (4+k+i,v[x/src][(S k+(v#>dst))/dst]).
        * apply subcode_sss_compute with (P := (3+i,mma_incs dst (S k))); auto.
          apply mma_incs_compute; f_equal; try lia.
          apply vec_pos_ext; intros y; dest y dst; lia.
        * mma sss DEC S with dst i (k+(v#>dst)); rew vec.
          apply sss_progress_compute, IHx; rew vec; f_equal.
          apply vec_pos_ext; intros y; dest y dst; try ring.
          dest y src.
    Qed.

    Fact mma_mult_cst_progress v st :
             st = (5+k+i,v[0/src][(k*(v#>src)+(v#>dst))/dst])
          -> (i,mma_mult_cst) // (i,v) -+> st.
    Proof using Hsd.
      intros ?; subst.
      apply mma_mult_cst_spec with (1 := eq_refl); do 2 f_equal.
      ring.
    Qed.

  End mma_mult_cst.

  Hint Rewrite mma_mult_cst_length : length_db.

  Section mma_mod_cst.


    Variable (src dst : pos n) (Hsd : src <> dst) (p q k i : nat).

    Definition mma_mod_cst :=
            DEC src (3+i)
         :: INC dst
         :: DEC dst p
         :: INC src
         :: mma_decs_copy src dst i q k (4+i).

    Fact mma_mod_cst_length : length mma_mod_cst = 6+4*k.
    Proof. unfold mma_mod_cst; rew length; lia. Qed.


    Hypothesis (Hk : 0 < k).

    Let mma_mod_cst_spec_0 v :
           v#>src = 0
        -> (i,mma_mod_cst) // (i,v) -+> (p,v).
    Proof.
      intros H; unfold mma_mod_cst.
      mma sss DEC 0 with src (3+i).
      mma sss INC with dst.
      mma sss DEC S with dst p (v#>dst); rew vec.
      mma sss stop.
    Qed.

    Let mma_mod_cst_spec_1 a b v w :
           v#>src = a*k+b
        -> w = v[b/src][(a*k+(v#>dst))/dst]
        -> (i,mma_mod_cst) // (i,v) ->> (i,w).
    Proof.
      revert v w; induction a as [ | a IHa ]; intros v w H1 H2; subst w.
      + mma sss stop; f_equal.
        simpl in H1; rewrite <- H1; simpl; rew vec.
      + unfold mma_mod_cst.
        mma sss DEC S with src (3+i) (S a*k+b-1).
        { rewrite H1; simpl; generalize (a*k); intro; lia. }
        mma sss INC with src.
        apply sss_compute_trans with (i, v[(a*k+b)/src][(k+(v#>dst))/dst]).
        * apply subcode_sss_compute with (P := (4+i,mma_decs_copy src dst i q k (4+i))); auto.
          { subcode_tac; rewrite <- app_nil_end; auto. }
          apply sss_progress_compute, mma_decs_copy_le_progress; auto; rew vec.
          { simpl; generalize (a*k); intro; lia. }
          do 3 f_equal; simpl mult; generalize (a*k); intro; lia.
        * apply IHa; rew vec.
          apply vec_pos_ext; intros y; dest y dst; try ring; dest y src.
    Qed.

    Let mma_mod_cst_spec_2 v w :
           0 < v#>src < k
        -> w = v[0/src][((v#>src)+(v#>dst))/dst]
        -> (i,mma_mod_cst) // (i,v) -+> (q,w).
    Proof.
      intros H ?; subst; unfold mma_mod_cst.
      case_eq (v#>src).
      { intros; lia. }
      intros x Hx.
      mma sss DEC S with src (3+i) x.
      mma sss INC with src.
      apply subcode_sss_compute with (P := (4+i,mma_decs_copy src dst i q k (4+i))); auto.
      { subcode_tac; rewrite <- app_nil_end; auto. }
       apply sss_progress_compute, mma_decs_copy_lt_progress; auto; rew vec; lia.
    Qed.

    Fact mma_mod_cst_divides_progress v a st :
            v#>src = a*k
         -> st = (p,v[0/src][((v#>src)+(v#>dst))/dst])
         -> (i,mma_mod_cst) // (i,v) -+> st.
    Proof using Hsd Hk.
      intros H1 ?; subst st.
      apply sss_compute_progress_trans with (i,v[0/src][((v#>src)+(v#>dst))/dst]).
      + apply mma_mod_cst_spec_1 with (a := a) (b := 0); try lia.
        rewrite <- H1; auto.
      + apply mma_mod_cst_spec_0; rew vec.
    Qed.

    Fact mma_mod_cst_not_divides_progress v a b st :
            v#>src = a*k+b
         -> 0 < b < k
         -> st = (q,v[0/src][((v#>src)+(v#>dst))/dst])
         -> (i,mma_mod_cst) // (i,v) -+> st.
    Proof using Hsd Hk.
      intros H1 H2 ?; subst st.
      apply sss_compute_progress_trans with (i,v[b/src][(a*k+(v#>dst))/dst]).
      + apply mma_mod_cst_spec_1 with (a := a) (b := b); try lia; auto.
      + apply mma_mod_cst_spec_2; rew vec.
        apply vec_pos_ext; intros y; dest y dst; try lia; dest y src.
    Qed.

  End mma_mod_cst.

  Hint Rewrite mma_decs_length mma_mod_cst_length : length_db.

  Section mma_div_cst.


    Variable (src dst : pos n) (Hsd : src <> dst) (k i : nat).

    Let p := (2+3*k+i).
    Let q := (5+3*k+i).

    Definition mma_div_cst :=
         mma_decs src p q k i ++ INC dst :: INC src :: DEC src i :: nil.

    Fact mma_div_cst_length : length mma_div_cst = 5+3*k.
    Proof. unfold mma_div_cst; rew length; lia. Qed.

    Hypothesis (Hk : 0 < k).

    Let mma_div_cst_spec a v w :
           v#>src = a*k
        -> w = v[0/src][(a+(v#>dst))/dst]
        -> (i, mma_div_cst) // (i,v) -+> (q,w).
    Proof.
      unfold mma_div_cst; revert v w; induction a as [ | a IHa ]; intros v w H1 ?; subst w.
      + apply subcode_sss_progress with (P := (i,mma_decs src p q k i)); auto.
        apply mma_decs_lt_progress; try lia.
        f_equal; simpl.
        apply vec_pos_ext; intros y; dest y dst.
      + apply sss_progress_trans with (p,v[(a*k)/src]).
        * apply subcode_sss_progress with (P := (i,mma_decs src p q k i)); auto.
          apply mma_decs_le_progress.
          - rewrite H1; simpl; generalize (a*k); intro; lia.
          - f_equal.
            apply vec_pos_ext; intros y; dest y dst; dest y src.
            rewrite H1; simpl; generalize (a*k); intro; lia.
        * unfold p.
          mma sss INC with dst.
          mma sss INC with src.
          mma sss DEC S with src i (a*k); rew vec.
          apply sss_progress_compute, IHa; rew vec.
          apply vec_pos_ext; intros y; dest y dst; try lia; dest y src.
    Qed.

    Fact mma_div_cst_progress a v st :
            v#>src = a*k
         -> st = (q,v[0/src][(a+(v#>dst))/dst])
         -> (i, mma_div_cst) // (i,v) -+> st.
    Proof using Hsd Hk.
      intros H1 H2; subst st; apply mma_div_cst_spec with (1 := H1); auto.
    Qed.

  End mma_div_cst.

  Hint Rewrite mma_div_cst_length : length_db.

End Minsky_Machine_alt_utils.