From stdpp Require Import prelude.
From Coq Require Import Streams Classical Sorted.
From VLSM.Lib Require Import Preamble ListExtras StdppExtras SortedLists NeList.

Utility: Stream Definitions and Results


Lemma stream_eq_hd_tl {A} (s s' : Stream A) :
  hd s = hd s' -> tl s = tl s' -> s = s'.
Proof. by destruct s, s'; cbn; intros -> ->. Qed.

Lemma fHere [A : Type] (P : Stream A -> Prop) : forall s, ForAll P s -> P s.
Proof. by intros s []. Qed.

Lemma fFurther [A : Type] (P : Stream A -> Prop) : forall s, ForAll P s -> ForAll P (tl s).
Proof. by intros s []. Qed.

Lemma ForAll_subsumption [A : Type] (P Q : Stream A -> Prop)
  (HPQ : forall s, P s -> Q s)
  : forall s, ForAll P s -> ForAll Q s.
Proof.
  apply ForAll_coind.
  - by intros s Hp; apply fHere in Hp; apply HPQ.
  - by apply fFurther.
Qed.

Lemma Exists_map_conv :
  forall [A B : Type] (f : A -> B) (P : Stream B -> Prop) (s : Stream A),
    Exists P (Streams.map f s) -> Exists (fun s => P (Streams.map f s)) s.
Proof.
  intros A B f P s HEx.
  remember (map f s) as fs; revert s Heqfs.
  induction HEx; [by intros s ->; constructor |].
  intros [a' s'] ->.
  constructor 2; cbn.
  by apply IHHEx.
Qed.

Lemma ForAll_tauto :
  forall [A : Type] (P : Stream A -> Prop),
    (forall s : Stream A, P s) -> forall s : Stream A, ForAll P s.
Proof.
  cofix CH.
  by constructor; [| apply CH].
Qed.

Lemma forall_ForAll :
  forall [A B : Type] (P : A -> Stream B -> Prop) [s : Stream B],
    (forall (a : A), ForAll (P a) s) -> ForAll (fun s' => forall a, P a s') s.
Proof.
  cofix CH.
  intros A B P [b s] H.
  constructor.
  - by intro a; destruct (H a).
  - apply CH.
    by intro a; destruct (H a).
Qed.

Lemma ForAll_impl :
  forall [A : Type] (P Q : Stream A -> Prop) (s : Stream A),
    ForAll (fun s => P s -> Q s) s -> ForAll P s -> ForAll Q s.
Proof.
  cofix CH.
  intros A P Q [a s] []; inversion 1; subst.
  constructor; cbn; [by apply H |].
  by apply (CH A P Q).
Qed.

Lemma Exists_impl :
  forall [A : Type] (P Q : Stream A -> Prop) (s : Stream A),
    ForAll (fun s => P s -> Q s) s -> Exists P s -> Exists Q s.
Proof.
  intros A P Q s Hall HEx.
  induction HEx.
  - by apply Here, Hall.
  - by apply Further, IHHEx; inversion Hall.
Qed.

Lemma not_Exists :
  forall [A : Type] (P : Stream A -> Prop) (s : Stream A),
    ~ Exists P s -> ForAll (fun s => ~ P s) s.
Proof.
  cofix CH.
  intros A P [a s] H.
  constructor.
  - by contradict H; constructor.
  - by apply CH; contradict H; constructor.
Qed.

Lemma not_ForAll :
  forall [A : Type] (P : Stream A -> Prop) (s : Stream A),
    ~ ForAll P s -> Exists (fun s => ~ P s) s.
Proof.
  intros A P s H.
  apply Classical_Prop.NNPP.
  contradict H.
  apply not_Exists in H.
  revert H; apply ForAll_impl, ForAll_tauto.
  by intro; apply Classical_Prop.NNPP.
Qed.

Lemma not_Exists_ForAll :
  forall [A : Type] (s : Stream A),
    ~ (exists x : A, Exists (ForAll (λ s : Stream A, hd s = x)) s) ->
    forall x : A, ForAll (Exists (fun s => hd s <> x)) s.
Proof.
  intros A s Hnex x.
  assert (Hnex' : ~ Exists (ForAll (fun s : Stream A => hd s = x)) s) by firstorder.
  apply not_Exists in Hnex'.
  revert Hnex'; apply ForAll_impl, ForAll_tauto.
  clear; intros s Hnall.
  by apply not_ForAll in Hnall.
Qed.

Lemma use_Exists :
  forall [A : Type] (P Q : Stream A -> Prop) (s : Stream A),
    Exists P s -> ForAll Q s -> exists s', P s' /\ ForAll Q s'.
Proof.
  induction 1.
  - by exists x.
  - by inversion 1; subst; apply IHExists.
Qed.

Lemma Exists_Str_nth_tl [A : Type] (P : Stream A -> Prop)
  : forall s, Exists P s <-> exists n, P (Str_nth_tl n s).
Proof.
  intros s; split.
  - induction 1; [by exists 0 |].
    destruct IHExists as [n Hn].
    by exists (S n).
  - intros [n Hn]. revert s Hn.
    induction n; [by apply Here |].
    intros s Hs. specialize (IHn (tl s) Hs).
    by apply Further.
Qed.

Retrieve an existential quantifier that works on elements from Exists, which works on substreams.
Definition Exists1 [A : Type] (P : A -> Prop) := Exists (fun s => P (hd s)).

Lemma Exists1_exists [A : Type] (P : A -> Prop) s
  : Exists1 P s <-> exists n, P (Str_nth n s).
Proof.
  split.
  - induction 1.
    + by exists 0.
    + destruct IHExists as [n Hp].
      by exists (S n).
  - intros [n Hp]. revert s Hp. induction n; intros.
    + by constructor.
    + by apply Further, IHn.
Qed.

Retrieve a universal quantifier that works on elements from ForAll, which works on substreams.
Definition ForAll1 [A : Type] (P : A -> Prop) := ForAll (fun s => P (hd s)).

Lemma ForAll1_subsumption [A : Type] (P Q : A -> Prop)
  (HPQ : forall a, P a -> Q a)
  : forall s, ForAll1 P s -> ForAll1 Q s.
Proof.
  by apply ForAll_subsumption; intro s; apply HPQ.
Qed.

Lemma ForAll1_forall [A : Type] (P : A -> Prop) s
  : ForAll1 P s <-> forall n, P (Str_nth n s).
Proof.
  split; intros.
  - by apply ForAll_Str_nth_tl with (m := n) in H; apply H.
  - apply ForAll_coind with (fun s : Stream A => forall n : nat, P (Str_nth n s))
    ; intros.
    + by specialize (H0 0).
    + by specialize (H0 (S n)).
    + by apply H.
Qed.

Definition ForAll2 [A : Type] (R : A -> A -> Prop) := ForAll (fun s => R (hd s) (hd (tl s))).

Lemma ForAll2_subsumption [A : Type] (R1 R2 : A -> A -> Prop)
  (HR : forall a b, R1 a b -> R2 a b)
  : forall s, ForAll2 R1 s -> ForAll2 R2 s.
Proof.
  by apply ForAll_subsumption; intro s; apply HR.
Qed.

Lemma ForAll2_forall [A : Type] (R : A -> A -> Prop) s
  : ForAll2 R s <-> forall n, R (Str_nth n s) (Str_nth (S n) s).
Proof.
  split; intros.
  - apply ForAll_Str_nth_tl with (m := n), fHere in H.
    by rewrite tl_nth_tl in H.
  - apply ForAll_coind with (fun s : Stream A => forall n : nat, R (Str_nth n s) (Str_nth (S n) s))
    ; intros.
    + by apply (H0 0).
    + by apply (H0 (S n)).
    + by apply H.
Qed.

Lemma recons
  {A : Type}
  (s : Stream A)
  : Cons (hd s) (tl s) = s.
Proof.
  by case s.
Qed.

Appends a stream to a list, yielding a stream.
Definition stream_app
  {A : Type}
  (prefix : list A)
  (suffix : Stream A)
  : Stream A
  :=
  fold_right (@Cons A) suffix prefix.

Definition stream_app_cons {A}
  (a : A)
  (l : Stream A)
  : stream_app [a] l = Cons a l
  := eq_refl.

Lemma stream_app_assoc
  {A : Type}
  (l m : list A)
  (n : Stream A)
  : stream_app l (stream_app m n) = stream_app (l ++ m) n.
Proof.
  by induction l; cbn; f_equal.
Qed.

Lemma stream_app_f_equal
  {A : Type}
  (l1 l2 : list A)
  (s1 s2 : Stream A)
  (Hl : l1 = l2)
  (Hs : EqSt s1 s2)
  : EqSt (stream_app l1 s1) (stream_app l2 s2).
Proof.
  by subst; induction l2; [| constructor].
Qed.

Lemma stream_app_inj_l
  {A : Type}
  (l1 l2 : list A)
  (s : Stream A)
  (Heq : stream_app l1 s = stream_app l2 s)
  (Heq_len : length l1 = length l2)
  : l1 = l2.
Proof.
  generalize dependent l2.
  induction l1; intros; destruct l2; try done; try inversion Heq_len.
  inversion Heq.
  by rewrite (IHl1 l2 H2 H0).
Qed.

Fixpoint stream_prefix
  {A : Type}
  (l : Stream A)
  (n : nat)
  : list A
  := match n, l with
  | 0, _ => []
  | S n, Cons a l => a :: stream_prefix l n
  end.

Lemma stream_prefix_nth
  {A : Type}
  (s : Stream A)
  (n : nat)
  (i : nat)
  (Hi : i < n)
  : nth_error (stream_prefix s n) i = Some (Str_nth i s).
Proof.
  revert s n Hi.
  induction i; intros [a s] [| n] Hi; [by inversion Hi .. |].
  by apply IHi; lia.
Qed.

Lemma stream_prefix_lookup
  {A : Type}
  (s : Stream A)
  (n : nat)
  (i : nat)
  (Hi : i < n)
  : stream_prefix s n !! i = Some (Str_nth i s).
Proof.
  revert s n Hi.
  induction i; intros [a s] [| n] Hi; [by inversion Hi .. |].
  by apply IHi; lia.
Qed.

Lemma stream_prefix_S
  {A : Type}
  (s : Stream A)
  (n : nat)
  : stream_prefix s (S n) = stream_prefix s n ++ [Str_nth n s].
Proof.
  revert s.
  by induction n; intros; rewrite <- (recons s); cbn; f_equal; rewrite <- IHn.
Qed.

Lemma stream_prefix_EqSt
  {A : Type}
  (s1 s2 : Stream A)
  (Heq : EqSt s1 s2)
  : forall n : nat, stream_prefix s1 n = stream_prefix s2 n .
Proof.
  intro n; revert s1 s2 Heq.
  induction n; [done |]; intros [a1 s1] [a2 s2] Heq.
  by inversion Heq; cbn in *; subst; erewrite IHn.
Qed.

Lemma EqSt_stream_prefix
  {A : Type}
  (s1 s2 : Stream A)
  (Hpref : forall n : nat, stream_prefix s1 n = stream_prefix s2 n)
  : EqSt s1 s2.
Proof.
  apply ntheq_eqst.
  intro n.
  assert (Hlt : n < S n) by constructor.
  assert (HSome : Some (Str_nth n s1) = Some (Str_nth n s2)).
  {
    rewrite <- (stream_prefix_nth s1 (S n) n Hlt).
    rewrite <- (stream_prefix_nth s2 (S n) n Hlt).
    by rewrite (Hpref (S n)).
  }
  by inversion HSome.
Qed.

Lemma elem_of_stream_prefix
  {A : Type}
  (l : Stream A)
  (n : nat)
  (a : A)
  : a stream_prefix l n <-> exists k : nat, k < n /\ Str_nth k l = a.
Proof.
  revert l a.
  induction n; simpl; split; intros.
  - by inversion H.
  - by destruct H as [k [Hk _]]; lia.
  - destruct l as (b, l).
    inversion H; subst.
    + by exists 0; split; [lia |].
    + apply IHn in H2 as [k [Hlt Heq]].
      by exists (S k); split; [lia |].
  - destruct l as (b, l).
    destruct H as [k [Hlt Heq]].
    destruct k.
    + by unfold Str_nth in Heq; simpl in Heq; subst; left.
    + unfold Str_nth in *; simpl in Heq.
      right; apply IHn.
      by exists k; split; [lia |].
Qed.

Lemma stream_prefix_app_l
  {A : Type}
  (l : list A)
  (s : Stream A)
  (n : nat)
  (Hle : n <= length l)
  : stream_prefix (stream_app l s) n = take n l.
Proof.
  revert n Hle; induction l; intros [| n] Hle; [by inversion Hle.. |].
  by cbn in *; rewrite IHl; [| lia].
Qed.

Lemma stream_prefix_app_r
  {A : Type}
  (l : list A)
  (s : Stream A)
  (n : nat)
  (Hge : n >= length l)
  : stream_prefix (stream_app l s) n = l ++ stream_prefix s (n - length l).
Proof.
  generalize dependent l.
  generalize dependent s.
  induction n.
  - by intros s [| a l] Hge; cbn in *; [| lia].
  - intros s [| a l] Hge; cbn in *; [done |].
    by rewrite <- IHn; [| lia].
Qed.

Lemma stream_prefix_map
  {A B : Type}
  (f : A -> B)
  (l : Stream A)
  (n : nat)
  : List.map f (stream_prefix l n) = stream_prefix (Streams.map f l) n.
Proof.
  by revert l; induction n; intros [a l]; cbn; rewrite ?IHn.
Qed.

Lemma stream_prefix_length
  {A : Type}
  (l : Stream A)
  (n : nat)
  : length (stream_prefix l n) = n.
Proof.
  by revert l; induction n; intros [a l]; cbn; rewrite ?IHn.
Qed.

The following two lemmas connect forall quantifiers looking at one element or two consecutive elements at a time with corresponding list quantifiers applied on their finite prefixes.

Lemma stream_prefix_ForAll
  {A : Type}
  (P : A -> Prop)
  (s : Stream A)
  : ForAll1 P s <-> forall n : nat, Forall P (stream_prefix s n).
Proof.
  rewrite ForAll1_forall.
  setoid_rewrite (Forall_nth P (hd s)).
  setoid_rewrite stream_prefix_length.
  specialize (stream_prefix_nth s) as Hi.
  split.
  - intros Hp n i Hlt.
    specialize (Hi _ _ Hlt).
    by apply nth_error_nth with (d := hd s) in Hi; rewrite Hi.
  - intros Hp n.
    assert (Hn : n < S n) by lia.
    specialize (Hp _ _ Hn).
    specialize (Hi _ _ Hn).
    apply nth_error_nth with (d := hd s) in Hi.
    by rewrite Hi in Hp.
Qed.

Lemma stream_prefix_ForAll2
  {A : Type}
  (R : A -> A -> Prop)
  (s : Stream A)
  : ForAll2 R s <-> forall n : nat, ForAllSuffix2 R (stream_prefix s n).
Proof.
  rewrite ForAll2_forall.
  setoid_rewrite (ForAllSuffix2_lookup R).
  specialize (stream_prefix_lookup s) as Hi.
  split.
  - intros Hp n i.
    destruct (decide (n <= S i)).
    + intros. rewrite lookup_ge_None_2 in H0; [done |].
      by rewrite stream_prefix_length.
    + pose proof (stream_prefix_length s n) as Hlen.
      rewrite (Hi n i), (Hi n (S i)) by lia.
      by do 2 inversion 1; subst.
  - intros Hp n.
    specialize (Hp (S (S n)) n).
    rewrite !Hi in Hp by lia.
    by apply Hp.
Qed.

Lemma ForAll2_transitive_lookup [A : Type] (R : A -> A -> Prop) {HT : Transitive R}
  : forall l, ForAll2 R l <-> forall m n, m < n -> R (Str_nth m l) (Str_nth n l).
Proof.
  setoid_rewrite stream_prefix_ForAll2.
  setoid_rewrite ForAllSuffix2_transitive_lookup; [| done].
  intros s.
  specialize (stream_prefix_lookup s) as Hi.
  split.
  - intros Hp i j Hij.
    specialize (Hp (S j) i j (Str_nth i s) (Str_nth j s) Hij).
    rewrite !Hi in Hp by lia.
    by apply Hp.
  - intros Hp n i j a b Hlt Ha Hb.
    apply lookup_lt_Some in Hb as Hltj.
    rewrite stream_prefix_length in Hltj.
    rewrite Hi in Ha, Hb by lia.
    inversion Ha; inversion Hb.
    by apply Hp.
Qed.

Lemma ForAll2_strict_lookup_rev [A : Type] (R : A -> A -> Prop) {HR : StrictOrder R}
  (l : Stream A) (Hl : ForAll2 R l)
  : forall m n, R (Str_nth m l) (Str_nth n l) -> m < n.
Proof.
  intros m n Hr.
  rewrite ForAll2_transitive_lookup in Hl by typeclasses eauto.
  destruct (Nat.lt_total m n) as [Hlt | [-> | Hgt]]; [done | |].
  - by eapply StrictOrder_Irreflexive in Hr.
  - elim (StrictOrder_Irreflexive (Str_nth n l)).
    transitivity (Str_nth m l); [| done].
    by apply Hl.
Qed.

Lemma ForAll2_strict_lookup [A : Type] (R : A -> A -> Prop) {HR : StrictOrder R}
  : forall l, ForAll2 R l <-> (forall m n, m < n <-> R (Str_nth m l) (Str_nth n l)).
Proof.
  split.
  - intro Hall; split.
    + by apply ForAll2_transitive_lookup; [typeclasses eauto |].
    + by apply ForAll2_strict_lookup_rev.
  - intro Hall.
    apply ForAll2_transitive_lookup; [typeclasses eauto |].
    by intros; apply Hall.
Qed.

Lemma ForAll2_strict_lookup_inj
 [A : Type] (R : A -> A -> Prop) {HR : StrictOrder R}
  (l : Stream A) (Hl : ForAll2 R l)
  : forall m n, Str_nth m l = Str_nth n l -> m = n.
Proof.
  intros m n Hmn.
  destruct HR as [HI HT].
  rewrite ForAll2_transitive_lookup in Hl by done.
  destruct (decide (m = n)); [done |].
  elim (HI (Str_nth n l)).
  by destruct (decide (m < n))
  ; [specialize (Hl m n) | specialize (Hl n m)]; (spec Hl; [lia |])
  ; rewrite Hmn in Hl.
Qed.

Definition stream_suffix
  {A : Type}
  (l : Stream A)
  (n : nat)
  : Stream A
  := Str_nth_tl n l.

Lemma stream_suffix_S
  {A : Type}
  (l : Stream A)
  (n : nat)
  : stream_suffix l n = Cons (Str_nth n l) (stream_suffix l (S n)).
Proof.
  revert l. induction n; intros.
  - by destruct l.
  - by apply IHn.
Qed.

Lemma stream_suffix_nth
  {A : Type}
  (s : Stream A)
  (n : nat)
  (i : nat)
  : Str_nth i (stream_suffix s n) = Str_nth (i + n) s.
Proof.
  by apply Str_nth_plus.
Qed.

Lemma stream_prefix_suffix
  {A : Type}
  (l : Stream A)
  (n : nat)
  : stream_app (stream_prefix l n) (stream_suffix l n) = l.
Proof.
  generalize dependent l. unfold stream_suffix.
  induction n; [done |]; intros [a l]; cbn.
  by f_equal; apply IHn.
Qed.

Lemma stream_prefix_prefix
  {A : Type}
  (l : Stream A)
  (n1 n2 : nat)
  (Hn : n1 <= n2)
  : take n1 (stream_prefix l n2) = stream_prefix l n1.
Proof.
  revert l n2 Hn.
  induction n1; intros [a l]; intros [| n2] Hn; simpl; [by inversion Hn.. |].
  by rewrite IHn1; [| lia].
Qed.

Lemma stream_prefix_of
  {A : Type}
  (l : Stream A)
  (n1 n2 : nat)
  (Hn : n1 <= n2)
  : stream_prefix l n1 `prefix_of` stream_prefix l n2.
Proof.
  rewrite <- (stream_prefix_prefix l n1 n2 Hn).
  by apply prefix_of_take.
Qed.

Definition stream_segment
  {A : Type}
  (l : Stream A)
  (n1 n2 : nat)
  : list A
  := drop n1 (stream_prefix l n2).

Lemma stream_segment_nth
  {A : Type}
  (l : Stream A)
  (n1 n2 : nat)
  (Hn : n1 <= n2)
  (i : nat)
  (Hi1 : n1 <= i)
  (Hi2 : i < n2)
  : nth_error (stream_segment l n1 n2) (i - n1) = Some (Str_nth i l).
Proof.
  unfold stream_segment.
  rewrite drop_nth; [| done].
  by apply stream_prefix_nth.
Qed.

Compute the sublist of stream l which starts at index n1 and ends before index n2.

Definition stream_segment_alt
  {A : Type}
  (l : Stream A)
  (n1 n2 : nat)
  : list A
  := stream_prefix (stream_suffix l n1) (n2 - n1).

Lemma stream_segment_alt_iff
  {A : Type}
  (l : Stream A)
  (n1 n2 : nat)
  : stream_segment l n1 n2 = stream_segment_alt l n1 n2.
Proof.
  apply nth_error_eq.
  intro k.
  unfold stream_segment_alt. unfold stream_segment.
  destruct (decide (n2 - n1 <= k)).
  - specialize (nth_error_None (drop n1 (stream_prefix l n2)) k); intros [_ H].
    specialize (nth_error_None (stream_prefix (stream_suffix l n1) (n2 - n1)) k); intros [_ H_alt].
    rewrite H, H_alt; [done | |].
    + by rewrite stream_prefix_length.
    + by rewrite drop_length, stream_prefix_length.
  - rewrite stream_prefix_nth, stream_suffix_nth by lia.
    assert (Hle : n1 <= n1 + k) by lia.
    specialize (drop_nth (n1 + k) n1 (stream_prefix l n2) Hle)
    ; intro Heq.
    clear Hle.
    assert (Hs : n1 + k - n1 = k) by lia.
    rewrite Hs in Heq.
    by rewrite Heq, stream_prefix_nth; [do 2 f_equal |]; lia.
Qed.

Lemma stream_prefix_segment
  {A : Type}
  (l : Stream A)
  (n1 n2 : nat)
  (Hn : n1 <= n2)
  : stream_prefix l n1 ++ stream_segment l n1 n2 = stream_prefix l n2.
Proof.
  unfold stream_segment.
  rewrite <- (take_drop n1 (stream_prefix l n2)) at 2.
  by rewrite stream_prefix_prefix.
Qed.

Lemma stream_prefix_segment_suffix
  {A : Type}
  (l : Stream A)
  (n1 n2 : nat)
  (Hn : n1 <= n2)
  : stream_app
      (stream_prefix l n1 ++ stream_segment l n1 n2)
      (stream_suffix l n2)
  = l.
Proof.
  rewrite <- (stream_prefix_suffix l n2) at 4.
  by rewrite stream_prefix_segment.
Qed.

Lemma stream_segment_app
  {A : Type}
  (l : Stream A)
  (n1 n2 n3 : nat)
  (H12 : n1 <= n2)
  (H23 : n2 <= n3)
  : stream_segment l n1 n2 ++ stream_segment l n2 n3 = stream_segment l n1 n3.
Proof.
  assert (Hle : n1 <= n3) by lia.
  specialize (stream_prefix_segment_suffix l n1 n3 Hle); intro Hl1.
  specialize (stream_prefix_segment_suffix l n2 n3 H23); intro Hl2.
  rewrite <- Hl2 in Hl1 at 4. clear Hl2.
  apply stream_app_inj_l in Hl1.
  - specialize (take_drop n1 (stream_prefix l n2)); intro Hl2.
    specialize (stream_prefix_prefix l n1 n2 H12); intro Hl3.
    rewrite Hl3 in Hl2.
    rewrite <- Hl2, <- app_assoc in Hl1.
    by apply app_inv_head in Hl1.
  - repeat rewrite app_length.
    unfold stream_segment.
    by rewrite !drop_length, !stream_prefix_length; lia.
Qed.

Definition monotone_nat_stream_prop
  (s : Stream nat)
  := forall n1 n2 : nat, n1 < n2 <-> Str_nth n1 s < Str_nth n2 s.

Lemma monotone_nat_stream_prop_from_successor s
  : ForAll2 lt s <-> monotone_nat_stream_prop s.
Proof. by apply ForAll2_strict_lookup; typeclasses eauto. Qed.

Lemma monotone_nat_stream_rev
  (s : Stream nat)
  (Hs : monotone_nat_stream_prop s)
  : forall n1 n2, Str_nth n1 s <= Str_nth n2 s -> n1 <= n2.
Proof.
  intros n1 n2 Hle.
  destruct (decide (n2 < n1)) as [Hlt |]; [| by lia].
  by apply Hs in Hlt; lia.
Qed.

Lemma monotone_nat_stream_find s (Hs : monotone_nat_stream_prop s) (n : nat)
  : n < hd s \/ exists k, Str_nth k s = n \/ Str_nth k s < n < Str_nth (S k) s.
Proof.
  induction n.
  - destruct (hd s) eqn: Hhd.
    + by right; exists 0; left.
    + by left; lia.
  - destruct (decide (hd s <= S n)); [| by left; lia].
    right.
    destruct IHn as [Hlt | [k Hk]]
; [by exists 0; left; cbv in *; lia |].
    destruct (decide (Str_nth (S k) s = S n))
    ; [by exists (S k); left |].
    exists k; right.
    cut (Str_nth k s < Str_nth (S k) s); [by lia |].
    by apply (Hs k (S k)); lia.
Qed.

Definition monotone_nat_stream :=
  {s : Stream nat | monotone_nat_stream_prop s}.

Lemma monotone_nat_stream_tl
  (s : Stream nat)
  (Hs : monotone_nat_stream_prop s)
  : monotone_nat_stream_prop (tl s).
Proof.
  by intros n1 n2; etransitivity; [| by apply (Hs (S n1) (S n2))]; lia.
Qed.

The stream of all natural numbers greater than or equal to n.
CoFixpoint nat_sequence_from (n : nat) : Stream nat
  := Cons n (nat_sequence_from (S n)).

The stream of all natural numbers.
Definition nat_sequence : Stream nat := nat_sequence_from 0.

Lemma nat_sequence_from_nth : forall m n, Str_nth n (nat_sequence_from m) = n + m.
Proof.
  intros m n; revert m.
  induction n; cbn; intros; [done |].
  by unfold Str_nth in IHn |- *; cbn; rewrite IHn; lia.
Qed.

Lemma nat_sequence_nth : forall n, Str_nth n nat_sequence = n.
Proof.
  by intros; unfold nat_sequence; rewrite nat_sequence_from_nth; lia.
Qed.

Lemma nat_sequence_from_sorted : forall n, ForAll2 lt (nat_sequence_from n).
Proof.
  intro n; apply ForAll2_forall; intro m.
  by rewrite !nat_sequence_from_nth; lia.
Qed.

Definition nat_sequence_sorted : ForAll2 lt nat_sequence :=
  nat_sequence_from_sorted 0.

Lemma nat_sequence_from_prefix_sorted
  : forall m n, LocallySorted lt (stream_prefix (nat_sequence_from m) n).
Proof.
  by intros; apply LocallySorted_ForAll2, stream_prefix_ForAll2, nat_sequence_from_sorted.
Qed.

Definition nat_sequence_prefix_sorted
  : forall n, LocallySorted lt (stream_prefix nat_sequence n) :=
  nat_sequence_from_prefix_sorted 0.

Definition stream_subsequence
  {A : Type}
  (s : Stream A)
  (ns : Stream nat)
  : Stream A
  := Streams.map (fun k => Str_nth k s) ns.

Lemma stream_prefix_nth_last
  {A : Type}
  (l : Stream A)
  (n : nat)
  (_last : A)
  : List.last (stream_prefix l (S n)) _last = Str_nth n l.
Proof.
  specialize (nth_error_last n (stream_prefix l (S n))); intro Hlast.
  specialize (stream_prefix_length l (S n)); intro Hpref_len.
  symmetry in Hpref_len.
  specialize (Hlast Hpref_len _last).
  specialize (stream_prefix_nth l (S n) n); intro Hnth.
  assert (Hlt : n < S n) by constructor.
  specialize (Hnth Hlt).
  rewrite Hnth in Hlast.
  by inversion Hlast.
Qed.

Section sec_infinitely_often.

Context
  [A : Type]
  (P : A -> Prop)
  {Pdec : forall a, Decision (P a)}
  .

Definition InfinitelyOften : Stream A -> Prop :=
  ForAll (Exists1 P).

Definition FinitelyMany : Stream A -> Prop :=
  Exists (ForAll1 (fun a => ~ P a)).

Definition FinitelyManyBound (s : Stream A) : Type :=
  { n : nat | ForAll1 (fun a => ~ P a) (Str_nth_tl n s)}.

Lemma FinitelyMany_from_bound
  : forall s, FinitelyManyBound s -> FinitelyMany s.
Proof.
  intros s [n Hn].
  apply Exists_Str_nth_tl.
  by exists n.
Qed.

Lemma InfinitelyOften_tl s
  : InfinitelyOften s -> InfinitelyOften (tl s).
Proof. by inversion 1. Qed.

Definition InfinitelyOften_nth_tl
  : forall n s, InfinitelyOften s -> InfinitelyOften (Str_nth_tl n s)
  := (@ForAll_Str_nth_tl _ (Exists1 P)).

End sec_infinitely_often.

Lemma InfinitelyOften_impl [A : Type] (P Q : A -> Prop) (HPQ : forall a, P a -> Q a)
  : forall s, InfinitelyOften P s -> InfinitelyOften Q s.
Proof.
  unfold InfinitelyOften.
  cofix IH.
  intros [a s] H.
  inversion H. simpl in H1. apply IH in H1.
  constructor; [| done].
  by rewrite Exists1_exists in *; firstorder.
Qed.

Lemma FinitelyManyBound_impl_rev
 [A : Type] (P Q : A -> Prop) (HPQ : forall a, P a -> Q a)
 : forall s, FinitelyManyBound Q s -> FinitelyManyBound P s.
Proof.
  intros s [n Hn].
  exists n.
  apply ForAll1_forall.
  rewrite ForAll1_forall in Hn.
  by firstorder.
Qed.

Prepend a non-empty list to a stream.
Definition stream_prepend {A} (nel : ne_list A) (s : Stream A) : Stream A :=
  (cofix prepend (l : ne_list A) :=
    Cons (ne_list_hd l) (from_option prepend s (ne_list_tl l))) nel.

Concatenate a stream of non-empty lists.
CoFixpoint stream_concat {A} (s : Stream (ne_list A)) : Stream A :=
  stream_prepend (hd s) (stream_concat (tl s)).

Lemma stream_prepend_prefix {A} (nel : ne_list A) (s : Stream A) :
  stream_prefix (stream_prepend nel s) (ne_list_length nel) = ne_list_to_list nel.
Proof.
  by induction nel; [| cbn; f_equal].
Qed.

Lemma stream_prepend_prefix_l
  {A : Type}
  (l : ne_list A)
  (s : Stream A)
  : forall n : nat, n <= ne_list_length l ->
    stream_prefix (stream_prepend l s) n = take n (ne_list_to_list l).
Proof.
  induction l; intros [| n] Hle; cbn; [done | | done |].
  - by cbn in Hle; replace n with 0 by lia.
  - by cbn in *; rewrite IHl; [| cbv in *; lia].
Qed.

Lemma stream_prepend_prefix_r
  {A : Type}
  (n : nat)
  : forall l : ne_list A, n >= ne_list_length l ->
    forall (s : Stream A),
      stream_prefix (stream_prepend l s) n
        =
      ne_list_to_list l ++ stream_prefix s (n - ne_list_length l).
Proof.
  induction n; intros [| a l] Hge s; cbn in *; [by lia.. | |].
  - by rewrite Nat.sub_0_r.
  - by rewrite <- IHn; [| cbv in *; lia].
Qed.

Lemma stream_concat_unroll {A} (a : ne_list A) (s : Stream (ne_list A)) :
  stream_concat (Cons a s) = stream_prepend a (stream_concat s).
Proof. by apply stream_eq_hd_tl. Qed.

Lemma stream_concat_prefix {A} (s : Stream (ne_list A)) n len
  (prefix := mjoin (List.map ne_list_to_list (stream_prefix s n)))
  : len = length prefix ->
    stream_prefix (stream_concat s) len = prefix.
Proof.
  intros ->; revert s prefix.
  induction n; [done |].
  intros [n0 s]; cbn.
  rewrite app_length.
  assert (Hge :
    length (ne_list_to_list n0) + length (mjoin (List.map ne_list_to_list (stream_prefix s n)))
     length (ne_list_to_list n0)) by lia.
  rewrite stream_concat_unroll, stream_prepend_prefix_r by done.
  rewrite <- IHn at 2.
  do 2 f_equal.
  rewrite Nat.add_comm.
  by apply Nat.add_sub.
Qed.

Section sec_temporal.

Definition progress [A : Type] (R : A -> A -> Prop) : Stream A -> Prop :=
  ForAll (fun s => let x := hd s in let a := hd (tl s) in a = x \/ R a x).

Lemma refutation :
  forall [A : Type] [R : A -> A-> Prop] (HR : well_founded R) [s : Stream A],
    ~ ForAll (fun s => Exists (fun x => R (hd x) (hd s)) s) s.
Proof.
  intros A R HR [h t]; revert t.
  specialize (HR h).
  induction HR as [h' _ IH].
  intros t HF.
  destruct (id HF).
  destruct (use_Exists _ _ _ H HF) as [[a' s'] [Ha' H1']].
  by eapply IH; eauto.
Qed.

Lemma progress_Exists_neq :
  forall [A : Type] [R : A -> A -> Prop] (s : Stream A),
    progress R s -> ForAll (Exists (fun s' => hd s' <> hd s)) s ->
      Exists (fun s' => R (hd s') (hd s)) s.
Proof.
  intros A R [a s] Hprogress Hex; cbn.
  generalize (eq_refl : hd (Cons a s) = a).
  destruct Hex as [Hex _].
  revert Hprogress; induction Hex; intros Hprogress <-; [by cbn in H; congruence |].
  constructor 2.
  inversion Hprogress as [[] ?]; cbn in *.
  - by apply IHHex.
  - by constructor.
Qed.

Lemma stabilization :
  forall [A : Type] [R : A -> A-> Prop] (HR : well_founded R) [s : Stream A],
    progress R s -> exists x : A, Exists (ForAll (fun s => hd s = x)) s.
Proof.
  intros A R HR s Hprogress.
  apply Classical_Prop.NNPP; intros Hnex.
  assert (H : forall x : A, ForAll (Exists (fun s => hd s <> x)) s)
    by (apply not_Exists_ForAll; done).
  apply (@refutation _ _ HR s).
  clear Hnex; revert s Hprogress H.
  cofix CH.
  constructor.
  - by apply progress_Exists_neq.
  - apply CH.
    + by apply Hprogress.
    + by apply H.
Qed.

End sec_temporal.