Require Import Arith ZArith Lia List.

From Undecidability.Shared.Libs.DLW.Utils
  Require Import utils_tac utils_list gcd prime php utils_nat.

From Undecidability.Shared.Libs.DLW.Vec
  Require Import pos vec.

From Undecidability.H10.ArithLibs
  Require Import Zp lagrange.

From Undecidability.H10.Dio
  Require Import dio_logic dio_single.

From Undecidability.H10
  Require Import H10 H10_undec.

From Undecidability.H10
  Require Import H10Z.

From Undecidability.H10.ArithLibs
  Require Import Zp lagrange.

Require Import Undecidability.Synthetic.Definitions.

Set Default Proof Using "Type".

Local Definition inj k n := 4 * n + k.

Lemma injection_spec k1 k2 n m : < 4 < 4 inj n = inj m = n = m.
Proof.
  intros. unfold inj in *. .
Qed.


Section pos_injs.

  Fixpoint {n} (p : pos n) : pos (n * 4).
  Proof.
    destruct p.
    - exact pos0.
    - exact (pos_right _ ( _ p)).
  Defined.


  Fixpoint {n} (p : pos n) : pos (n * 4).
  Proof.
    destruct p.
    - exact pos1.
    - exact (pos_right _ ( _ p)).
  Defined.


  Fixpoint {n} (p : pos n) : pos (n * 4).
  Proof.
    destruct p.
    - exact pos2.
    - exact (pos_right _ ( _ p)).
  Defined.


  Fixpoint {n} (p : pos n) : pos (n * 4).
  Proof.
    destruct p.
    - exact pos3.
    - exact (pos_right _ ( _ p)).
  Defined.


End pos_injs.

Arguments dp_cnst {V P}.
Arguments dp_var {V P}.
Arguments dp_par {V P}.
Arguments dp_comp {V P}.

Module dionat := dio_single.

Notation dp_sq a := (dp_comp do_mul a a).
Notation sq a := (a * a)%Z.

Fixpoint to_Z_poly E n (p : dionat.dio_polynomial (pos n) E) : dio_polynomial (pos (n * 4)) E :=
  match p with
  | dionat.dp_nat n dp_cnst (Z.of_nat n)
  | dionat.dp_var v dp_add (dp_sq (dp_var ( v))) (dp_add (dp_sq (dp_var ( v))) (dp_add (dp_sq (dp_var ( v))) (dp_sq (dp_var ( v)))))
  | dionat.dp_par p dp_par p
  | dionat.dp_comp o dp_comp o (to_Z_poly ) (to_Z_poly )
  end.

Lemma create_sol_correct E (n : ) (Φ : pos n ) (Φ' : pos (n * 4) Z) :
  ( i : pos n, Z.of_nat (Φ i) = sq (Φ' ( i)) + sq (Φ' ( i)) + sq (Φ' ( i)) + sq (Φ' ( i)))%Z
   p : dionat.dio_polynomial (pos n) E, Z.of_nat (dio_single.dp_eval Φ ( _ : E 0) p) = dp_eval Φ' ( _ : E 0%Z) (to_Z_poly p).
Proof.
  intros H p.
  induction p as [ k | v | | [] ]; cbn; auto.
  - rewrite H; ring.
  - rewrite Nat2Z.inj_add; congruence.
  - rewrite Nat2Z.inj_mul; congruence.
Qed.


Lemma create_sol_exists (n : ) (Φ : pos n ) : (Φ' : pos (n * 4) Z),
    ( i : pos n, Z.of_nat (Φ i) = sq (Φ' ( i)) + sq (Φ' ( i)) + sq (Φ' ( i)) + sq (Φ' ( i)))%Z.
Proof.
  induction n as [ | n IHn ].
  - exists ( _ 0%Z); intros p; invert pos p.
  - destruct (IHn ( j Φ (pos_nxt j))) as [Φ' H'].
    cbn [mult].
    destruct (lagrange_theorem_nat (Φ pos0)) as (a & b & c & d & Hl).
    exists ( j : pos (4 + n * 4)
       match pos_both _ _ j with
        | inl a
        | inl b
        | inl c
        | inl _ d
        | inr j Φ' j
      end).
    intros p; invert pos p; auto.
    rewrite Hl; ring.
Qed.


Lemma recover_sol_exists (n : ) (Φ' : pos (n * 4) Z) : (Φ : pos n ),
    ( i : pos n, Z.of_nat (Φ i) = sq (Φ' ( i)) + sq (Φ' ( i)) + sq (Φ' ( i)) + sq (Φ' ( i)))%Z.
Proof.
  induction n as [ | n IHn ].
  - exists ( _ 0); intros p; invert pos p.
  - destruct (IHn ( j Φ' (pos_right 4 j))) as [Φ H].
    unshelve eexists.
    + intros p; invert pos p.
      * exact (Z.to_nat (sq (Φ' ( pos0)) + sq (Φ' ( pos0)) + sq (Φ' ( pos0)) + sq (Φ' ( pos0)))).
      * exact (Φ p).
    + intros p; invert pos p.
      * rewrite Z2Nat.id; auto.
        pose proof (Z.square_nonneg (Φ' pos3)).
        pose proof (Z.square_nonneg (Φ' pos2)).
        pose proof (Z.square_nonneg (Φ' pos1)).
        pose proof (Z.square_nonneg (Φ' pos0)).
        nia.
      * apply H.
Qed.


Lemma create_sol (n : ) (Φ : pos n ) : Φ' : pos (n * 4) Z,
   p : dionat.dio_polynomial (pos n) (pos 0), Z.of_nat (dio_single.dp_eval Φ ( _ 0) p) = dp_eval Φ' ( _ 0%Z) (to_Z_poly p).
Proof.
  destruct (create_sol_exists Φ) as [Φ'].
  exists Φ'; intro; now eapply create_sol_correct.
Qed.


Lemma recover_sol (n : ) (Φ' : pos (n * 4) Z) : Φ : pos n ,
   p : dionat.dio_polynomial (pos n) (pos 0), Z.of_nat (dio_single.dp_eval Φ ( _ 0) p) = dp_eval Φ' ( _ 0%Z) (to_Z_poly p).
Proof.
  destruct (recover_sol_exists Φ') as [Φ].
  exists Φ; intro; now eapply create_sol_correct.
Qed.


Opaque Zmult.

Lemma H10_H10Z : H10Z.
Proof.
  unshelve eexists.
  - intros (n & p & q).
    exists (n * 4).
    exact (dp_add (to_Z_poly p) (dp_mul (dp_cnst (-1)%Z) (to_Z_poly q))).
  - intros (n & p & q).
    simpl; split; intros [ Φ H ]; simpl in *.
    + destruct (create_sol Φ) as [Φ' H'].
      exists Φ'.
      rewrite !H'; .
    + destruct (recover_sol Φ) as [Φ' H'].
      exists Φ'; simpl.
      apply Nat2Z.inj; rewrite !H'; .
Qed.


Check H10_H10Z.