(**
This file is part of the Elfic library

Copyright (C) Boldo, Clément, Faissole, Martin, Mayero

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
COPYING file for more details.
*)

(* All that should be useless for Subset_system once using Bigops from MathComp. *)

From Coq Require Import (*Classical*) FunctionalExtensionality.
From Coq Require Import (*RelationClasses*) Morphisms.
From Coq Require Import Arith Lia Reals Lra.

Require Import nat_compl.


Section sFun_Def0.

Context {E F : Type}.

Variable eqE : E -> E -> Prop.
Variable eqF : F -> F -> Prop.

Variable PE : E -> Prop.
Variable PF : F -> Prop.

Variable f : E -> F.

Definition sInjective : Prop :=
  forall x1 x2, PE x1 -> PE x2 -> eqF (f x1) (f x2) -> eqE x1 x2.

Definition sSurjective : Prop :=
  forall y, PF y -> exists x, PE x /\ eqF (f x) y.

End sFun_Def0.


Section sFun_Def1.

Context {E1 E2 : Type}.

Variable eq1 : E1 -> E1 -> Prop.

Variable P1 : E1 -> Prop.
Variable P2 : E2 -> Prop.

Variable f12 : E1 -> E2.
Variable f21 : E2 -> E1.

Definition range : Prop :=
  forall x1, P1 x1 -> P2 (f12 x1).

Definition identity : Prop :=
  forall x1, P1 x1 -> eq1 (f21 (f12 x1)) x1.

End sFun_Def1.


Section sFun_Def2.

Context {E1 E2 : Type}.

Variable eq1 : E1 -> E1 -> Prop.
Variable eq2 : E2 -> E2 -> Prop.

Variable P1 : E1 -> Prop.
Variable P2 : E2 -> Prop.

Variable f12 : E1 -> E2.
Variable f21 : E2 -> E1.

Definition sBijective : Prop :=
  range P1 P2 f12 /\ range P2 P1 f21 /\
  identity eq1 P1 f12 f21 /\ identity eq2 P2 f21 f12.

End sFun_Def2.


Section sFun_Fact.

Context {E1 E2 : Type}.

Variable eq1 : E1 -> E1 -> Prop.
Variable eq2 : E2 -> E2 -> Prop.

Hypothesis Eq1 : Equivalence eq1.
Hypothesis Eq2 : Equivalence eq2.

Variable P1 : E1 -> Prop.
Variable P2 : E2 -> Prop.

Variable f12 : E1 -> E2.
Variable f21 : E2 -> E1.

Hypothesis F21 : respectful eq2 eq1 f21 f21.
(*Hypothesis F21 : respectful_hetero... *)

Lemma sBijective_equiv :
  sBijective eq1 eq2 P1 P2 f12 f21 -> sBijective eq2 eq1 P2 P1 f21 f12.
Proof.
now intros [H H'].
Qed.

Lemma sBijective_sSurjective :
  sBijective eq1 eq2 P1 P2 f12 f21 -> sSurjective eq2 P1 P2 f12.
Proof.
intros [_ [H [_ H']]] x2 Hx2.
exists (f21 x2); split.
now apply H.
now apply H'.
Qed.

Lemma sBijective_sInjective :
  sBijective eq1 eq2 P1 P2 f12 f21 -> sInjective eq1 eq2 P1 f12.
Proof.
intros [_ [_ [H _]]] x1 y1 Hx1 Hy1 H1.
rewrite <- (H x1), <- (H y1); try easy.
now f_equiv.
Qed.

End sFun_Fact.


Section Finite_bijection_prod_Def.

(* The common cardinality of
  {n | n < S N} * {m | m < S M} and {p | p < S N * S M}
  is N * M + N + M = pred (S N * S M). *)

Variable phi : nat -> nat * nat.
Variable psi : nat * nat -> nat.
Variable N M : nat.

Definition prod_Pl : nat -> Prop :=
  fun p => p < S N * S M.

Definition prod_Pr : nat * nat -> Prop :=
  fun nm => fst nm < S N /\ snd nm < S M.

Definition prod_range_l := range prod_Pl prod_Pr phi.
Definition prod_range_r := range prod_Pr prod_Pl psi.
Definition prod_identity_l := identity eq prod_Pl phi psi.
Definition prod_identity_r := identity eq prod_Pr psi phi.
Definition prod_bBijective := sBijective eq eq prod_Pl prod_Pr phi psi.
Definition prod_bInjective_l := sInjective eq eq prod_Pl phi.
Definition prod_bSurjective_l := sSurjective eq prod_Pl prod_Pr phi.
Definition prod_bInjective_r := sInjective eq eq prod_Pr psi.
Definition prod_bSurjective_r := sSurjective eq prod_Pr prod_Pl psi.

End Finite_bijection_prod_Def.


Section Finite_bijection_prod.

Lemma prod_bBijective_ex :
  forall N M, exists (phi : nat -> nat * nat) (psi : nat * nat -> nat),
    prod_bBijective phi psi N M.
Proof.
intros N M.
pose (phi := fun p => (p mod S N, p / S N)); exists phi.
pose (psi := fun nm => S N * snd nm + fst nm); exists psi.
split; [ | repeat split].
(* *)
intros p Hp; unfold phi; split.
apply Nat.mod_upper_bound; lia.
apply Nat.div_lt_upper_bound; [lia | easy].
(* *)
intros nm [Hn Hm]; unfold psi, prod_Pl.
rewrite Nat.lt_succ_r in Hn, Hm; rewrite lt_mul_S, pred_mul_S, Nat.lt_succ_r.
apply plus_le_compat; try easy.
now apply mult_le_compat_l.
(* *)
intros p Hp; unfold phi, psi.
rewrite (Nat.div_mod p (S N)) at 5; simpl; lia.
(* *)
intros nm [Hn Hm]; unfold phi, psi.
rewrite <- (Nat.mod_unique _ _ (snd nm) (fst nm)); try easy.
rewrite <- (Nat.div_unique _ _ (snd nm) (fst nm)); try easy.
now rewrite <- surjective_pairing.
Qed.

Lemma prod_bBijective_bInjective_l :
  forall (phi : nat -> nat * nat) (psi : nat * nat -> nat) N M,
    prod_bBijective phi psi N M -> prod_bInjective_l phi N M.
Proof.
intros phi psi N M H.
apply sBijective_sInjective with (prod_Pr N M) psi;
    now try apply eq_equivalence.
Qed.

Lemma prod_bBijective_bSurjective_l :
  forall (phi : nat -> nat * nat) (psi : nat * nat -> nat) N M,
    prod_bBijective phi psi N M -> prod_bSurjective_l phi N M.
Proof.
intros phi psi N M H.
now apply sBijective_sSurjective with eq psi.
Qed.

Lemma prod_bBijective_bInjective_r :
  forall (phi : nat -> nat * nat) (psi : nat * nat -> nat) N M,
    prod_bBijective phi psi N M -> prod_bInjective_r psi N M.
Proof.
intros phi psi N M H; apply sBijective_equiv in H.
apply sBijective_sInjective with (prod_Pl N M) phi;
    now try apply eq_equivalence.
Qed.

Lemma prod_bBijective_bSurjective_r :
  forall (phi : nat -> nat * nat) (psi : nat * nat -> nat) N M,
    prod_bBijective phi psi N M -> prod_bSurjective_r psi N M.
Proof.
intros phi psi N M H; apply sBijective_equiv in H.
now apply sBijective_sSurjective with eq phi.
Qed.

End Finite_bijection_prod.


Section Finite_bijection_pow_Def.

(* The common cardinality of
  {n | n < S N} -> {m | m < S M} and {p | p < pow (S M) (S N)} is
  M * \sum_{n | n < S N} pow (S M) n = pred (pow (S M) (S N)). *)

Variable phi : nat -> nat -> nat.
Variable psi : (nat -> nat) -> nat.
Variable N M : nat.

Definition pow_Eqr : (nat -> nat) -> (nat -> nat) -> Prop :=
  fun f1 f2 => forall n, n < S N -> f1 n = f2 n.

Lemma pow_Eqr_refl :
  Reflexive pow_Eqr.
Proof.
now intros f n Hn.
Qed.

Lemma pow_Eqr_sym :
  Symmetric pow_Eqr.
Proof.
intros f1 f2 Hf n Hn; symmetry; now apply Hf.
Qed.

Lemma pow_Eqr_trans :
  Transitive pow_Eqr.
Proof.
intros f1 f2 f3 Hf12 Hf23 n Hn; now rewrite Hf12, <- Hf23.
Qed.

Definition pow_Eqr_equivalence : Equivalence pow_Eqr :=
  Build_Equivalence pow_Eqr pow_Eqr_refl pow_Eqr_sym pow_Eqr_trans.

Definition pow_Pl : nat -> Prop :=
  fun p => p < Nat.pow (S M) (S N).

Definition pow_Pr : (nat -> nat) -> Prop :=
  fun f => forall n, n < S N -> f n < S M.

Definition pow_range_l := range pow_Pl pow_Pr phi.
Definition pow_range_r := range pow_Pr pow_Pl psi.
Definition pow_identity_l := identity eq pow_Pl phi psi.
Definition pow_identity_r := identity pow_Eqr pow_Pr psi phi.
Definition pow_bBijective := sBijective eq pow_Eqr pow_Pl pow_Pr phi psi.
Definition pow_bInjective_l := sInjective eq pow_Eqr pow_Pl phi.
Definition pow_bSurjective_l := sSurjective pow_Eqr pow_Pl pow_Pr phi.
Definition pow_bInjective_r := sInjective pow_Eqr eq pow_Pr psi.
Definition pow_bSurjective_r := sSurjective eq pow_Pr pow_Pl psi.

Lemma pow_respect_l :
  respectful eq pow_Eqr phi phi.
Proof.
intros p1 p2 Hp; rewrite Hp; apply pow_Eqr_refl.
Qed.

(* This one is wrong, must change this statement!
Lemma pow_respect_r :
  pow_bBijective -> respectful pow_Eqr eq psi psi.
Proof.
intros [H1 [H2 [H3 H4]]] f1 f2 Hf.
Aglopted.
*)

End Finite_bijection_pow_Def.


Open Scope R_scope.


Section Finite_bijection_pow.

(* Provide a correct expression.
Lemma pow_bBijective_ex :
  forall N M, exists (phi : nat -> nat -> nat) (psi : (nat -> nat) -> nat),
    pow_bBijective phi psi N M.
Proof.
intros N M.
(* A correct formula for phi could be something like
 the n-th digit of p written in basis (S M). *)
pose (phi := fun (p : nat) (n : nat) => 42); exists phi.
pose (psi := fun (f : nat -> nat) => 42); exists psi.
split; [ | repeat split].
(* *)
intros p Hp; unfold phi, psi.

aglop.

(* *)
intros f Hf; unfold phi, psi.

aglop.

(* *)
intros p Hp; unfold phi, psi.

aglop.

(* *)
intros f Hf.

aglop.

Aglopted.
*)

(*
Lemma pow_bBijective_bInjective_l :
  forall (phi : nat -> nat -> nat) (psi : (nat -> nat) -> nat) N M,
    pow_bBijective phi psi N M -> pow_bInjective_l phi N M.
Proof.
intros; apply sBijective_sInjective with (pow_Pr N M) psi; try easy.
apply eq_equivalence.
now apply pow_respect_r with phi M. (* Not yet proved! *)
Qed.
*)

Lemma pow_bBijective_bSurjective_l :
  forall (phi : nat -> nat -> nat) (psi : (nat -> nat) -> nat) N M,
    pow_bBijective phi psi N M -> pow_bSurjective_l phi N M.
Proof.
intros; now apply sBijective_sSurjective with eq psi.
Qed.

Lemma pow_bBijective_bInjective_r :
  forall (phi : nat -> nat -> nat) (psi : (nat -> nat) -> nat) N M,
    pow_bBijective phi psi N M -> pow_bInjective_r psi N M.
Proof.
intros phi psi N M H; apply sBijective_equiv in H.
apply sBijective_sInjective with (pow_Pl N M) phi; try easy.
apply pow_Eqr_equivalence.
apply pow_respect_l.
Qed.

Lemma pow_bBijective_bSurjective_r :
  forall (phi : nat -> nat -> nat) (psi : (nat -> nat) -> nat) N M,
    pow_bBijective phi psi N M -> pow_bSurjective_r psi N M.
Proof.
intros phi psi N M H; apply sBijective_equiv in H.
now apply sBijective_sSurjective with (pow_Eqr N) phi.
Qed.

End Finite_bijection_pow.