Require Import Crypto.Spec.MxDH.
Require Import Crypto.Algebra.Monoid Crypto.Algebra.Group Crypto.Algebra.Ring Crypto.Algebra.Field.
Require Import Crypto.Util.Tuple Crypto.Util.Prod.
Require Import Crypto.Util.Tactics.DestructHead.
Require Import Crypto.Util.Tactics.BreakMatch.

Section MxDHRepChange.
  Context {F Feq Fzero Fone Fopp Fadd Fsub Fmul Finv Fdiv} {field:@Algebra.Hierarchy.field F Feq Fzero Fone Fopp Fadd Fsub Fmul Finv Fdiv} {Feq_dec:Decidable.DecidableRel Feq} {Fcswap:bool->F*F->F*F->(F*F)*(F*F)} {Fa24:F} {tb1:nat->bool}.
  Context {K Keq Kzero Kone Kopp Kadd Ksub Kmul Kinv Kdiv} {impl_field:@Algebra.Hierarchy.field K Keq Kzero Kone Kopp Kadd Ksub Kmul Kinv Kdiv} {Keq_dec:Decidable.DecidableRel Keq} {Kcswap:bool->K*K->K*K->(K*K)*(K*K)} {Ka24:K} {tb2:nat->bool}.

  Context {FtoK} {homom:@Ring.is_homomorphism F Feq Fone Fadd Fmul
                                              K Keq Kone Kadd Kmul FtoK}.
  Context {homomorphism_inv_zero:Keq (FtoK (Finv Fzero)) (Kinv Kzero)}.
  Context {homomorphism_a24:Keq (FtoK Fa24) Ka24}.
  Context {Fcswap_correct:forall b x y, Fcswap b x y = if b then (y,x) else (x,y)}.
  Context {Kcswap_correct:forall b x y, Kcswap b x y = if b then (y,x) else (x,y)}.
  Context {tb2_correct:forall i, tb2 i = tb1 i}.

  (* TODO: move to algebra *)
  Lemma homomorphism_multiplicative_inverse_complete' x : Keq (FtoK (Finv x)) (Kinv (FtoK x)).
  Proof using Feq_dec Keq_dec field homom homomorphism_inv_zero impl_field.
    eapply (homomorphism_multiplicative_inverse_complete).
    intro J; rewrite J. rewrite homomorphism_inv_zero, homomorphism_id.
    reflexivity.
  Qed.

  Ltac t :=
    let hom := match goal with H : is_homomorphism |- _ => H end in
    let mhom := constr:(@homomorphism_is_homomorphism _ _ _ _ _ _ _ _ _ _ _ hom) in
    repeat (
        rewrite (@homomorphism_id _ _ _ _ _ _ _ _ _ _ _ _ _ mhom) ||
        rewrite (@homomorphism_one _ _ _ _ _ _ _ _ _ _ _ hom) ||
        rewrite (@homomorphism_sub _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ hom) ||
        rewrite (@homomorphism_add _ _ _ _ _ _ _ _ _ _ _ hom) ||
        rewrite (@homomorphism_mul _ _ _ _ _ _ _ _ _ _ _ hom) ||
        rewrite homomorphism_a24 ||
        rewrite homomorphism_multiplicative_inverse_complete' ||
        reflexivity
      ).

  (*Import Crypto.Util.Tactics.*)
  Import List.
  Import Coq.Classes.Morphisms.

  Global Instance Proper_ladderstep :
    Proper (Keq ==> (fieldwise (n:=2) Keq) ==> fieldwise (n:=2) Keq ==> fieldwise (n:=2) (fieldwise (n:=2) Keq)) (@MxDH.ladderstep K Kadd Ksub Kmul Ka24).
  Proof using Keq_dec impl_field.
    cbv [MxDH.ladderstep tuple tuple' fieldwise fieldwise' fst snd];
      repeat intro; destruct_head' prod; destruct_head' and; repeat split;
        repeat match goal with [H:Keq ?x ?y |- _ ] => rewrite !H; clear H x end; reflexivity.
  Qed.

  Lemma MxLadderstepRepChange u P Q Ku (Ku_correct:Keq (FtoK u) Ku):
    fieldwise (n:=2) (fieldwise (n:=2) Keq)
    ((Tuple.map (n:=2) (Tuple.map (n:=2) FtoK)) (@MxDH.ladderstep F Fadd Fsub Fmul Fa24 u P Q))
    (@MxDH.ladderstep K Kadd Ksub Kmul Ka24 Ku (Tuple.map (n:=2) FtoK P) (Tuple.map (n:=2) FtoK Q)).
  Proof using Feq_dec Keq_dec field homom homomorphism_a24 impl_field.
    destruct P as [? ?], Q as [? ?]; cbv; repeat split; rewrite <-?Ku_correct; t.
  Qed.

  Let loopiter_sig F Fzero Fone Fadd Fsub Fmul Finv Fa24 Fcswap b tb u :
    { loopiter | @MxDH.montladder F Fzero Fone Fadd Fsub Fmul Finv Fa24 Fcswap b tb u =
      let '(_, _, _) := MxDH.downto _ _ loopiter in  _ } := exist _ _ eq_refl.
  Let loopiter F Fzero Fone Fadd Fsub Fmul Finv Fa24 Fcswap b tb u :=
  Eval cbv [proj1_sig loopiter_sig] in (
    proj1_sig (loopiter_sig F Fzero Fone Fadd Fsub Fmul Finv Fa24 Fcswap b tb u)).

  Let loopiter_phi s : ((K * K) * (K * K)) * bool :=
    (Tuple.map (n:=2) (Tuple.map (n:=2) FtoK) (fst s), snd s).

  Let loopiter_eq (a b: (((K * K) * (K * K)) * bool)) :=
    fieldwise (n:=2) (fieldwise (n:=2) Keq) (fst a) (fst b) /\ snd a = snd b.

  Local Instance Equivalence_loopiter_eq : Equivalence loopiter_eq.
  Proof using Keq_dec impl_field loopiter_phi.
    unfold loopiter_eq; split; repeat intro;
      intuition (reflexivity||symmetry;eauto||etransitivity;symmetry;eauto).
  Qed.

  Lemma MxLoopIterRepChange b Fu s i Ku (HKu:Keq (FtoK Fu) Ku) : loopiter_eq
    (loopiter_phi (loopiter F Fzero Fone Fadd Fsub Fmul Finv Fa24 Fcswap b tb1 Fu s i))
    (loopiter K Kzero Kone Kadd Ksub Kmul Kinv Ka24 Kcswap b tb2 Ku (loopiter_phi s) i).
  Proof using Fcswap_correct Feq_dec Kcswap_correct Keq_dec field homom homomorphism_a24 impl_field tb2_correct.
    destruct_head' prod; break_match.
    simpl.
    rewrite !Fcswap_correct, !Kcswap_correct, tb2_correct in *.
    break_match; cbv [loopiter_eq loopiter_phi fst snd]; split; try reflexivity;
      (etransitivity;[|etransitivity]; [ | eapply (MxLadderstepRepChange _ _ _ _ HKu) | ];
            match goal with Heqp:_ |- _ => rewrite <-Heqp; reflexivity end).
  Qed.

  Global Instance Proper_fold_left {A RA B RB} :
    Proper ((RA==>RB==>RA) ==> SetoidList.eqlistA RB ==> RA ==> RA) (@fold_left A B).
  Proof using Type.
    intros ? ? ? ? ? Hl; induction Hl; repeat intro; [assumption|].
    simpl; cbv [Proper respectful] in *; eauto.
  Qed.

  Lemma proj_fold_left {A B L} R {Equivalence_R:@Equivalence B R} (proj:A->B) step step' {Proper_step':(R ==> eq ==> R)%signature step' step'} (xs:list L) init init' (H0:R (proj init) init') (Hproj:forall x i, R (proj (step x i)) (step' (proj x) i)) :
         R (proj (fold_left step xs init)) (fold_left step' xs init').
  Proof using Type.
    generalize dependent init; generalize dependent init'.
    induction xs as [|?? IHxs]; [solve [eauto]|].
    repeat intro; simpl; rewrite IHxs by eauto.
    apply (_ : Proper ((R ==> eq ==> R) ==> SetoidList.eqlistA eq ==> R ==> R) (@fold_left _ _));
      try reflexivity;
      eapply Proper_step'; eauto.
  Qed.

  Global Instance Proper_downto {T R} {Equivalence_R:@Equivalence T R} :
    Proper (R ==> Logic.eq ==> (R==>Logic.eq==>R) ==> R) MxDH.downto.
  Proof using Type.
    intros s0 s0' Hs0 n' n Hn'; subst n'; generalize dependent s0; generalize dependent s0'.
    induction n; repeat intro; [assumption|].
    unfold MxDH.downto; simpl.
    eapply Proper_fold_left; try eassumption; try reflexivity.
    cbv [Proper respectful] in *; eauto.
  Qed.

  Global Instance Proper_loopiter a b c :
   Proper (loopiter_eq ==> eq ==> loopiter_eq) (loopiter K Kzero Kone Kadd Ksub Kmul Kinv Ka24 Kcswap a b c).
  Proof using Kcswap_correct Keq_dec impl_field.
    unfold loopiter; intros [? ?] [? ?] [[[] []] ?]; repeat intro ; cbv [fst snd] in * |-; subst.
    repeat (break_match; break_match_hyps).
    split; [|reflexivity].
    etransitivity; [|etransitivity]; [ | eapply Proper_ladderstep | ];
      [eapply eq_subrelation; [ exact _ | symmetry; eassumption ]
      | reflexivity | |
      | eapply eq_subrelation; [exact _ | eassumption ] ];
      rewrite !Kcswap_correct in *;
      match goal with [H: (if ?b then _ else _) = _ |- _] => destruct b end;
      destruct_head prod; inversion_prod; subst; eauto.
  Qed.

  Lemma MxDHRepChange b (x:F) :
    Keq
      (FtoK (@MxDH.montladder F Fzero Fone Fadd Fsub Fmul Finv Fa24 Fcswap b tb1 x))
      (@MxDH.montladder K Kzero Kone Kadd Ksub Kmul Kinv Ka24 Kcswap b tb2 (FtoK x)).
  Proof using Fcswap_correct Feq_dec Kcswap_correct Keq_dec field homom homomorphism_a24 homomorphism_inv_zero impl_field tb2_correct.
    cbv [MxDH.montladder].
    repeat break_match.
    assert (Hrel:loopiter_eq (loopiter_phi (p, p0, b0)) (p1, p3, b1)).
    {
      rewrite <-Heqp0; rewrite <-Heqp.
      unfold MxDH.downto.
      eapply (proj_fold_left (L:=nat) loopiter_eq loopiter_phi).
      { eapply @Proper_loopiter. }
      { cbv; repeat split; t. }
      { intros; eapply MxLoopIterRepChange; reflexivity. }
    }
    { destruct_head' prod; destruct Hrel as [[[] []] ?]; simpl in *; subst.
      rewrite !Fcswap_correct, !Kcswap_correct in *.
      match goal with [H: (if ?b then _ else _) = _ |- _] => destruct b end;
        destruct_head prod; inversion_prod; subst;
        repeat match goal with [H: Keq (FtoK ?x) (?kx)|- _ ] => rewrite <- H end;
        t.
    }
    Unshelve.
    exact 0.
    exact 0.
  Qed.
End MxDHRepChange.
