Require Import Arith Nat Lia List.

From Undecidability.Shared.Libs.DLW.Utils
  Require Import utils_tac gcd prime binomial sums rel_iter.

From Undecidability.H10.ArithLibs
  Require Import Zp.

Set Implicit Arguments.

Set Default Proof Using "Type".

Local Notation power := (mscal mult 1).
Local Notation expo := (mscal mult 1).

Section fact.

  Let factorial_cancel n a b : fact n * a = fact n * b a = b.
  Proof.
    apply Nat.mul_cancel_l.
    generalize (fact_gt_0 n); intro; .
  Qed.


  Notation Π := (msum mult 1).

  Notation mprod_an := ( a n Π n ( i i+a)).

  Fact mprod_factorial n : fact n = mprod_an 1 n.
  Proof.
    induction n as [ | n IHn ].
    + rewrite msum_0; auto.
    + rewrite msum_plus1; auto.
      rewrite mult_comm, IHn, fact_S.
      f_equal; .
  Qed.


  Variable (p : ) (Hp : p 0).

  Notation "〚 x 〛" := (nat2Zp Hp x).

  Let expo_p_cancel n a b : expo n p * a = expo n p * b a = b.
  Proof.
    apply Nat.mul_cancel_l.
    generalize (power_ge_1 n Hp); intros; .
  Qed.


  Fact mprod_factorial_Zp i n :mprod_an (i*p+1) n=fact n.
  Proof.
    rewrite mprod_factorial.
    induction n as [ | n IHn ].
    + do 2 rewrite msum_0; auto.
    + do 2 (rewrite msum_plus1; auto).
      do 2 rewrite nat2Zp_mult; f_equal; auto.
      apply nat2Zp_inj.
      rewrite (plus_comm n), plus_assoc, plus_comm.
      rewrite rem_plus_div; auto.
      * f_equal; .
      * apply divides_mult, divides_refl.
  Qed.


  Notation φ := ( n r mprod_an (n*p+1) r).
  Notation Ψ := ( n Π n ( i mprod_an (i*p+1) (p-1))).

  Let phi_Zp_eq n r :φ n r=fact r.
  Proof. apply mprod_factorial_Zp. Qed.

  Fact mprod_factorial_mult n : fact (n*p) = expo n p * fact n * Ψ n.
  Proof using Hp.
    induction n as [ | n IHn ].
    + rewrite Nat.mul_0_l, msum_0, mscal_0, fact_0; auto.
    + replace (S n*p) with (n*p+p) by ring.
      rewrite mprod_factorial, msum_plus, mprod_factorial; auto.
      replace p with (S (p-1)) at 2 by .
      rewrite msum_plus1; auto.
      rewrite plus_assoc.
      replace (p-1+1) with p by .
      replace (n*p+p) with ((S n)*p) by ring.
      rewrite mscal_S, fact_S, msum_S.
      rewrite IHn.
      repeat rewrite mult_assoc.
      rewrite (mult_comm _ p).
      repeat rewrite mult_assoc.
      do 2 f_equal.
      rewrite (mult_comm (S n)).
      repeat rewrite mult_assoc; f_equal.
      repeat rewrite mult_assoc; f_equal.
      rewrite msum_ext with (f := i n*p+i+1)
                            (g := i i+(n*p+1)).
      2: intros; ring.
      rewrite msum_plus1; auto.
  Qed.


  Lemma mprod_factorial_euclid n r : fact (n*p+r) = expo n p * fact n * φ n r * Ψ n.
  Proof using Hp.
    rewrite mprod_factorial, msum_plus; auto.
    rewrite mprod_factorial.
    rewrite msum_ext with (f := i n*p+i+1)
                          (g := i i+(n*p+1)).
    2: intros; ring.
    rewrite mprod_factorial_mult; auto; ring.
  Qed.


  Notation Zp := (Zp_zero Hp).
  Notation Op := (Zp_one Hp).
  Notation "∸" := (Zp_opp Hp).
  Infix "⊗" := (Zp_mult Hp) (at level 40, left associativity).
  Notation expoZp := (mscal (Zp_mult Hp) (Zp_one Hp)).

  Hint Resolve Nat_mult_monoid : core.

  Let Psi_Zp_eq n :Ψ n= expoZp nfact (p-1).
  Proof.
    induction n as [ | n IHn ].
    + rewrite msum_0, mscal_0; auto.
    + rewrite msum_plus1, nat2Zp_mult.
      rewrite mscal_plus1; auto.
      2: apply Zp_mult_monoid.
      2: apply Nat_mult_monoid.
      f_equal; auto.
  Qed.


  Hypothesis (Hprime : prime p).

  Let phi_Zp_invertible n r : r < p Zp_invertible Hp φ n r.
  Proof.
    intros H; simpl; rewrite phi_Zp_eq.
    apply Zp_invertible_factorial; auto.
  Qed.


  Let Psi_Zp_invertible n : Zp_invertible Hp Ψ n.
  Proof.
    simpl; rewrite (Psi_Zp_eq n).
    apply Zp_expo_invertible, Zp_invertible_factorial; auto; .
  Qed.



  Section binomial_without_p_not_zero.

    Variable (n N k K : ) (Hn : n = N*p+) (Hk : k = K*p+) ( : K N) ( : ).

    Let Hkn : k n.
    Proof.
      rewrite Hn, Hk.
      replace N with (K+(N-K)) by .
      rewrite Nat.mul_add_distr_r.
      generalize ((N-K)*p); intros; .
    Qed.


    Let Hnk : n - k = (N-K)*p+(-).
    Proof.
      rewrite Hn, Hk, Nat.mul_sub_distr_r.
      cut (K*p N*p).
      + generalize (K*p) (N*p); intros; .
      + apply mult_le_compat; auto.
    Qed.


    Fact binomial_wo_p : φ K * Ψ K * φ (N-K) (-) * Ψ (N-K) * binomial n k
                       = binomial N K * φ N * Ψ N.
    Proof using Hkn.
      apply (factorial_cancel (N-K)); repeat rewrite mult_assoc.
      rewrite (mult_comm (fact _) (binomial _ _)).
      apply (factorial_cancel K); repeat rewrite mult_assoc.
      rewrite (mult_comm (fact _) (binomial _ _)).
      rewrite binomial_thm; auto.
      apply expo_p_cancel with N.
      repeat rewrite mult_assoc.
      rewrite mprod_factorial_euclid, Hn.
      rewrite binomial_thm with (1 := Hkn).
      rewrite Hnk.
      rewrite Hk at 3.
      replace N with (K+(N-K)) at 1 by .
      rewrite power_plus.
      do 2 rewrite mprod_factorial_euclid.
      ring.
    Qed.


    Hypothesis ( : < p).

    Hint Resolve Zp_mult_monoid : core.

    Fact binomial_Zp_prod :binomial n k=binomial N Kbinomial .
    Proof using Hkn Hprime.
      generalize binomial_wo_p; intros G.
      apply f_equal with (f := nat2Zp Hp) in G.
      repeat rewrite nat2Zp_mult in G.
      repeat rewrite Psi_Zp_eq in G.
      repeat rewrite phi_Zp_eq in G.
      rewrite binomial_thm with (1 := ) in G.
      repeat rewrite nat2Zp_mult in G.
      rewrite (Zp_mult_comm _ _ fact ) in G.
      repeat rewrite Zp_mult_assoc in G.
      rewrite (Zp_mult_comm _ _ fact ) in G.
      repeat rewrite Zp_mult_assoc in G.
      apply Zp_invertible_cancel_l in G.
      2: apply Zp_invertible_factorial; auto; .
      repeat rewrite Zp_mult_assoc in G.
      do 2 rewrite (Zp_mult_comm _ _ fact _ ) in G.
      repeat rewrite Zp_mult_assoc in G.
      apply Zp_invertible_cancel_l in G.
      2: apply Zp_invertible_factorial; auto; .
      repeat rewrite Zp_mult_assoc in G.
      rewrite mscal_plus in G; auto.
      replace (K+(N-K)) with N in G by .
      rewrite (Zp_mult_comm _ _ (expoZp _ _)) in G.
      apply Zp_invertible_cancel_l in G; trivial.
      apply Zp_expo_invertible, Zp_invertible_factorial; auto; .
    Qed.


  End binomial_without_p_not_zero.

  Section binomial_without_p_zero.

    Variable (n N k K : ) (Hn : n = N*p+) (Hk : k = K*p+)
             ( : K < N) ( : < ) ( : < p).

    Let : p - (-) < p. Proof. . Qed.
    Let : S (N-1) = N. Proof. . Qed.
    Let : N-1 = K+(N-(K+1)). Proof. . Qed.
    Let : N = K+1+(N-(K+1)). Proof. . Qed.
    Let HNK : N-K = S (N-(K+1)). Proof. . Qed.

    Let Hkn : k n.
    Proof.
      rewrite Hn, Hk, .
      do 2 rewrite Nat.mul_add_distr_r.
      generalize ((N-(K+1))*p); clear HNK; intros; .
    Qed.


    Let Hnk : n - k = (N-(K+1))*p+(p-(-)).
    Proof.
      rewrite Hn, Hk, Nat.mul_sub_distr_r.
      cut ((K+1)*p N*p).
      + rewrite Nat.mul_add_distr_r.
        generalize (K*p) (N*p); clear HNK Hkn; intros; .
      + apply mult_le_compat; auto; clear HNK Hkn; .
    Qed.


    Fact binomial_with_p : fact K * fact (N-(K+1)) * φ K * Ψ K * φ (N-(K+1)) (p-(-)) * Ψ (N-(K+1)) * binomial n k
                         = p * fact N * φ N * Ψ N.
    Proof using Hkn.
      apply expo_p_cancel with (N-1).
      repeat rewrite mult_assoc.
      rewrite (mult_comm (expo _ _) p).
      rewrite mscal_S.
      rewrite , mprod_factorial_euclid, Hn.
      rewrite binomial_thm with (1 := Hkn).
      rewrite Hnk.
      rewrite Hk at 3.
      do 2 rewrite mprod_factorial_euclid.
      rewrite at 1.
      rewrite power_plus.
      ring.
    Qed.


    Fact binomial_with_p' : φ K * Ψ K * φ (N-(K+1)) (p-(-)) * Ψ (N-(K+1)) * binomial n k
                          = p * binomial N K * (N-K) * φ N * Ψ N.
    Proof using Hkn.
      apply (factorial_cancel (N-(K+1))); repeat rewrite mult_assoc.
      apply (factorial_cancel K); repeat rewrite mult_assoc.
      rewrite binomial_with_p.
      rewrite binomial_thm with (n := N) (p := K).
      2: { apply lt_le_weak; auto. }
      rewrite HNK at 1.
      rewrite fact_S.
      rewrite HNK.
      ring.
    Qed.


    Fact binomial_Zp_zero :binomial n k= Zp.
    Proof using Hkn Hprime.
      generalize binomial_with_p'; intros G.
      apply f_equal with (f := nat2Zp Hp) in G.
      repeat rewrite nat2Zp_mult in G.
      rewrite nat2Zp_p in G.
      repeat rewrite Zp_mult_zero in G.
      apply Zp_invertible_eq_zero in G; auto.
      repeat (apply Zp_mult_invertible; auto).
    Qed.


  End binomial_without_p_zero.

End fact.

Section lucas_lemma.


  Variables (p : ) (Hprime : prime p).

  Let Hp : p 0.
  Proof.
    generalize (prime_ge_2 Hprime); intro; .
  Qed.


  Variables (n N k K : )
            ( : n = N*p+) ( : < p)
            ( : k = K*p+) ( : < p).

  Let choice : (K N )
             ( < K < N)
             (( < N < K) n < k).
  Proof.
    destruct (le_lt_dec k n) as [ | ];
    destruct (le_lt_dec ) as [ | ];
    destruct (le_lt_dec K N) as [ | ]; try .
    + do 2 right; split; auto.
      rewrite , .
      replace K with (N+1+(K-N-1)) by .
      do 2 rewrite Nat.mul_add_distr_r.
      generalize ((K-N-1)*p); intros; .
    + destruct (eq_nat_dec N K); try .
    + do 2 right; split; auto.
      rewrite , .
      replace K with (N+1+(K-N-1)) by .
      do 2 rewrite Nat.mul_add_distr_r.
      generalize ((K-N-1)*p); intros; .
  Qed.


  Theorem lucas_lemma : rem (binomial n k) p = rem (binomial N K * binomial ) p.
  Proof using choice.
    destruct choice as [ ( & )
                     | [ ( & )
                       | ( & ) ] ]; clear choice.
    3: { rewrite binomial_gt with (1 := ).
         f_equal.
         destruct as [ | ];
           rewrite binomial_gt with (1 := ); ring. }
    + apply nat2Zp_inj with (Hp := Hp).
      rewrite nat2Zp_mult.
      apply binomial_Zp_prod; auto.
    + rewrite binomial_gt with (1 := ).
      rewrite Nat.mul_0_r.
      apply nat2Zp_inj with (Hp := Hp).
      rewrite nat2Zp_zero.
      apply binomial_Zp_zero with (2 := ) (3 := ); auto.
  Qed.


End lucas_lemma.


Section lucas_theorem.

  Variable (p : ) (Hp : prime p).

  Implicit Types (l m : list ).


  Notation base_p := (expand p).

  Fixpoint binomial_p l :=
    match l with
      | nil fix loop m := match m with
        | nil 1
        | y::m binomial 0 y * loop m
      end
      | x::l m match m with
        | nil binomial x 0 * binomial_p l nil
        | y::m binomial x y * binomial_p l m
      end
    end.

  Fact binomial_p_fix00 : binomial_p nil nil = 1.
  Proof. auto. Qed.

  Fact binomial_p_fix01 y m : binomial_p nil (y::m) = binomial 0 y * binomial_p nil m.
  Proof. auto. Qed.

  Fact binomial_p_fix10 x l : binomial_p (x::l) nil = binomial x 0 * binomial_p l nil.
  Proof. auto. Qed.

  Fact binomial_p_fix11 x l y m : binomial_p (x::l) (y::m) = binomial x y * binomial_p l m.
  Proof. auto. Qed.


  Theorem lucas_theorem (l m : list ) :
         Forall ( i i < p) l
       Forall ( i i < p) m
       rem (binomial (base_p l) (base_p m)) p
       = rem (binomial_p l m) p.
  Proof using Hp.
    intros H; revert H m.
    induction 1 as [ | x l ];
    induction 1 as [ | y m ].
    + simpl; auto.
    + rewrite binomial_p_fix01; simpl base_p.
      rewrite rem_mult_rem, , rem_mult_rem,
              (mult_comm p), plus_comm, (mult_comm (binomial _ _)).
      apply lucas_lemma; auto; simpl; .
    + rewrite binomial_p_fix10; simpl base_p.
      rewrite rem_mult_rem, , rem_mult_rem; auto.
      rewrite (mult_comm p), plus_comm, (mult_comm (binomial _ _)).
      apply lucas_lemma; auto; simpl; .
    + rewrite binomial_p_fix11; simpl base_p.
      rewrite rem_mult_rem, , rem_mult_rem; auto.
      rewrite !(mult_comm p), !(plus_comm _ (_ * _)), (mult_comm (binomial _ _)).
      apply lucas_lemma; auto; simpl; .
  Qed.


End lucas_theorem.