From Undecidability.L Require Import Computability.MuRec.
From Undecidability.L.Datatypes Require Import LNat LOptions LProd Lists.
From Undecidability.Synthetic Require Import DecidabilityFacts EnumerabilityFacts ListEnumerabilityFacts ReducibilityFacts.

Require Import Datatypes.

Inductive is_computable {A} {t : TT A} (a : A) : Prop :=
  C : computable a is_computable a.

Notation enumerates f p := ( x, p x n : , f n = Some x).

Definition L_decidable {X} `{registered X} (P : X Prop) :=
   f : X bool, is_computable f x, P x f x = true.

Definition L_enumerable {X} `{registered X} (p : X Prop) :=
   f : option X, is_computable f (enumerates f p).

Definition L_recognisable {X} `{registered X} (p : X Prop) :=
   f : X bool, is_computable f x, p x n, f x n = true.

Definition L_recognisable' {X} `{registered X} (p : X Prop) :=
   s : term, x, p x converges (L.app s (enc x)).

Section L_enum_rec.

  Variable X : Type.
  Context `{registered X}.
  Variable (p : X Prop).

  Hypotheses (f : option X) (c_f : computable f) (H_f : enumerates f p).
  Hypotheses (d : X X bool) (c_d : computable d) (H_d : x y, reflect (x = y) (d x y)).

  Definition test := ( x n match f n with Some y d x y | None false end).

  Instance term_test : computable test.
  Proof using c_f c_d.
    extract.
  Qed.


  Import HOAS_Notations.

  Lemma proc_test (x : X) :
    proc [L_HOAS λ y, !!(ext test) !!(enc x) y].
  Proof.
    cbn. Lproc.
  Qed.


  Lemma L_enumerable_recognisable :
    L_recognisable' p.
  Proof using c_f c_d H_f H_d.
    exists [L_HOAS λ x, !! (λ y, !!(ext test) x y)].
    intros. split; intros.
    - eapply H_f in as [n ].
      edestruct (mu_complete (proc_test x)) with (n := n).
      + intros. exists (test x ). cbn. now Lsimpl.
      + cbn. Lsimpl. unfold test. rewrite . destruct (H_d x x); intuition.
      + exists (ext ). split; try Lproc.
        cbn. Lsimpl. now rewrite .
    - destruct as (v & ? & ?).
      edestruct (mu_sound (proc_test x)) with (v := v) as (n & ? & ? & _).
      + intros. exists (test x n). cbn. now Lsimpl.
      + Lproc.
      + rewrite . symmetry. cbn. now Lsimpl.
      + subst. eapply H_f. exists n.
        assert ([L_HOAS (λ y, !! (ext test) !! (enc x) y) !!(ext n)] == ext (test x n)).
        cbn. now Lsimpl. cbn in *. rewrite in *.
        eapply unique_normal_forms in ;[|Lproc..].
        eapply inj_enc in .
        unfold test in . destruct (f n); inv .
        destruct (H_d x ); firstorder congruence.
  Qed.


End L_enum_rec.

Definition opt_to_list n := match nat_enum n with Some x [x] | None [] end.

#[global]
Instance term_opt_to_list : computable opt_to_list.
Proof.
  extract.
Qed.


Definition L_nat := cumul (opt_to_list).

#[global]
Instance term_L_nat : computable L_nat.
Proof.
  unfold L_nat. unfold cumul.
  extract.
Qed.





Require Import Undecidability.Shared.embed_nat Nat.


Definition F' := (fix F (n : ) : := match n with
                                                           | 0 0
                                                           | S S + F
                                                           end).

#[global]
Instance term_F' : computable F'.
Proof.
  extract.
Qed.


Definition F'' := (fix F (n0 : ) : * := match with
                                                     | 0 (0, 0)
                                                     | S match F with
                                                               | (0, y) (S y, 0)
                                                               | (S , y) (, S y)
                                                               end
                                             end).

#[global]
Instance term_F'' : computable F''.
Proof.
  extract.
Qed.


#[global]
Instance term_embed_nat : computable embed.
Proof.
  change (computable ( '(x, y) y + F' (y + x))).
  extract.
Qed.


#[global]
Instance term_unembed_nat : computable unembed.
Proof.
  unfold unembed.
  change (computable F'').
  exact term_F''.
Qed.








Definition lenumerates {X} L (p : X Prop) :=
  cumulative L ( x : X, p x ( m : , x L m)).

Definition L_enum {X} `{registered X} (p : X Prop) :=
   L, is_computable L lenumerates L p.

Lemma projection X Y {HX : registered X} {HY : registered Y} (p : X * Y Prop) :
  L_enumerable p L_enumerable ( x y, p (x,y)).
Proof.
  intros (f & [cf] & ?).
  exists ( n match f n with Some (x, y) Some x | None None end).
  split.
  - econstructor. extract.
  - intros; split.
    + intros [y ?]. eapply H in as [n]. exists n. now rewrite .
    + intros [n ?]. destruct (f n) as [ [] | ] eqn:E; inv .
      exists y. eapply H. eauto.
Qed.


Lemma L_enumerable_ext X `{registered X} p q : L_enumerable p ( x : X, p x q x) L_enumerable q.
Proof.
  intros (f & cf & Hf) He. exists f; split; eauto.
  intros ?. rewrite He. eapply Hf.
Qed.


Definition {X} (T : list X) := ( n let (n, m) := unembed n in nth_error (T n) m).

#[global]
Instance term_F1 {X} {H : registered X} : @computable (( list X) option X) ((! ~> ! list X) ~> ! ~> ! option X) (@ X).
Proof.
  extract.
Qed.


Lemma L_enumerable_enum {X} `{registered X} (p : X Prop) :
  L_enum p L_enumerable p.
Proof.
  intros (f & [cf] & Hf).
  exists ( f). split.
  - econstructor. extract.
  - destruct Hf as [CX HX].
    intros x. unfold .
    now rewrite list_enumerator_to_enumerator.
Qed.


Lemma L_enumerable_halt {X} `{registered X} (p : X Prop) :
  L_decidable (X := X * X) ( '(x,y) x = y)
  L_enumerable p p converges.
Proof.
  intros (d & [c_d] & H_d) (f & [c_f] & H_f).
  edestruct L_enumerable_recognisable with (p := p) (d := x y d (x,y)) (f := f); eauto.
  - extract.
  - intros. specialize (H_d (x,y)). destruct (d (x,y)); intuition.
  - now exists ( x0 L.app x (enc )).
Qed.


Import L_Notations.

Lemma L_recognisable'_recognisable {X} `{registered X} (p : X Prop) :
  L_recognisable p L_recognisable' p.
Proof.
  intros (f & [c_f] & H_f).
  exists (lam ( (lam (ext f 1 0)))).
  intros.
  assert (((lam ( (lam ((ext f 1) 0)))) (enc x)) >* (lam (ext f (enc x) 0))) by now Lsimpl.
  rewrite . rewrite mu_spec.
  - rewrite H_f. split; intros [n]; exists n.
    Lsimpl. now rewrite .
    eapply enc_extinj.
    now assert ((lam (((ext f) (enc x)) 0)) (ext n) == enc (f x n)) as by now Lsimpl.
  - Lproc.
  - intros. exists (f x n). now Lsimpl.
Qed.


Lemma L_recognisable_halt {X} `{registered X} (p : X Prop) :
  L_recognisable p p converges.
Proof.
  intros. eapply L_recognisable'_recognisable in as (f & H_f). now exists ( x0 f (enc )).
Qed.