Require Import Arith List Lia.

From Undecidability.Shared.Libs.DLW Require Import utils_tac pos.
From Undecidability.Synthetic Require Import Undecidability.
From Undecidability.PCP Require Import PCP.
From Undecidability.H10 Require Import dio_logic dio_single H10 summary.

Set Implicit Arguments.

Local Infix "∈" := In (at level 70).

Local Reserved Notation "R ⊳ u ∕ l" (at level 70, format "R ⊳ u ∕ l").

Inductive BPCP_derive (R : list (list bool * list bool)) : list bool -> list bool -> Prop :=
  | in_BPCP_0 : forall p q, (p,q) R -> R pq
  | in_BPCP_1 : forall p q u l, (p,q) R -> R ul -> R (p++u)∕(q++l)
where "R ⊳ u ∕ l" := (BPCP_derive R u l).

Definition BPCP R := exists s, R ss.

Section dPCPb_BPCP.


  Variable R : list (list bool * list bool).

  Local Fact derive_BPCP_derive u l : derivable R u l <-> R ul.
  Proof. split; (induction 1; [ constructor 1 | constructor 2 ]; auto). Qed.

  Local Fact dPCPb_BPCP : dPCPb R <-> BPCP R.
  Proof. split; intros (s & ?); exists s; now apply derive_BPCP_derive. Qed.

End dPCPb_BPCP.


Notation U := nat.
Inductive poly : Set :=
  | poly_unk : U -> poly
  | poly_cst : nat -> poly
  | poly_add : poly -> poly -> poly
  | poly_mul : poly -> poly -> poly.

Local Notation "£ x" := (poly_unk x) (at level 1, format "£ x").
Local Notation "⌞ c ⌟" := (poly_cst c) (at level 1, format "⌞ c ⌟").
Local Infix "⊕" := poly_add (at level 50, left associativity).
Local Infix "⊗" := poly_mul (at level 48, left associativity).

Local Reserved Notation "⟦ p ⟧ φ" (at level 40, format "⟦ p ⟧ φ").

Fixpoint poly_eval (φ : U -> nat) p :=
  match p with
    | £x => φ x
    | c => c
    | pq => p φ + q φ
    | pq => p φ * q φ
  end
where "⟦ p ⟧ φ" := (poly_eval φ p).

Definition H10nat (e : poly*poly) := let (p,q) := e in exists φ, p φ = q φ.

Section H10_H10nat.


  Section dp2p.

    Variable (n : nat).

    Local Fixpoint dp2p (p : dio_polynomial (pos n) (pos 0)) : poly :=
      match p with
        | dp_nat c => c
        | dp_var x => £(pos2nat x)
        | dp_par a => pos_O_invert _ a
        | dp_comp do_add p q => dp2p p dp2p q
        | dp_comp do_mul p q => dp2p p dp2p q
      end.

    Variable (φ : pos n -> nat) (ψ : U -> nat).

    Local Definition valuations_match := forall x, φ x = ψ (pos2nat x).

    Hypothesis Hφψ : valuations_match.

    Local Fact dp2p_spec p ν : dp_eval φ ν p = dp2p p ψ.
    Proof.
      induction p as [ | | a | [] ]; simpl; auto.
      invert pos a.
    Qed.

  End dp2p.

  Local Fact H10_H10nat : H10 H10nat.
  Proof.
    apply reduces_dependent; exists.
    intros (n,(p,q)).
    exists (dp2p p,dp2p q); simpl.
    split.
    + intros (phi & Hphi).
      set (psi i := match le_lt_dec n i with left _ => 0 | right H => phi (nat2pos H) end).
      assert (Hpsi : valuations_match phi psi).
      { intro x; unfold psi.
        generalize (pos2nat_prop x); intros Hx.
        destruct (le_lt_dec n (pos2nat x)) as [ H | H ]; try lia.
        f_equal; symmetry; apply nat2pos_pos2nat. }
      exists psi.
      eq goal Hphi; f_equal; apply dp2p_spec; auto.
    + intros (psi & Hpsi).
      exists (fun x => psi (pos2nat x)).
      eq goal Hpsi; f_equal; symmetry; apply dp2p_spec; red; simpl; auto.
  Qed.

End H10_H10nat.

Local Definition many_one_reduces X Y (P : X -> Prop) (Q : Y -> Prop) :=
  exists f : X -> Y, forall x, P x <-> Q (f x).

Goal many_one_reduces = @reduces.
Proof. reflexivity. Qed.

Local Infix "⪯ₘ" := many_one_reduces (at level 70).

Theorem BPCP_many_one_reduces_to_H10nat : BPCP ⪯ₘ H10nat.
Proof.
  change (BPCP H10nat).
  eapply reduces_transitive with (Q := dPCPb).
  1: { exists id; intro; symmetry; apply dPCPb_BPCP. }
  do 6 (eapply reduces_transitive; [ apply Hilberts_Tenth_entire_chain | ]).
  apply H10_H10nat.
Qed.

Check BPCP_many_one_reduces_to_H10nat.
Print Assumptions BPCP_many_one_reduces_to_H10nat.