Require Import List Arith Lia.

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

Set Implicit Arguments.
Set Default Goal Selector "!".

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

Record godel_coding n := {
  gc_pr : pos n -> nat;
  gc_enc : vec nat n -> nat;
  gc_pr_nz : forall p, 0 < gc_pr p;
  gc_not_div : forall p v, v#>p = 0 -> ~ divides (gc_pr p) (gc_enc v);
  gc_succ : forall p v, gc_pr p * gc_enc v = gc_enc (v[(S (v#>p))/p])
}.

Arguments godel_coding : clear implicits.

Section powers_of_2357_props.


  Local Fact prime_2 : prime 2. Proof. apply prime_bool_spec; trivial. Qed.
  Local Fact prime_3 : prime 3. Proof. apply prime_bool_spec; trivial. Qed.
  Local Fact prime_5 : prime 5. Proof. apply prime_bool_spec; trivial. Qed.
  Local Fact prime_7 : prime 7. Proof. apply prime_bool_spec; trivial. Qed.

  Hint Resolve prime_2 prime_3 prime_5 prime_7 : core.

  Ltac does_not_divide_1 := now intros H%divides_pow%prime_divides.

  Local Fact not_divides_2_3 x : ~ divides 2 (3^x). Proof. does_not_divide_1. Qed.
  Local Fact not_divides_2_5 x : ~ divides 2 (5^x). Proof. does_not_divide_1. Qed.
  Local Fact not_divides_2_7 x : ~ divides 2 (7^x). Proof. does_not_divide_1. Qed.

  Local Fact not_divides_3_2 x : ~ divides 3 (2^x). Proof. does_not_divide_1. Qed.
  Local Fact not_divides_3_5 x : ~ divides 3 (5^x). Proof. does_not_divide_1. Qed.
  Local Fact not_divides_3_7 x : ~ divides 3 (7^x). Proof. does_not_divide_1. Qed.

  Local Fact not_divides_5_2 x : ~ divides 5 (2^x). Proof. does_not_divide_1. Qed.
  Local Fact not_divides_5_3 x : ~ divides 5 (3^x). Proof. does_not_divide_1. Qed.
  Local Fact not_divides_5_7 x : ~ divides 5 (7^x). Proof. does_not_divide_1. Qed.

  Local Fact not_divides_7_2 x : ~ divides 7 (2^x). Proof. does_not_divide_1. Qed.
  Local Fact not_divides_7_3 x : ~ divides 7 (3^x). Proof. does_not_divide_1. Qed.
  Local Fact not_divides_7_5 x : ~ divides 7 (5^x). Proof. does_not_divide_1. Qed.

  Hint Resolve not_divides_2_3 not_divides_2_5 not_divides_2_7
               not_divides_3_2 not_divides_3_5 not_divides_3_7
               not_divides_5_2 not_divides_5_3 not_divides_5_7
               not_divides_7_2 not_divides_7_3 not_divides_7_5 : core.

  Local Fact not_divides_5_1 : ~ divides 5 1.
  Proof. apply not_divides_5_3 with (x := 0). Qed.

  Ltac fold_not := match goal with |- ?t -> False => change (~ t) end.
  Ltac does_not_divide_2 :=
    let H := fresh
    in intros [H|H]%prime_div_mult; auto; revert H; fold_not; auto.

  Local Fact not_divides_2_35 x y : ~ divides 2 (3^x*5^y). Proof. does_not_divide_2. Qed.
  Local Fact not_divides_3_25 x y : ~ divides 3 (2^x*5^y). Proof. does_not_divide_2. Qed.
  Local Fact not_divides_5_23 x y : ~ divides 5 (2^x*3^y). Proof. does_not_divide_2. Qed.
  Local Fact not_divides_7_23 x y : ~ divides 7 (2^x*3^y). Proof. does_not_divide_2. Qed.

  Hint Resolve not_divides_2_35 not_divides_3_5 not_divides_5_1 : core.

  Ltac does_not_divide_3 :=
    let H := fresh
    in intros H; do 2 try apply prime_div_mult in H as [H|H]; auto; revert H; fold_not; auto.

  Local Fact not_divides_2_357 x y z : ~ divides 2 (3^x*5^y*7^z). Proof. does_not_divide_3. Qed.
  Local Fact not_divides_3_257 x y z : ~ divides 3 (2^x*5^y*7^z). Proof. does_not_divide_3. Qed.
  Local Fact not_divides_5_237 x y z : ~ divides 5 (2^x*3^y*7^z). Proof. does_not_divide_3. Qed.
  Local Fact not_divides_7_235 x y z : ~ divides 7 (2^x*3^y*5^z). Proof. does_not_divide_3. Qed.

End powers_of_2357_props.

Section godel_coding_235.

  Definition combi_235 a b c := 2^a*(3^b*5^c).

  Notation "⦉ x , y , z ⦊ " := (combi_235 x y z) (at level 1, format "⦉ x , y , z ⦊ ").

  Ltac combi_235_x_0 := unfold combi_235; rewrite Nat.pow_0_r; ring.

  Local Fact combi_235_2_0 b c : 0,b,c = 3^b*5^c. Proof. combi_235_x_0. Qed.
  Local Fact combi_235_3_0 a c : a,0,c = 2^a*5^c. Proof. combi_235_x_0. Qed.
  Local Fact combi_235_5_0 a b : a,b,0 = 2^a*3^b. Proof. combi_235_x_0. Qed.

  Ltac combi_235_multx := unfold combi_235; rewrite Nat.pow_succ_r'; ring.

  Local Fact combi_235_mult2 a b c : 2*a,b,c = S a,b,c . Proof. combi_235_multx. Qed.
  Local Fact combi_235_mult3 a b c : 3*a,b,c = a,S b,c . Proof. combi_235_multx. Qed.
  Local Fact combi_235_mult5 a b c : 5*a,b,c = a,b,S c . Proof. combi_235_multx. Qed.

  Local Definition pos3_235 : pos 3 -> nat.
  Proof.
    intro p; repeat invert pos p.
    + exact 2.
    + exact 3.
    + exact 5.
  Defined.

  Local Fact pos3_235_gt_0 x : 0 < pos3_235 x.
  Proof. repeat invert pos x; cbn; lia. Qed.

  Theorem godel_coding_235 : godel_coding 3.
  Proof.
    exists pos3_235 (fun v => v#>pos0,v#>pos1,v#>pos2).
    + apply pos3_235_gt_0.
    + intros p; repeat invert pos p; intros v ->.
      * rewrite combi_235_2_0; apply not_divides_2_35.
      * rewrite combi_235_3_0; apply not_divides_3_25.
      * rewrite combi_235_5_0; apply not_divides_5_23.
    + intros p; repeat invert pos p; intros v; rew vec.
      * apply combi_235_mult2.
      * apply combi_235_mult3.
      * apply combi_235_mult5.
  Qed.

End godel_coding_235.

Section godel_coding_2357.

  Definition combi_2357 a b c d := 2^a*(3^b*(5^c*7^d)).

  Notation "⦉ x , y , z , u ⦊" := (combi_2357 x y z u) (at level 1, format "⦉ x , y , z , u ⦊").

  Ltac combi_2357_x_0 := unfold combi_2357; rewrite Nat.pow_0_r; ring.

  Local Fact combi_2357_2_0 b c d : 0,b,c,d = 3^b*5^c*7^d. Proof. combi_2357_x_0. Qed.
  Local Fact combi_2357_3_0 a c d : a,0,c,d = 2^a*5^c*7^d. Proof. combi_2357_x_0. Qed.
  Local Fact combi_2357_5_0 a b d : a,b,0,d = 2^a*3^b*7^d. Proof. combi_2357_x_0. Qed.
  Local Fact combi_2357_7_0 a b c : a,b,c,0 = 2^a*3^b*5^c. Proof. combi_2357_x_0. Qed.

  Ltac combi_2357_multx := unfold combi_2357; rewrite Nat.pow_succ_r'; ring.

  Local Fact combi_2357_mult2 a b c d : 2*a,b,c,d = S a,b,c,d. Proof. combi_2357_multx. Qed.
  Local Fact combi_2357_mult3 a b c d : 3*a,b,c,d = a,S b,c,d. Proof. combi_2357_multx. Qed.
  Local Fact combi_2357_mult5 a b c d : 5*a,b,c,d = a,b,S c,d. Proof. combi_2357_multx. Qed.
  Local Fact combi_2357_mult7 a b c d : 7*a,b,c,d = a,b,c,S d. Proof. combi_2357_multx. Qed.

  Local Definition pos4_2357 : pos 4 -> nat.
  Proof.
    intro p; repeat invert pos p.
    + exact 2.
    + exact 3.
    + exact 5.
    + exact 7.
  Defined.

  Local Fact pos4_2357_gt_0 x : 0 < pos4_2357 x.
  Proof. repeat invert pos x; cbn; lia. Qed.

  Theorem godel_coding_2357 : godel_coding 4.
  Proof.
    exists pos4_2357 (fun v => v#>pos0,v#>pos1,v#>pos2,v#>pos3).
    + apply pos4_2357_gt_0.
    + intros p; repeat invert pos p; intros v ->.
      * rewrite combi_2357_2_0; apply not_divides_2_357.
      * rewrite combi_2357_3_0; apply not_divides_3_257.
      * rewrite combi_2357_5_0; apply not_divides_5_237.
      * rewrite combi_2357_7_0; apply not_divides_7_235.
    + intros p; repeat invert pos p; intros v; rew vec.
      * apply combi_2357_mult2.
      * apply combi_2357_mult3.
      * apply combi_2357_mult5.
      * apply combi_2357_mult7.
  Qed.

End godel_coding_2357.