From Coq Require Import ZArith Lia.
Require Import Crypto.Util.ZUtil.Peano.
From Coq Require Import Morphisms.
Require Import Crypto.Util.Tactics.BreakMatch.
Require Import Crypto.Algebra.Hierarchy Crypto.Algebra.Group.
Local Open Scope Z_scope.

Section ScalarMultProperties.
  Context {G eq add zero opp} `{groupG:@group G eq add zero opp}.
  Context {mul:Z->G->G}.
  Local Infix "=" := eq : type_scope. Local Infix "=" := eq.
  Local Infix "+" := add. Local Infix "*" := mul.
  Class is_scalarmult :=
    {
      scalarmult_0_l : forall P, 0 * P = zero;
      scalarmult_succ_l_nn : forall n P, 0 <= n -> Z.succ n * P = P + n * P;
      scalarmult_pred_l_np : forall n P, n <= 0 -> Z.pred n * P = opp P + n * P;

      scalarmult_Proper : Proper (Logic.eq==>eq==>eq) mul
    }.
  Global Existing Instance scalarmult_Proper.
  Context `{mul_is_scalarmult:is_scalarmult}.

  Lemma scalarmult_succ_l n P : Z.succ n * P = P + n * P.
  Proof using groupG mul_is_scalarmult.
    induction n using Z.peano_rect_strong; intros; rewrite ?Z.succ'_succ, ?Z.pred'_pred in * by lia;
      repeat (rewrite ?scalarmult_0_l, ?scalarmult_succ_l_nn, ?scalarmult_pred_l_np, ?left_identity, ?right_identity, ?Z.succ_pred, ?Z.pred_succ, ?associative, ?right_inverse, ?left_inverse by lia); reflexivity.
  Qed.

  Lemma scalarmult_pred_l n P : Z.pred n * P = opp P + n * P.
    induction n using Z.peano_rect_strong; intros; rewrite ?Z.succ'_succ, ?Z.pred'_pred in * by lia;
      repeat (rewrite ?scalarmult_0_l, ?scalarmult_succ_l_nn, ?scalarmult_pred_l_np, ?left_identity, ?right_identity, ?Z.succ_pred, ?Z.pred_succ, ?associative, ?right_inverse, ?left_inverse by lia); reflexivity.
  Qed.

  Definition scalarmult_ref (n:Z) (P:G) : G :=
    Z.peano_rect
      (fun _ => G)
      (zero)
      (fun _ => add P)
      (fun _ => add (opp P))
      n
  .

  Global Instance Proper_scalarmult_ref : Proper (Logic.eq==>eq==>eq) scalarmult_ref.
  Proof using groupG.
    intros n n' H; subst n';
      induction n using Z.peano_rect_strong;
      cbv [Proper respectful] in *;
      intros;
      cbv [scalarmult_ref] in *; break_match; try reflexivity;
      rewrite ?Z.peano_rect_succ, ?Z.peano_rect_pred in * by lia;
      rewrite IHn by eassumption;
      match goal with H : _ = _ |- _ => rewrite H; reflexivity end.
  Qed.

  Lemma scalarmult_ext n P : mul n P = scalarmult_ref n P.
  Proof using Type*.
    revert P.
    destruct mul_is_scalarmult.
    induction n using Z.peano_rect_strong; intros;
      cbv [scalarmult_ref] in *; break_match;
        rewrite ?Z.peano_rect_succ, ?Z.peano_rect_pred, ?Z.succ'_succ, ?Z.pred'_pred in *;
        try rewrite <-IHn; try match goal with [H:_|-_] => eapply H end; lia.
  Qed.

  Lemma scalarmult_1_l P : 1*P = P.
  Proof using Type*.
    intros. change 1 with (Z.succ 0).
    rewrite scalarmult_succ_l, scalarmult_0_l, right_identity; reflexivity.
  Qed.

  Lemma scalarmult_opp1_l P : -1*P = opp P.
    change (-1) with (Z.pred 0).
    rewrite scalarmult_pred_l, scalarmult_0_l, right_identity; reflexivity.
  Qed.

  Lemma scalarmult_add_l (n m:Z) (P:G) : ((n + m)%Z * P = n * P + m * P).
  Proof using Type*.
    revert P.
    induction n using Z.peano_rect_strong; intros; rewrite ?Z.succ'_succ, ?Z.pred'_pred in * by lia;
        rewrite ?Z.add_0_l, ?Z.add_succ_l, ?Z.add_pred_l;
        rewrite ?scalarmult_0_l, ?scalarmult_succ_l, ?scalarmult_pred_l, ?left_identity, <-?associative, <-?IHn; try reflexivity.
  Qed.

  Lemma scalarmult_opp_l (n:Z) (P:G) : (-n) * P = opp (n*P).
  Proof using Type*.
    revert P.
    induction n using Z.peano_rect_strong; intros; rewrite ?Z.succ'_succ, ?Z.pred'_pred in * by lia.
    { change (-0) with (0). rewrite !scalarmult_0_l, inv_id; reflexivity. }
    { rewrite scalarmult_succ_l_nn, inv_op, <-IHn by lia.
      replace (-Z.succ n) with (-n + (- 1))%Z by auto with zarith.
      rewrite scalarmult_add_l, scalarmult_opp1_l; reflexivity. }
    { rewrite scalarmult_pred_l_np, inv_op, <-IHn, inv_inv by lia.
      replace (- Z.pred n) with (-n+1)%Z by auto with zarith.
      rewrite scalarmult_add_l, scalarmult_1_l; reflexivity. }
  Qed.

  Lemma scalarmult_zero_r (n:Z) : n * zero = zero.
    induction n using Z.peano_rect_strong; intros; rewrite ?Z.succ'_succ, ?Z.pred'_pred in * by lia;
        rewrite ?scalarmult_0_l, ?scalarmult_succ_l_nn, ?scalarmult_pred_l_np, ?IHn, ?inv_id, ?left_identity by lia;
        try reflexivity.
  Qed.

  Lemma scalarmult_assoc (n m : Z) P : n * (m * P) = (m * n)%Z * P.
  Proof using Type*.
    revert P.
    induction n using Z.peano_rect_strong; intros; rewrite ?Z.succ'_succ, ?Z.pred'_pred in * by lia.
    { rewrite Z.mul_0_r, 2scalarmult_0_l by lia; reflexivity. }
    { rewrite scalarmult_succ_l, IHn by lia.
      rewrite (Z.mul_comm m (Z.succ n)), Z.mul_succ_l, (Z.mul_comm n m), (Z.add_comm (m*n) m).
      rewrite scalarmult_add_l. reflexivity. }
    { rewrite scalarmult_pred_l, IHn by lia.
      rewrite (Z.mul_comm m (Z.pred n)), Z.mul_pred_l, (Z.mul_comm n m), <-Z.add_opp_l.
      rewrite scalarmult_add_l, scalarmult_opp_l. reflexivity. }
  Qed.

  Lemma scalarmult_sub_l (a b:Z) (P:G) : (a - b)*P = a*P + opp(b*P).
  Proof using Type*.
    rewrite <-Z.add_opp_r, scalarmult_add_l, scalarmult_opp_l; reflexivity.
  Qed.

  Lemma scalarmult_opp_r (n:Z) (P:G) : n*opp P = opp (n * P).
  Proof using Type*.
    revert P.
    induction n using Z.peano_rect_strong; intros; rewrite ?Z.succ'_succ, ?Z.pred'_pred in * by lia;
      rewrite <-?Z.add_1_l, <-?Z.sub_1_r;
      [|rewrite Z.add_comm at 1|rewrite <-Z.add_opp_l; rewrite Z.add_comm at 1];
      repeat (rewrite ?scalarmult_0_l, ?scalarmult_opp_l, ?scalarmult_add_l, ?inv_id, ?inv_op, ?inv_inv, ?IHn, ?scalarmult_1_l);
      reflexivity.
  Qed.

  Lemma scalarmult_times_order : forall l B, l*B = zero -> forall n, (l * n) * B = zero.
  Proof using Type*. intros ? ? Hl ?. rewrite <-scalarmult_assoc, Hl, scalarmult_zero_r. reflexivity. Qed.

  Lemma scalarmult_mod_order : forall l B, l <> 0 -> l*B = zero -> forall n, n mod l * B = n * B.
  Proof using Type*.
    intros l B Hnz Hmod n.
    rewrite (Z.div_mod n l Hnz) at 2.
    rewrite scalarmult_add_l, scalarmult_times_order, left_identity by auto. reflexivity.
  Qed.

End ScalarMultProperties.

Section ScalarMultHomomorphism.
  Context {G EQ ADD ZERO OPP} {groupG:@group G EQ ADD ZERO OPP}.
  Context {H eq add zero opp} {groupH:@group H eq add zero opp}.
  Declare Scope eq_scope.
  Local Infix "=" := eq : type_scope. Local Infix "=" := eq : eq_scope.
  Context {MUL} {MUL_is_scalarmult:@is_scalarmult G EQ ADD ZERO OPP MUL}.
  Context {mul} {mul_is_scalarmult:@is_scalarmult H eq add zero opp mul}.
  Context {phi} {homom:@Monoid.is_homomorphism G EQ ADD H eq add phi}.

  Lemma homomorphism_scalarmult : forall n P, phi (MUL n P) = mul n (phi P).
  Proof using Type*.
    induction n using Z.peano_rect_strong; intros; rewrite ?Z.succ'_succ, ?Z.pred'_pred in * by lia;
      rewrite ?scalarmult_0_l, ?scalarmult_succ_l, ?scalarmult_pred_l by lia.
    { apply homomorphism_id. }
    { rewrite <-IHn. rewrite Monoid.homomorphism. reflexivity. }
    { rewrite <-IHn. rewrite Monoid.homomorphism, Group.homomorphism_inv. reflexivity. }
  Qed.
End ScalarMultHomomorphism.

Global Instance scalarmult_ref_is_scalarmult {G eq add zero opp} {groupG:@group G eq add zero opp}
  : @is_scalarmult G eq add zero opp (@scalarmult_ref G add zero opp).
Proof.
  split; try exact _; cbv [scalarmult_ref] in *; intros;
    rewrite <-?Z.succ'_succ, <-?Z.pred'_pred, ?Z.peano_rect_succ, ?Z.peano_rect_pred in * by lia;
    reflexivity.
Qed.
