Require Import List Arith Lia Relations.

From Undecidability.MinskyMachines
  Require Import MM2 ndMM2.

From Undecidability.Shared.Libs.DLW
  Require Import utils.

From Undecidability.Synthetic
  Require Import Definitions ReducibilityFacts.

Set Implicit Arguments.

Set Default Proof Using "Type".

Section MM2_ndMM2.

  Notation STOP := (@ndmm2_stop _).
  Notation INC := (@ndmm2_inc _).
  Notation DEC := (@ndmm2_dec _).
  Notation ZERO := (@ndmm2_zero _).

  Notation α := true.
  Notation β := false.

  Definition mm2_instr_enc i ρ :=
    match ρ with
      | mm2_inc_a INC α i (1+i) :: nil
      | mm2_inc_b INC β i (1+i) :: nil
      | mm2_dec_a j DEC α i j :: ZERO true i (1+i) :: nil
      | mm2_dec_b j DEC β i j :: ZERO false i (1+i) :: nil
    end.

  Fixpoint mm2_linstr_enc i l :=
    match l with
      | nil nil
      | ρ::l mm2_instr_enc i ρ mm2_linstr_enc (1+i) l
    end.

  Fact mm2_linstr_enc_app i l m : mm2_linstr_enc i (lm) = mm2_linstr_enc i l mm2_linstr_enc (length l+i) m.
  Proof.
    revert i; induction l as [ | ? l IHl ]; intros ?; simpl; auto.
    rewrite app_ass, IHl; do 3 f_equal; auto.
  Qed.


  Fact mm2_linstr_enc_In i P c :
          In c (mm2_linstr_enc i P)
        l r ρ, P = lρ::r In c (mm2_instr_enc (length l+i) ρ).
  Proof.
    revert i; induction P as [ | ρ P IH ]; intros i.
    + intros [].
    + simpl; rewrite in_app_iff; intros [ H | H ].
      * exists nil, P, ρ; split; auto.
      * destruct (IH (1+i)) as (l & r & ρ' & & ); auto.
        exists::l), r, ρ'; split; auto.
        - simpl; f_equal; auto.
        - eq goal ; do 2 f_equal; simpl; .
  Qed.


  Notation "Σ // a ⊕ b ⊦ u" := (ndmm2_accept Σ a b u) (at level 70, no associativity).

  Local Fact mm2_instr_enc_sound Σ ρ s1 s2 :
          mm2_atom ρ
        match , with
            | (i,(a,b)), (j,(a',b')) incl (mm2_instr_enc i ρ) Σ
                                      Σ // a' b' j
                                      Σ // a b i
          end.
  Proof.
    induction 1 as [ i a b | i a b | i j a b | i j a b | i j b | i j a ]; simpl; intros H.
    + constructor 2 with (1+i); auto; apply ; simpl; auto.
    + constructor 3 with (1+i); auto; apply ; simpl; auto.
    + constructor 4 with j; auto; apply ; simpl; auto.
    + constructor 5 with j; auto; apply ; simpl; auto.
    + constructor 6 with (1+i); auto; apply ; simpl; auto.
    + constructor 7 with (1+i); auto; apply ; simpl; auto.
  Qed.


  Local Fact mm2_step_linstr_sound Σ P s1 s2 :
          mm2_step P
        match , with
            | (i,(a,b)), (j,(a',b')) incl (mm2_linstr_enc 1 P) Σ
                                      Σ // a' b' j
                                      Σ // a b i
          end.
  Proof.
    intros (ρ & (l&r&&) & ).
    apply mm2_instr_enc_sound with (Σ := Σ) in ; revert .
    intros (i,(a,b)) (j,(a',b')); simpl; intros .
    apply ; auto.
    apply incl_tran with (2 := ).
    rewrite , mm2_linstr_enc_app.
    apply incl_appr; simpl.
    apply incl_appl.
    rewrite , plus_comm.
    apply incl_refl.
  Qed.


  Notation "P // x ↠ y" := (clos_refl_trans _ (mm2_step P) x y) (at level 70).

  Variable P : list mm2_instr.

  Definition mm2_prog_enc := STOP 0 :: mm2_linstr_enc 1 P.

  Notation Σ := mm2_prog_enc.

  Local Lemma mm2_prog_enc_compute s1 s2 :
          P //
        match , with
            | (i,(a,b)), (j,(a',b')) Σ // a' b' j
                                      Σ // a b i
          end.
  Proof.
    induction 1 as [ (?,(?,?)) (?,(?,?)) H
                   | (?,(?,?))
                   | (?,(?,?)) (?,(?,?)) (?,(?,?)) ]; eauto.
    apply mm2_step_linstr_sound with (Σ := Σ) in H.
    apply H, incl_tl, incl_refl.
  Qed.


  Local Lemma mm2_prog_enc_stop : Σ // 0 0 0.
  Proof. constructor 1; simpl; auto. Qed.

  Hint Resolve mm2_prog_enc_stop : core.

  Local Lemma mm2_prog_enc_complete a b p :
             Σ // a b p P // (p,(a,b)) (0,(0,0)).
  Proof.
    induction 1 as [ u H
                   | a b u v H
                   | a b u v H
                   | a b u v H
                   | a b u v H
                   | b p u H
                   | a p u H ].
    + destruct H as [ H | H ].
      * inversion H; subst u; constructor 2.
      * apply mm2_linstr_enc_In in H
          as (l & r & [ | | | ] & & ); simpl in .
        1-2: now destruct .
        1-2: now destruct as [ | [] ].
    + destruct H as [ H | H ]; try discriminate.
      apply mm2_linstr_enc_In in H
        as (l & r & [ | | | ] & & ); simpl in .
      2: now destruct .
      2-3: now destruct as [ | [] ].
      destruct as [ | [] ]; inversion ; subst.
      constructor 3 with (length l+2, (S a,b)).
      * constructor 1.
        exists mm2_inc_a; split.
        - exists l, r; simpl; split; auto; .
        - rewrite !(plus_comm (length l)); constructor.
      * eq goal ; do 2 f_equal; .
    + destruct H as [ H | H ]; try discriminate.
      apply mm2_linstr_enc_In in H
        as (l & r & [ | | | ] & & ); simpl in .
      1: now destruct .
      2-3: now destruct as [ | [] ].
      destruct as [ | [] ]; inversion ; subst.
      constructor 3 with (length l+2, (a,S b)).
      * constructor 1.
        exists mm2_inc_b; split.
        - exists l, r; simpl; split; auto; .
        - rewrite !(plus_comm (length l)); constructor.
      * eq goal ; do 2 f_equal; .
    + destruct H as [ H | H ]; try discriminate.
      apply mm2_linstr_enc_In in H
        as (l & r & [ | | | ] & & ); simpl in .
      1-2: now destruct .
      2: now destruct as [ | [] ].
      destruct as [ | ].
      2: now destruct .
      inversion ; subst.
      constructor 3 with (v, (a,b)); auto.
      constructor 1.
      exists (mm2_dec_a v); split; auto.
      * exists l, r; simpl; split; auto; .
      * rewrite !(plus_comm (length l)); constructor.
    + destruct H as [ H | H ]; try discriminate.
      apply mm2_linstr_enc_In in H
        as (l & r & [ | | | ] & & ); simpl in .
      1-2: now destruct .
      1: now destruct as [ | [] ].
      destruct as [ | ].
      2: now destruct .
      inversion ; subst.
      constructor 3 with (v, (a,b)); auto.
      constructor 1.
      exists (mm2_dec_b v); split; auto.
      * exists l, r; simpl; split; auto; .
      * rewrite !(plus_comm (length l)); constructor.
    + destruct H as [ H | H ]; try discriminate.
      apply mm2_linstr_enc_In in H
        as (l & r & [ | | | ] & & ); simpl in .
      1-2: now destruct .
      2: now destruct as [ | [] ].
      destruct as [ | [ | [] ] ].
      1: easy.
      inversion ; subst.
      constructor 3 with (length l+2, (0,b)).
      * constructor 1.
        exists (mm2_dec_a n); split.
        - exists l, r; simpl; split; auto; .
        - rewrite !(plus_comm (length l)); constructor.
      * eq goal ; do 2 f_equal; .
    + destruct H as [ H | H ]; try discriminate.
      apply mm2_linstr_enc_In in H
        as (l & r & [ | | | ] & & ); simpl in .
      1-2: now destruct .
      1: now destruct as [ | [] ].
      destruct as [ | [ | [] ] ].
      1: easy.
      inversion ; subst.
      constructor 3 with (length l+2, (a,0)).
      * constructor 1.
        exists (mm2_dec_b n); split.
        - exists l, r; simpl; split; auto; .
        - rewrite !(plus_comm (length l)); constructor.
      * eq goal ; do 2 f_equal; .
  Qed.


  Theorem MM2_ndMM2_equiv a b : MM2_HALTS_ON_ZERO (P,a,b) Σ // a b 1.
  Proof.
    split.
    + intros H; apply mm2_prog_enc_compute in H; auto.
    + apply mm2_prog_enc_complete.
  Qed.


End MM2_ndMM2.

Theorem reduction : MM2_HALTS_ON_ZERO @ndMM2_ACCEPT .
Proof.
  apply reduces_dependent; exists.
  intros ((P,a),b).
  exists (existT _ (mm2_prog_enc P) (1,(a,b))).
  apply MM2_ndMM2_equiv.
Qed.