Require Import List Arith Lia Eqdep_dec Bool.

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

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

Set Implicit Arguments.


Section finite.

  Definition finite_t X := { lX | forall x : X, In x lX }.
  Definition finite X := exists lX, forall x : X, In x lX.

  Fact finite_t_finite X : finite_t X -> finite X.
  Proof. intros (l & ?); exists l; auto. Qed.

  Definition fin_t X (P : X -> Prop) := { l | forall x, P x <-> In x l }.
  Definition fin X (P : X -> Prop) := exists l, forall x, P x <-> In x l.

  Fact fin_t_fin X P : @fin_t X P -> fin P.
  Proof. intros (l & ?); exists l; auto. Qed.

  Fact finite_t_fin_t_eq X : (finite_t X -> fin_t (fun _ : X => True))
                           * (fin_t (fun _ : X => True) -> finite_t X).
  Proof.
    split; intros (l & ?); exists l; firstorder.
  Qed.

  Fact finite_fin_eq X : finite X <-> fin (fun _ : X => True).
  Proof.
    split; intros (l & ?); exists l; firstorder.
  Qed.

  Fact fin_t_map X Y (f : X -> Y) (P Q : _ -> Prop) :
             (forall y, Q y <-> exists x, f x = y /\ P x)
          -> @fin_t X P
          -> @fin_t Y Q.
  Proof.
    intros H (lP & HP).
    exists (map f lP).
    intros x; rewrite in_map_iff, H.
    firstorder.
  Qed.

  Fact finite_t_map X Y (f : X -> Y) :
           (forall y, exists x, f x = y)
        -> finite_t X
        -> finite_t Y.
  Proof.
    intros H (l & Hl).
    exists (map f l).
    intros y.
    destruct (H y) as (x & <-).
    apply in_map_iff; exists x; auto.
  Qed.

  Fact Forall_reif_t X l (P : X -> Prop) : Forall P l -> { m : list (sig P) | map (@proj1_sig _ _) m = l }.
  Proof.
    induction l as [ | x l IHl ].
    + exists nil; auto.
    + intros H; rewrite Forall_cons_inv in H.
      destruct H as (H1 & H2).
      destruct (IHl H2) as (m & Hm).
      exists (exist _ x H1 :: m); simpl; f_equal; auto.
  Qed.

  Fact fin_t_finite_t X (P : X -> Prop) (Pirr : forall x (H1 H2 : P x), H1 = H2) :
         fin_t P -> finite_t (sig P).
  Proof.
    intros (l & Hl).
    destruct (@Forall_reif_t _ l P) as (m & Hm).
    + rewrite Forall_forall; intro; apply Hl.
    + exists m.
      intros (x & Hx).
      generalize Hx; intros H.
      rewrite Hl, <- Hm, in_map_iff in Hx.
      destruct Hx as (y & <- & Hy).
      eq goal Hy; f_equal.
      destruct y; simpl; f_equal; apply Pirr.
  Qed.

  Fact fin_t_equiv X (P Q : X -> Prop) : (forall x, P x <-> Q x) -> fin_t P -> fin_t Q.
  Proof.
    intros H (l & Hl); exists l.
    intro; rewrite <- H, Hl; tauto.
  Qed.

  Fixpoint list_prod X Y (l : list X) (m : list Y) :=
    match l with
      | nil => nil
      | x::l => map (fun y => (x,y)) m ++ list_prod l m
    end.

  Fact list_prod_spec X Y l m c : In c (@list_prod X Y l m) <-> In (fst c) l /\ In (snd c) m.
  Proof.
    revert c; induction l as [ | x l IHl ]; intros c; simpl; try tauto.
    rewrite in_app_iff, IHl, in_map_iff; simpl.
    split.
    + intros [ (y & <- & ?) | (? & ?) ]; simpl; auto.
    + intros ([ -> | ] & ? ); destruct c; simpl; firstorder.
  Qed.

  Fact fin_t_prod X Y (P Q : _ -> Prop) :
         @fin_t X P -> @fin_t Y Q -> fin_t (fun c => P (fst c) /\ Q (snd c)).
  Proof.
    intros (l & Hl) (m & Hm).
    exists (list_prod l m); intro; rewrite list_prod_spec, Hl, Hm; tauto.
  Qed.

  Fact finite_t_prod X Y : finite_t X -> finite_t Y -> finite_t (X*Y).
  Proof.
    intros (l & Hl) (m & Hm); exists (list_prod l m).
    intros []; apply list_prod_spec; auto.
  Qed.

  Fact finite_prod X Y : finite X -> finite Y -> finite (X*Y).
  Proof.
    intros (l & Hl) (m & Hm); exists (list_prod l m).
    intros []; apply list_prod_spec; auto.
  Qed.

  Fact fin_t_sum X Y (P Q : _ -> Prop) :
         @fin_t X P -> @fin_t Y Q -> fin_t (fun z : X + Y => match z with inl x => P x | inr y => Q y end).
  Proof.
    intros (l & Hl) (m & Hm).
    exists (map inl l ++ map inr m).
    intros z; rewrite in_app_iff, in_map_iff, in_map_iff.
    destruct z as [ x | y ]; [ rewrite Hl | rewrite Hm ].
    + split.
      * left; exists x; auto.
      * intros [ (z & E & ?) | (z & C & _) ]; try discriminate; inversion E; subst; auto.
    + split.
      * right; exists y; auto.
      * intros [ (z & C & _) | (z & E & ?) ]; try discriminate; inversion E; subst; auto.
  Qed.

  Fact finite_t_sum X Y : finite_t X -> finite_t Y -> finite_t (X+Y)%type.
  Proof.
    intros H1 H2.
    apply finite_t_fin_t_eq in H1.
    apply finite_t_fin_t_eq in H2.
    apply finite_t_fin_t_eq.
    generalize (fin_t_sum H1 H2).
    apply fin_t_equiv.
    intros []; tauto.
  Qed.

  Fact finite_sum X Y : finite X -> finite Y -> finite (X+Y)%type.
  Proof.
    intros (l & Hl) (r & Hr).
    exists (map inl l++map inr r).
    intros []; apply in_app_iff; [ left | right ];
      apply in_map; auto.
  Qed.

  Fact finite_t_unit : finite_t unit.
  Proof. exists (tt::nil); intros []; simpl; auto. Qed.

  Fact finite_unit : finite unit.
  Proof. apply finite_t_finite, finite_t_unit. Qed.

  Fact finite_t_bool : finite_t bool.
  Proof. exists (true::false::nil); intros []; simpl; auto. Qed.

  Theorem fin_t_length X n : finite_t X -> fin_t (fun l => @length X l < n).
  Proof.
    intros HX.
    apply finite_t_fin_t_eq in HX.
    generalize finite_t_unit; intros H1.
    apply finite_t_fin_t_eq in H1.
    induction n as [ | n IHn ].
    + exists nil; intros; split; try lia; intros [].
    + generalize (fin_t_sum H1 (fin_t_prod HX IHn)).
      apply fin_t_map with (f := fun c => match c with
        | inl _ => nil
        | inr (x,l) => x::l
      end).
      intros [ | x l ]; simpl.
      * split; try lia; exists (inl tt); auto.
      * split.
        - intros Hl; exists (inr (x,l)); simpl; msplit 2; auto; lia.
        - intros ([ [] | (y,m) ] & E & H); try discriminate.
          simpl in *; inversion E; subst; lia.
  Qed.

  Theorem finite_t_list X n : finite_t X -> finite_t { l : list X | length l < n }.
  Proof.
    intros H; apply (fin_t_length n) in H; revert H; intros (l & Hl).
    assert (forall x, In x l -> length x < n) as f by (intro; apply Hl).
    set (g x Hx := exist (fun x => length x < n) x (f x Hx)).
    exists (list_in_map l g).
    intros (x & Hx).
    assert (G : In x l) by (revert Hx; apply Hl).
    assert (E : Hx = f _ G) by apply lt_pirr.
    subst Hx; apply In_list_in_map with (f := g).
  Qed.

  Theorem finite_t_option X : finite_t X -> finite_t (option X).
  Proof.
    intros (lX & HX).
    exists (None :: map Some lX).
    intros [x|]; simpl; auto.
    right; apply in_map_iff; exists x; auto.
  Qed.

  Fact finite_t_pos n : finite_t (pos n).
  Proof. exists (pos_list n); apply pos_list_prop. Qed.

  Theorem fin_t_vec X P n : @fin_t X P -> fin_t (fun v : vec _ n => forall p, P (vec_pos v p)).
  Proof.
    intros HP.
    induction n as [ | n IHn ].
    + exists (vec_nil :: nil).
      intros v; vec nil v; simpl; split; try tauto.
      intros _ p; invert pos p.
    + generalize (fin_t_prod HP IHn).
      apply fin_t_map with (f := fun c => vec_cons (fst c) (snd c)).
      intros v; vec split v with x; split.
      * intros H; exists (x,v); simpl; msplit 2; auto.
        - apply (H pos0).
        - intro; apply (H (pos_nxt _)).
      * intros ((x',v') & H1 & H2 & H3).
        simpl in H1, H2, H3.
        apply vec_cons_inv in H1.
        destruct H1 as (-> & ->).
        intros p; invert pos p; auto.
  Qed.

  Theorem finite_t_vec X n : finite_t X -> finite_t (vec X n).
  Proof.
    intros HX.
    apply finite_t_fin_t_eq.
    apply finite_t_fin_t_eq in HX.
    apply fin_t_vec with (n := n) in HX.
    revert HX; apply fin_t_equiv; tauto.
  Qed.

  Section filter.

    Variable (X : Type) (P : X -> Prop) (Pdec : forall x, { P x } + { ~ P x }).

    Theorem fin_t_dec Q : @fin_t X Q -> fin_t (fun x => P x /\ Q x).
    Proof.
      intros (l & Hl).
      exists (filter (fun x => if Pdec x then true else false) l).
      intros x; rewrite filter_In, <- Hl.
      destruct (Pdec x); try tauto.
      split; try tauto.
      intros []; discriminate.
    Qed.

  End filter.

  Fact finite_t_fin_t_dec (X : Type) (P : X -> Prop) (Pdec : forall x, { P x } + { ~ P x }) :
         finite_t X -> fin_t P.
  Proof.
    intros H.
    apply finite_t_fin_t_eq in H.
    apply fin_t_dec with (1 := Pdec) in H.
    revert H; apply fin_t_equiv; tauto.
  Qed.

End finite.

Lemma finite_t_sig_bool X (P : X -> bool) : finite_t X -> finite_t { x | P x = true }.
Proof.
  intros H.
  apply fin_t_finite_t.
  + intros; apply UIP_dec, bool_dec.
  + apply finite_t_fin_t_dec; auto.
    intro; apply bool_dec.
Qed.