Require Import Lia Arith.

Require Import Undecidability.PCP.PCP.
Require Import Undecidability.PCP.Util.PCP_facts.
Require Import Undecidability.Synthetic.DecidabilityFacts.
From Undecidability Require Import FOL.Utils.FullSyntax.
From Undecidability Require FOL.Semantics.FiniteTarski.Fragment.
From Undecidability Require Import FOL.FSAT.
Require Import Undecidability.Shared.ListAutomation.
Import ListAutomationNotations ListAutomationHints.

Local Unset Strict Implicit.


Local Notation "| s |" := (length s) (at level 100).

Definition bstring n :=
  { s : string bool | | s | <= n}.

Lemma string_nil (s : string bool) :
  |s| <= 0 <-> s = nil.
Proof.
  destruct s; cbn.
  - split; trivial; lia.
  - split; try congruence. intros H. lia.
Qed.

Definition bnil n : bstring n := exist _ nil (Nat.le_0_l n).

Definition bcons n (b : bool) (H : bstring n) : bstring (S n) :=
  match H with
    exist _ bs Hs => exist _ (b::bs) (le_n_S (|bs|) n Hs) end.

Lemma bstring_eq n (s t : bstring n) :
  proj1_sig s = proj1_sig t <-> s = t.
Proof.
  split; try now intros ->.
  destruct s as [s H1], t as [t H2]; cbn.
  intros ->. now rewrite (le_unique _ _ H1 H2).
Qed.

Lemma bstring_eq' n (s t : bstring n) :
  proj1_sig s = proj1_sig t -> s = t.
Proof.
  apply bstring_eq.
Qed.

Definition bstring_step n (L : list (bstring n)) :=
  [bnil (S n)] ++ map (bcons true) L ++ map (bcons false) L.

Definition bcast n s (H : |s| <= n) : bstring n :=
  exist _ s H.

Lemma listable_bstring n :
  listable (bstring n).
Proof.
  induction n.
  - exists [bnil 0]. intros [s H].
    assert (s = nil) as -> by now apply string_nil.
    left. now apply bstring_eq.
  - destruct IHn as [L HL]. exists (bstring_step L).
    intros [ [|[] s] H]; apply in_app_iff; cbn in H.
    + left. left. now apply bstring_eq.
    + right. apply in_app_iff. left. apply in_map_iff.
      assert (H' : |s| <= n) by lia. exists (bcast H').
      split; trivial. now apply bstring_eq.
    + right. apply in_app_iff. right. apply in_map_iff.
      assert (H' : |s| <= n) by lia. exists (bcast H').
      split; trivial. now apply bstring_eq.
Qed.

Notation i_f b x := (@i_func _ _ _ _ (f b) (Vector.cons _ x _ (Vector.nil _))).
Definition i_f' {domain:Type} {i:interp domain} (b:bool) (x:domain) := i_f b x.

Notation i_e := (@i_func _ _ _ _ e (Vector.nil _)).

Notation i_dum := (@i_func _ _ _ _ dum (Vector.nil _)).

Notation i_P x y := (@i_atom _ _ _ _ P (Vector.cons _ x _ (Vector.cons _ y _ (Vector.nil _)))).

Notation i_less x y := (@i_atom _ _ _ _ less (Vector.cons _ x _ (Vector.cons _ y _ (Vector.nil _)))).

Notation i_equiv x y := (@i_atom _ _ _ _ equiv (Vector.cons _ x _ (Vector.cons _ y _ (Vector.nil _)))).

Fixpoint iprep domain {I : interp domain} (x : list bool) (y : domain) :=
  match x with
  | nil => y
  | b::x => i_f b (iprep x y)
  end.

Definition ienc domain {I : interp domain} (x : list bool) := iprep x i_e.

Local Definition BSRS := list (card bool).
Local Notation "x / y" := (x, y).


Section FIB.

  Variable R : BSRS.

  Definition obstring n :=
    option (bstring n).

  Lemma listable_obstring n :
    listable (obstring n).
  Proof.
    destruct (listable_bstring n) as [L HL]. exists (None :: map Some L).
    intros [x|].
    - right. apply in_map, HL.
    - cbn. eauto.
  Qed.

  Notation obcast H := (Some (bcast H)).

  Definition ccons n b (s : obstring n) : obstring n :=
  match s with
  | Some (exist _ s _) => match (le_dec (|b::s|) n) with
             | left H => obcast H
             | right _ => None
             end
  | None => None
  end.

  Definition cdrv n (s t : obstring n) :=
    match s, t with
      | Some (exist _ s _), Some (exist _ t _) => derivable R s t
      | _, _ => False
    end.

  Definition sub n (x y : obstring n) :=
    match x, y with
    | Some (exist _ s _), Some (exist _ t _) => s <> t /\ exists s', t = s'++s
    | _, _ => False
    end.

  Global Instance FIB n : interp (obstring n).
  Proof using R.
    split.
    - intros k; cbn. destruct k as [b | |].
      + intros v. exact (ccons b (Vector.hd v)).
      + intros _. exact (Some (bnil n)).
      + intros _. exact None.
    - intros k; cbn. destruct k.
      + intros v. exact (cdrv (Vector.hd v) (Vector.hd (Vector.tl v))).
      + intros v. exact (sub (Vector.hd v) (Vector.hd (Vector.tl v))).
      + intros v. exact (eq (Vector.hd v) (Vector.hd (Vector.tl v))).
  Defined.


  Lemma app_bound n (s t : string bool) :
    |t| <= n -> |s++t| <= n + |s|.
  Proof.
    intros H. rewrite app_length. lia.
  Qed.

  Lemma obstring_iprep n x u (HX : |x++u| <= n) (HU : |u| <= n) :
    iprep x (obcast HU) = obcast HX.
  Proof.
    induction x; cbn.
    - f_equal. now apply bstring_eq.
    - assert (H : |x++u| <= n).
      { rewrite app_length in *. cbn in HX. lia. }
      rewrite (IHx H). unfold ccons, bcast at 1. destruct le_dec.
      + f_equal. now apply bstring_eq.
      + exfalso. cbn in *. rewrite app_length in *. lia.
  Qed.

  Lemma obstring_ienc n s (H : |s| <= n) :
    ienc s = obcast H.
  Proof.
    unfold ienc. cbn.
    setoid_rewrite obstring_iprep.
    f_equal. apply bstring_eq, app_nil_r.
    Unshelve. rewrite app_length. cbn. lia.
  Qed.

  Lemma obstring_ienc' n s (H : ~ |s| <= n) :
    @ienc _ (FIB n) s = None.
  Proof.
    induction s; cbn in *; try lia.
    change (@ccons n a (ienc s) = None).
    destruct (le_dec (|s|) n) as [H'|H'].
    - setoid_rewrite (obstring_ienc H'). cbn.
      destruct le_dec; tauto.
    - now rewrite IHs.
  Qed.

  Lemma crdv_iff n (x y : obstring n) :
    i_P x y <-> exists s t, derivable R s t /\ x = ienc s /\ y = ienc t /\ |s| <= n /\ |t| <= n.
  Proof.
    destruct x as [ [x HX]|], y as [ [y HY]|]; split; cbn; auto.
    { intros H. exists x, y. repeat setoid_rewrite obstring_ienc. now repeat split. }
    all: intros (s&t&H1&H2&H3&H4&H5).
    all: unshelve setoid_rewrite obstring_ienc in H2; [auto |];
         unshelve setoid_rewrite obstring_ienc in H3; [auto |].
    all: try discriminate.
    revert H2 H3. now intros [= ->] [= ->].
  Qed.

  Definition obembed n (s : obstring n) : obstring (S n) :=
    match s with
    | Some (exist _ s H) => Some (exist _ s (le_S _ _ H))
    | None => None
    end.

  Lemma cdrv_mon n (s t : obstring n) :
    cdrv s t -> @cdrv (S n) (obembed s) (obembed t).
  Proof.
    now destruct s as [ [s HS]|], t as [ [t HT]|].
  Qed.

  Lemma cdrv_mon' n s t :
    @cdrv n (ienc s) (ienc t) -> @cdrv (S n) (ienc s) (ienc t).
  Proof.
    destruct (le_dec (|s|) n) as [H|H], (le_dec (|t|) n) as [H'|H'].
    - repeat unshelve setoid_rewrite obstring_ienc; trivial; lia.
    - setoid_rewrite (obstring_ienc H). setoid_rewrite (obstring_ienc' H'). cbn. tauto.
    - rewrite (obstring_ienc' H). cbn. tauto.
    - rewrite (obstring_ienc' H). cbn. tauto.
  Qed.

  Lemma drv_cdrv s t :
    derivable R s t <-> @cdrv (max (|s|) (|t|)) (ienc s) (ienc t).
  Proof.
    repeat unshelve setoid_rewrite obstring_ienc; try reflexivity; lia.
  Qed.

  Lemma drv_cdrv' s :
    derivable R s s <-> @cdrv (|s|) (ienc s) (ienc s).
  Proof.
   repeat unshelve setoid_rewrite obstring_ienc; try reflexivity; lia.
  Qed.

  Lemma BPCP_P :
    dPCPb R <-> exists n x, @i_atom _ _ _ (FIB n) P ((Vector.cons _ x _ (Vector.cons _ x _ (Vector.nil _)))).
  Proof.
    split.
    - intros [s H]. exists (|s|), (ienc s). cbn. now apply drv_cdrv'.
    - intros [n[ [ [s H]|] H'] ].
      + cbn in H'. now exists s.
      + destruct H'.
  Qed.


  Section Ax.

    Variable n : nat.
    Implicit Type x y : obstring n.

    Lemma app_eq_nil' (s t : string bool) :
      s = t++s -> t = nil.
    Proof.
      destruct t; trivial. intros H. exfalso.
      assert (H' : |s| = |(b :: t) ++ s|) by now rewrite H at 1.
      cbn in H'. rewrite app_length in H'. lia.
    Qed.

    Lemma app_neq b (s t : string bool) :
      s <> (b :: t) ++ s.
    Proof.
      intros H. apply app_eq_nil' in H. discriminate.
    Qed.

    Lemma FIB_HP x y :
      i_P x y -> x <> None /\ y <> None.
    Proof.
      destruct x as [ [s HS] |], y as [ [t HT]|]; auto.
      intros _. split; discriminate.
    Qed.

    Lemma FIB_HS1 x :
      ~ sub x x.
    Proof.
      destruct x as [ [s HS]|]; cbn; firstorder.
    Qed.

    Lemma FIB_HS2 x y z :
      sub x y -> sub y z -> sub x z.
    Proof.
      destruct x as [ [s HS]|], y as [ [t HT]|], z as [ [u HU]|]; cbn; auto.
      intros [H1[s' HS'] ] [H2[t' HT'] ]. subst. split.
      - rewrite app_assoc. intros H % app_eq_nil'.
        apply app_eq_nil in H as [-> ->]. now apply H1.
      - exists (t'++s'). apply app_assoc.
    Qed.

    Lemma FIB_HF1 b x :
      i_f b x <> i_e.
    Proof.
      destruct x as [ [s H]|]; cbn; try congruence.
      destruct le_dec; try congruence. injection. congruence.
    Qed.

    Lemma FIB_HF2 b1 b2 x y :
      i_f b1 x <> None -> i_f b1 x = i_f b2 y -> x = y /\ b1 = b2.
    Proof.
      destruct x as [ [s HS] |], y as [ [t HT]|]; cbn.
      all: repeat destruct le_dec; cbn. all: try congruence.
      intros _ [= -> ->]. split; trivial. f_equal. now apply bstring_eq.
    Qed.

    Lemma None_dec X (x : option X) :
      {x = None} + {x <> None}.
    Proof.
      destruct x; auto. right. discriminate.
    Qed.

    Lemma FIB_HF2' x y :
      i_f true x = i_f false y -> i_f true x = None /\ i_f false y = None.
    Proof.
      intros H. destruct (None_dec (i_f' true x)), (None_dec (i_f' false y)); try tauto; exfalso. all: unfold i_f'.
      - symmetry in H. specialize (FIB_HF2 n0 H). intros [H1 H2]; congruence.
      - specialize (FIB_HF2 n0 H). intros [H1 H2]; congruence.
      - specialize (FIB_HF2 n0 H). intros [_ H']. discriminate.
    Qed.

    Lemma FIB_HF3 b x :
      i_f b x <> None -> x <> None.
    Proof.
      destruct x as [ [s HS] |]; cbn; congruence.
    Qed.

    Lemma FIB_HI x y :
      i_P x y -> (exists s t, s/t el R /\ x = ienc s /\ y = ienc t)
                \/ (exists s t u v, s/t el R /\ x = iprep s u /\ y = iprep t v /\ i_P u v /\
                              ((sub u x /\ v = y) \/ (sub v y /\ u = x) \/ (sub u x /\ sub v y))).
    Proof.
      destruct x as [ [x HX]|], y as [ [y HY]|]; cbn; auto. induction 1.
      - left. exists x, y. repeat setoid_rewrite obstring_ienc. repeat split; trivial.
      - assert (HU : |u| <= n). { rewrite app_length in HX. lia. }
        assert (HV : |v| <= n). { rewrite app_length in HY. lia. }
        destruct x as [|b x], y as [|c y].
        + cbn. apply IHderivable.
        + right. exists [], (c::y), (obcast HU), (obcast HV).
          repeat setoid_rewrite obstring_iprep. repeat split; trivial.
          right. left. repeat split; eauto using app_neq. f_equal. now apply bstring_eq.
        + right. exists (b::x), [], (obcast HU), (obcast HV).
          repeat setoid_rewrite obstring_iprep. repeat split; trivial.
          left. repeat split; eauto using app_neq. f_equal. now apply bstring_eq.
        + right. exists (b::x), (c::y), (obcast HU), (obcast HV).
          repeat setoid_rewrite obstring_iprep. repeat split; trivial.
          right. right. repeat split; eauto using app_neq.
    Qed.

  End Ax.

End FIB.


Section Conv.

  Variable R : BSRS.

  Variable D : Type.
  Hypothesis HD : listable D.

  Variable I : interp D.

  Notation "x == y" := (i_equiv x y) (at level 50).
  Notation "x =/= y" := (~ (i_equiv x y)) (at level 50).

  Hypothesis HP : forall x y, i_P x y -> x =/= i_dum /\ y =/= i_dum.

  Hypothesis HS1 : forall x, ~ i_less x x.
  Hypothesis HS2 : forall x y z, i_less x y -> i_less y z -> i_less x z.

  Hypothesis HF1 : forall b x, i_f b x =/= i_e.
  Hypothesis HF2 : forall b1 b2 x y, i_f b1 x =/= i_dum -> i_f b1 x == i_f b2 y -> x == y /\ b1 = b2.
  Hypothesis HF3 : forall b x, i_f b x =/= i_dum -> x =/= i_dum.

  Hypothesis HE1 : forall x, x == x.
  Hypothesis HE2 : forall x y, x == y -> y == x.
  Hypothesis HE3 : forall x y z, x == y -> y == z -> x == z.
  Hypothesis HER1 : forall x y b, x == y -> i_f b x == i_f b y.
  Hypothesis HER3 : forall x1 x2 y1 y2, x1 == x2 -> y1 == y2 -> i_less x1 y1 -> i_less x2 y2.

  Hypothesis HI :
    forall x y, i_P x y -> (exists s t, s/t el R /\ x == ienc s /\ y == ienc t)
                     \/ (exists s t u v, s/t el R /\ x == iprep s u /\ y == iprep t v /\ i_P u v /\
                                   ((i_less u x /\ v == y) \/ (i_less v y /\ u == x) \/ (i_less u x /\ i_less v y))).


  Lemma ienc_inj s t :
    ienc s =/= i_dum -> ienc s == ienc t -> s = t.
  Proof using HF3 HF2 HF1 HE2.
    revert t. induction s; intros [|]; cbn; trivial.
    - intros _ H. apply HE2 in H. now apply HF1 in H.
    - intros _ H. now apply HF1 in H.
    - intros H [H' ->] % HF2; trivial. f_equal.
      apply IHs; trivial. apply HF3 in H. trivial.
  Qed.

  Definition sub' L x y :=
    i_less x y /\ x el L.

  Lemma sub_acc_pred L x y :
    i_less y x -> Acc (sub' L) x -> Acc (sub' L) y.
  Proof using HS2.
    intros H H'. constructor. intros z [H1 H2].
    apply H'. split; trivial. now apply (HS2 H1).
  Qed.

  Lemma sub_acc_cons L x y :
    Acc (sub' L) x -> ~ i_less y x -> Acc (sub' (y::L)) x.
  Proof using HS2 HD.
    induction 1 as [x HX IH]. intros H.
    constructor. intros z [H1[->|H2] ].
    - contradiction.
    - apply IH. 1: now split.
      intros HH. now eapply H, HS2 with z.
  Qed.

  Lemma sub_acc_cons' L x y :
    i_less y x -> Acc (sub' L) x -> Acc (sub' (y::L)) y.
  Proof using HS2 HS1 HD.
    intros H1 H2. apply sub_acc_cons; trivial.
    now apply (sub_acc_pred H1).
  Qed.

  Lemma sub_acc_step L a x :
    Acc (sub' L) x -> Acc (sub' (a::L)) x.
  Proof using HS2 HS1 HD.
    induction 1 as [x HX IH].
    constructor. intros y [H [->|H'] ].
    - now apply (sub_acc_cons' H).
    - apply IH. now split.
  Qed.

  Lemma sub_acc' L x :
    Acc (sub' L) x.
  Proof using HS2 HS1 HD.
    induction L.
    - constructor. intros y [_ [] ].
    - apply sub_acc_step, IHL.
  Qed.

  Lemma sub_acc x :
    Acc (fun a b => i_less a b) x.
  Proof using HS2 HS1 HD.
    destruct HD as [L HL].
    induction (sub_acc' L x) as [x _ IH].
    constructor. intros y H. now apply IH.
  Qed.

  Inductive psub : D -> D -> D -> D -> Prop :=
  | psub1 x u y y' : i_less u x -> y == y' -> psub u y x y'
  | psub2 y u x x' : i_less u y -> x == x' -> psub x u x' y
  | psub3 x y u v : i_less u x -> i_less v y -> psub u v x y.

  Lemma psub_equiv a b c d a' b' c' d' :
    a == a' -> b == b' -> c == c' -> d == d' ->
    psub a b c d -> psub a' b' c' d'.
  Proof using HE2 HE3 HER3.
    intros Ha Hb Hc Hd Habcd. induction Habcd as [c a b d Hac Hbd|d b a c Hdb Hac|c d a b Hac Hdb] in Ha,Hb,Hc,Hd,a',b',c',d'|-*.
    + eapply psub1.
      - now eapply HER3 with a c.
      - eapply HE3 with b. 1: now eapply HE2.
        now eapply HE3 with d.
    + eapply psub2.
      - now eapply HER3 with b d.
      - eapply HE3 with a. 1: now eapply HE2.
        now eapply HE3 with c.
    + eapply psub3.
      - now eapply HER3 with a c.
      - now eapply HER3 with b d.
  Qed.

  Definition psub' : D * D -> D * D -> Prop := fun P1 P2 => let (a,b) := P1 in let (c,d) := P2 in psub a b c d.

  Definition pequiv : D * D -> D * D -> Prop := fun P1 P2 => let (a,b) := P1 in let (c,d) := P2 in a == c /\ b == d.

  Lemma pequiv_equiv : RelationClasses.Equivalence pequiv.
  Proof using HE1 HE2 HE3.
    split.
    - intros [x y]. split; now apply HE1.
    - intros [x1 y1] [x2 y2] [H1 H2]; split; now apply HE2.
    - intros [x1 y1] [x2 y2] [x3 y3] [Hx1 Hx2] [Hy1 Hy2]; split; [eapply HE3 with x2 | eapply HE3 with y2]; easy.
  Qed.

  Lemma psub_acc p :
    Acc psub' p.
  Proof using HS2 HS1 HD HI HE1 HE2 HE3 HER3.
    destruct p as [x y]. revert y.
    induction (sub_acc x) as [x HX IHX]. intros y.
    induction (sub_acc y) as [y HY IHY]. constructor.
    intros [u v]. cbn. inversion 1; subst.
    - now apply IHX.
    - eapply (Morphisms_Prop.Acc_pt_morphism pequiv) with (x, v).
      + apply pequiv_equiv.
      + intros [a b] [a' b'] [Ha Hb] [c d] [c' d'] [Hc Hd]. split; apply psub_equiv; (try easy); apply HE2; easy.
      + now split.
      + apply IHY. easy.
    - now apply IHX.
  Qed.

  Lemma ienc_iprep s t :
    iprep s (ienc t) == ienc (s ++ t).
  Proof using HER1 HE3 HE1.
    induction s; cbn; trivial. apply HER1. apply (HE3 IHs). apply HE1.
  Qed.

  Lemma iprep_respectful s t t' : t == t' -> iprep s t == iprep s t'.
  Proof using HER1.
  intros Ht. induction s.
  - cbn. easy.
  - cbn. apply HER1. easy.
  Qed.

  Lemma P_drv' p :
    i_P (fst p) (snd p) -> exists s t, derivable R s t /\ fst p == ienc s /\ snd p == ienc t.
  Proof using HS2 HS1 HI HD HE1 HE2 HE3 HER1 HER3.
    intros H. induction (psub_acc p) as [ [x' y'] _ IH]; cbn in *.
    destruct (HI H) as [(s&t&H1&H2&H3)|(s&t&u&v&H1&H2&H3&H4&H5)]; subst.
    - exists s, t. repeat split; trivial. now constructor.
    - destruct (IH (u,v)) as (s'&t'&H6&H7&H'); cbn in *; trivial; subst.
      + destruct H5 as [ [H5 H5r]|[ [H5 H5r]|[] ] ].
        * now apply psub1.
        * now apply psub2.
        * now apply psub3.
      + exists (s++s'), (t++t'). repeat split. 1: now right.
        * apply (HE3 H2). eapply HE3. 2: apply ienc_iprep. apply iprep_respectful. easy.
        * apply (HE3 H3). eapply HE3. 2: apply ienc_iprep. apply iprep_respectful. easy.
  Qed.

  Lemma P_drv x y :
    i_P x y -> exists s t, derivable R s t /\ x == ienc s /\ y == ienc t.
  Proof using HS2 HS1 HI HD HE1 HE2 HE3 HER1 HER3.
    apply P_drv' with (p:=(x,y)).
  Qed.

  Lemma P_BPCP x :
    i_P x x -> dPCPb R.
  Proof using HS2 HS1 HP HI HF3 HF2 HF1 HD HE1 HE2 HE3 HER1 HER3.
    intros H. destruct (P_drv H) as (s&t&H1&H2&H3); subst.
    assert (ienc s == ienc t) as H4.
    - apply HE3 with x. now apply HE2. easy.
    - apply ienc_inj in H4. exists t. congruence. destruct (@HP x x) as [Hx _]. 1:easy.
      intros Hcc. apply Hx. now apply HE3 with (ienc s).
  Qed.

End Conv.


Section Reduction.
Notation t_f b x := (func (f b) (Vector.cons _ x _ (Vector.nil _))).

Notation t_e :=
  (func e (Vector.nil _)).

Notation t_dum :=
  (func dum (Vector.nil _)).

Notation f_P x y := (atom P (Vector.cons _ x _ (Vector.cons _ y _ (Vector.nil _)))).

Notation f_less x y := (atom less (Vector.cons _ x _ (Vector.cons _ y _ (Vector.nil _)))).

Notation f_equiv x y := (atom equiv (Vector.cons _ x _ (Vector.cons _ y _ (Vector.nil _)))).

  Notation "x ≡ y" := (f_equiv x y) (at level 20).
  Notation "x ≢ y" := (¬ (x y)) (at level 20).
  Notation "x ≺ y" := (f_less x y) (at level 20).

  Fixpoint tprep (x : list bool) (y : term) :=
    match x with
    | nil => y
    | b::x => t_f b (tprep x y)
    end.

  Lemma tprep_eval D (I : interp D) rho x t :
    eval rho (tprep x t) = iprep x (eval rho t).
  Proof.
    induction x; cbn.
    - reflexivity.
    - rewrite IHx. reflexivity.
  Qed.

  Lemma tprep_bounded x t n : bounded_t n t -> bounded_t n (tprep x t).
  Proof.
    intros H; induction x; solve_bounds. 1: apply H.
    apply IHx.
  Qed.

  Definition tenc (x : list bool) := tprep x t_e.

  Definition ax_P := f_P $1 $0 ($1 t_dum) ($0 t_dum).
  Definition ax_S1 := ¬ ($0 $0).
  Definition ax_S2 := $2 $1 $1 $0 $2 $0.

  Definition ax_HF1_true := t_f true $0 t_e.
  Definition ax_HF1_false := t_f false $0 t_e.
  Definition ax_HF2_true := t_f true $1 t_dum t_f true $1 t_f true $0 $1 $0.
  Definition ax_HF2_false := t_f false $1 t_dum t_f false $1 t_f false $0 $1 $0.
  Definition ax_HF2 := t_f true $1 t_f false $0 (t_f true $1 t_dum t_f false $0 t_dum).

  Definition ax_HF3_true := t_f true $0 t_dum $0 t_dum.
  Definition ax_HF3_false := t_f false $0 t_dum $0 t_dum.

  Definition ax_HE1 := $0 $0.
  Definition ax_HE2 := $1 $0 $0 $1.
  Definition ax_HE3 := $2 $1 $1 $0 $2 $0.

  Definition ax_HER1_true := $1 $0 t_f true $1 t_f true $0.
  Definition ax_HER1_false := $1 $0 t_f false $1 t_f false $0.
  Definition ax_HER3 := $3 $2 $1 $0 f_less $3 $1 f_less $2 $0.

  Definition ax_HI' c :=
    ($1 tenc (fst c) $0 tenc (snd c))
     ( $3 tprep (fst c) $1 $2 tprep (snd c) $0 f_P $1 $0
            (($1 $3 $0 $2) ($0 $2 $1 $3) ($1 $3 $0 $2))).

  Fixpoint list_or (A : list form) : form :=
    match A with
    | nil =>
    | phi::A => phi list_or A
    end.

  Lemma list_or_spec A D (I : interp D) rho :
    rho list_or A <-> exists phi, phi el A /\ rho phi.
  Proof.
    induction A; cbn; split; auto.
    - firstorder.
    - intros [H|H].
      + exists a. tauto.
      + apply IHA in H as [phi[H1 H2]]. exists phi. tauto.
    - intros [phi [[->|H1] H]]; auto.
      right. apply IHA. now exists phi.
  Qed.

  Lemma list_or_closed n A : (forall a, In a A -> bounded n a) -> bounded n (list_or A).
  Proof.
    intros H. induction A; solve_bounds.
    - apply H. now left.
    - apply IHA. intros a' Ha. apply H. now right.
  Qed.

  Definition ax_HI (R : BSRS) := f_P $1 $0 list_or (map ax_HI' R).

  Definition finsat_formula (R : BSRS) :=
    ax_P ax_S1 ax_S2 ax_HF1_true ax_HF1_false ax_HF2_true ax_HF2_false
     ax_HF2 ax_HF3_true ax_HF3_false ax_HI R
     ax_HE1 ax_HE2 ax_HE3 ax_HER1_true ax_HER1_false ax_HER3
     f_P $0 $0.

  Lemma finsat_closed R : closed (finsat_formula R).
  Proof.
    unfold closed. solve_bounds.
    apply list_or_closed.
    intros a [x [<- Hx2]]%in_map_iff.
    solve_bounds. all: apply tprep_bounded; solve_bounds.
  Qed.

  Ltac rsplit n := let rec f n := match n with 0 => idtac | S ?nn => split; [f nn|idtac] end in f n.

  Lemma obstring_eqdec n : eq_dec (obstring n).
  Proof.
    apply option_eq_dec. unfold bstring.
    intros [a Ha] [b Hb]. destruct (list_eq_dec bool_eq_dec a b) as [Hn0|Hn0].
    - subst. left. apply EqdepFacts.eq_dep_eq_sig. assert (Ha = Hb) as -> by (apply le_unique). constructor.
    - right. intros H. eapply Hn0, EqdepFacts.eq_sig_fst. exact H.
  Defined.

  Definition cdrv_decider (R : BSRS) (n : nat) (s t : obstring n) : dec (cdrv R s t).
  Proof.
    destruct s as [[s Hs]|], t as [[t Ht]|].
    - cbn. apply pcp_hand_dec. apply bool_eq_dec.
    - cbn. now right.
    - cbn. now right.
    - cbn. now right.
  Defined.
  Definition sub_decider (n : nat) (s t : obstring n) : dec (sub s t).
  Proof.
    destruct s as [[s Hs]|], t as [[t Ht]|].
    - cbn. destruct (list_eq_dec bool_eq_dec s t) as [Hl|Hr].
      + right. intros [H _]. tauto.
      + destruct (is_a_tail_dec bool_eq_dec t s) as [Hl2|Hr2].
        * left. split; try easy. destruct Hl2 as [x Hx]. exists x. congruence.
        * right. intros [_ [x Hx]]. apply Hr2. exists x. congruence.
    - cbn. now right.
    - cbn. now right.
    - cbn. now right.
  Defined.

  Lemma signature_is_decidable n R : (forall p : syms_pred, decidable (fun v : Vector.t (obstring n) (ar_preds p) => @i_atom _ _ (obstring n) (@FIB R n) p v)).
  Proof.
    intros [| |]; cbn.
    - exists (fun X => if cdrv_decider R (Vector.hd X) (Vector.hd (Vector.tl X)) then true else false).
      intros X. destruct (cdrv_decider R (Vector.hd X) (Vector.hd (Vector.tl X))) as [Ht|Hf]; now split.
    - exists (fun X => if sub_decider (Vector.hd X) (Vector.hd (Vector.tl X)) then true else false).
      intros X. destruct (sub_decider (Vector.hd X) (Vector.hd (Vector.tl X))) as [Ht|Hf]; now split.
    - exists (fun X => if obstring_eqdec (Vector.hd X) (Vector.hd (Vector.tl X)) then true else false).
      intros X. destruct (obstring_eqdec (Vector.hd X) (Vector.hd (Vector.tl X))) as [Ht|Hf]; now split.
  Qed.

  Theorem finsat_reduction_1 R :
    dPCPb R -> FSATd (finsat_formula R).
  Proof.
    intros H.
    - apply BPCP_P in H as [n [s H]]. exists (obstring n), (@FIB R n), (fun _ => None).
      split; try apply listable_obstring. cbn.
      split. 2:split. 2: apply signature_is_decidable.
      1: {exists (fun '(a,b) => if obstring_eqdec a b then true else false).
          intros [a b]. destruct (obstring_eqdec a b) as [Ht|Hf]; now split. }
      rsplit 17.
      + apply (@FIB_HP R).
      + apply FIB_HS1.
      + apply FIB_HS2.
      + intros x. apply (@FIB_HF1 R _ true x).
      + intros x. apply (@FIB_HF1 R _ false x).
      + intros x y H1 H2. now destruct (FIB_HF2 (R:=R) H1 H2).
      + intros x y H1 H2. now destruct (FIB_HF2 (R:=R) H1 H2).
      + apply (@FIB_HF2' R).
      + intros x. apply (@FIB_HF3 R _ true x).
      + intros x. apply (@FIB_HF3 R _ false x).
      + intros u v [[a[b[H1 H2]]]|[a[b[a'[b'[H1 H2]]]]]] % (@FIB_HI R _ u v); apply list_or_spec.
      * exists (ax_HI' (a/b)). split; try now apply in_map.
        left. cbn. now rewrite !tprep_eval.
      * exists (ax_HI' (a/b)). split; try now apply in_map.
        right. exists a', b'. cbn. rewrite !tprep_eval. tauto.
      + congruence.
      + congruence.
      + congruence.
      + congruence.
      + congruence.
      + congruence.
      + try now exists s.
  Qed.

  Theorem finsat_reduction_2 R :
    FSAT (finsat_formula R) -> dPCPb R.
  Proof.
    intros H.
    - destruct H as (D & (I & rho & HF & HE & (((((((((((((((((H1 & H2) & H3) & H4) &
                     H5) & H6) & H7) & H8) & H9) & H10) & H11) & H12) & H13) & H14) & H15) & H16) & H17) & s & H18))).
      cbn in *.
      eapply P_BPCP with (x:=s); trivial; unfold not in *; cbn in *.
      + intros [|]. apply H4. apply H5.
      + intros [|] [|] d1 d2 Hi1 Hi2.
        1: split; try easy; now apply H6.
        3: split; try easy; now apply H7.
        all: exfalso. 2: apply H13 in Hi2. all: destruct (H8 _ _ Hi2) as [Hi3 Hi4]. all:easy.
      + intros [|]. apply H9. apply H10.
      + intros d1 d2 [|]. apply H15. apply H16.
      + cbn in H11. intros x y H. specialize (H11 x y H).
        apply list_or_spec in H11 as [c[[[u v][<- HR]] % in_map_iff [H'|[a[b H']]]]].
        * left. exists u, v. split; trivial. cbn in H'. rewrite !tprep_eval in H'. apply H'.
        * right. exists u, v, a, b. split; trivial. cbn in H'. rewrite !tprep_eval in H'. tauto.
  Qed.

  Theorem FSATd_reduction R :
    dPCPb R <-> FSATd (finsat_formula R).
  Proof.
    split; intros H. 1: now apply finsat_reduction_1.
    destruct H as (D & I & rho & Hlis & Hdis & HH).
    apply finsat_reduction_2. exists D, I, rho. now split.
  Qed.

  Theorem FSAT_reduction R :
    dPCPb R <-> FSAT (finsat_formula R).
  Proof.
    split; intros H. 2: now apply finsat_reduction_2.
    destruct (finsat_reduction_1 H) as (D & I & rho & Hlis & Hdis & HH).
    exists D, I, rho. now split.
  Qed.

  Theorem FSATdc_reduction R :
    dPCPb R <-> FSATdc (exist _ (finsat_formula R) (finsat_closed R)).
  Proof.
    split; intros H. 1: now apply finsat_reduction_1.
    destruct H as (D & I & rho & Hlis & Hdis & HH).
    apply finsat_reduction_2. exists D, I, rho. now split.
  Qed.

End Reduction.