From PslBase Require Import Base.
From Undecidability.L.Complexity Require Import Tactics MorePrelim.
From Undecidability.L.Datatypes Require Import LLists LLNat LBool LProd LOptions.
From Undecidability.L.Complexity.Problems Require Import FSAT Cook.BinaryPR.
Require Import Lia.

Reduction of BinaryPR to FSAT

High-level overview: We lay out the BinaryPR computation in a tableau which has (steps + 1) lines, where steps is the number of steps of the BPR instance, and each line has a length which is equal to the length of the BPR strings. Each cell of the tableau corresponds to one symbol of a BPR string and is encoded using a single Boolean variable in the FSAT instance.
The FSAT formula consists of three gadgets, encoding:
  • the constraint on the initial string
  • the validity of rewrites
  • the final constraints.
The constraint on the initial string is straightforward to encode: We just have a big AND over the positions of the string.
For the validity of rewrites, we have a AND consisting of a subformula for each of the rewrites. Each rewrite in turn forces that the successor string evolves validly from the current string - we have an AND over the offsets of the string at which rewrite windows have to hold. For each of the offsets, we then have a disjunction over all rewrite windows. That a rewrite window holds at a position is encoded similarly to the initial string.
For the final constraint, we have a disjunction over the final strings and a nested disjunction over all positions at which a string can be a substring.

Section fixInstance.
  Variable (bpr : BinaryPR).

  Notation offset := (offset bpr).
  Notation width := (width bpr).
  Notation init := (init bpr).
  Notation windows := (windows bpr).
  Notation final := (final bpr).
  Notation steps := (steps bpr).

  Context (A : BinaryPR_wellformed bpr).
  Notation llength := (length init).
  Implicit Types (a : assgn) (v : var).

convenience functions for creating formulas
  Notation Ffalse := (¬Ftrue).
  Fixpoint listOr (l : list formula) := match l with
    | [] Ffalse
    | a :: l a listOr l
    end.

  Fixpoint listAnd (l : list formula) := match l with
    | [] Ftrue
    | a :: l a listAnd l
    end.

  Lemma listOr_sat_iff l a : satisfies a (listOr l) f, f l satisfies a f.
  Proof.
    induction l; cbn.
    - unfold satisfies. cbn. split; [congruence | intros (f & [] & _)].
    - unfold satisfies. rewrite evalFormula_or_iff, IHl. split.
      + intros [ H | (f & & )]; [exists ; eauto | exists f; eauto].
      + intros (f & [ | ] & ); [now left | right; exists f; eauto].
  Qed.


  Lemma listAnd_sat_iff l a : satisfies a (listAnd l) f, f l satisfies a f.
  Proof.
    induction l; cbn.
    - unfold satisfies. cbn. split; [intros _ f [] | intros _; easy].
    - unfold satisfies. rewrite evalFormula_and_iff, IHl. split.
      + intros ( & ) f [ | %]; eauto.
      + intros H; split; [ apply H | intros f ; apply H]; eauto.
  Qed.


generate the list of values assigned to the variables by a in range lower, lower + len)
  Fixpoint explicitAssignment a lower len :=
    match len with
      | 0 []
      | S len explicitAssignment a lower len [list_in_decb Nat.eqb a (lower + len)]
    end.

  Lemma explicitAssignment_length a lower len : |explicitAssignment a lower len| = len.
  Proof.
    revert lower. induction len; intros; cbn.
    - reflexivity.
    - rewrite app_length, IHlen. now cbn.
  Qed.


  Lemma explicitAssignment_nth a lower len k :
     k < len nth_error (explicitAssignment a lower len) k = Some (evalVar a (lower + k)).
  Proof.
    intros. induction len.
    - lia.
    - cbn. destruct (le_lt_dec len k).
      + assert (len = k) as by lia.
        rewrite nth_error_app2; rewrite explicitAssignment_length; [ rewrite Nat.sub_diag; cbn | lia]. easy.
      + rewrite nth_error_app1; [ | rewrite explicitAssignment_length; easy ]. now apply IHlen.
  Qed.


  Lemma explicitAssignment_app a : explicitAssignment a ( + ) = explicitAssignment a explicitAssignment a ( + ) .
  Proof.
    induction ; cbn.
    - now rewrite Nat.add_0_r, app_nil_r.
    - rewrite Nat.add_succ_r. cbn. rewrite , app_assoc. easy.
  Qed.


  Lemma expAssgn_to_assgn s b :
    {a & x, x a x s nth_error b (x - s) = Some true}.
  Proof.
    revert s.
    induction b; cbn; intros.
    - exists []. intros x. destruct (x-s); cbn; easy.
    - destruct (IHb (S s)) as (assgn & IH). destruct a.
      + exists (s :: assgn). intros x. split.
        * intros [ | H]; [ rewrite Nat.sub_diag; easy | ].
          apply IH in H as ( & ). split; [ lia | ].
          now eapply nth_error_step.
        * intros ( & ). apply le_lt_eq_dec in as [ | ].
          -- right. apply IH. split; [ lia | ]. eapply nth_error_step, . lia.
          -- now left.
      + exists assgn. intros x. split.
        * intros ( & )%IH. split; [ lia | rewrite nth_error_step; easy].
        * intros ( & ). apply le_lt_eq_dec in as [ | ].
          -- apply IH. split; [ lia | ]. apply nth_error_step in ; easy.
          -- rewrite Nat.sub_diag in . cbn in . congruence.
  Qed.


  Lemma expAssgn_to_assgn_inv a s b : ( x, x a x s nth_error b (x -s) = Some true)
     explicitAssignment a s (|b|) = b.
  Proof.
    intros. apply list_eq_nth_error. split; [ apply explicitAssignment_length | ].
    intros k . rewrite explicitAssignment_length in .
    rewrite explicitAssignment_nth by apply .
    unfold evalVar. destruct list_in_decb eqn:.
    - apply list_in_decb_iff in ; [ | intros; now rewrite Nat.eqb_eq ].
      apply H in as (_ & ). now replace (s + k - s) with k in by lia.
    - apply list_in_decb_iff' in ; [ | intros; now rewrite Nat.eqb_eq].
      destruct (nth_error b k) eqn:.
      + destruct ; [ | easy]. exfalso; apply . apply H.
        replace (s + k - s) with k by lia. easy.
      + apply nth_error_none in . lia.
  Qed.


  Definition predicate := list bool Prop.
  Implicit Type (p : predicate).
  Definition encodesPredicateAt (start : ) (l : ) f p:= a, satisfies a f p (explicitAssignment a start l).

  Definition projVars start len (l : list bool) := firstn len (skipn start l).

  Lemma projVars_length l s m : |l| (s + m) |projVars s m l| = m.
  Proof.
    intros. unfold projVars. rewrite firstn_length. rewrite skipn_length. lia.
  Qed.


  Lemma projVars_app1 m : || = m projVars 0 m ( ) = .
  Proof.
    intros. unfold projVars. cbn. rewrite firstn_app. subst.
    now rewrite Nat.sub_diag, firstn_O, app_nil_r, firstn_all.
  Qed.


  Lemma projVars_app2 u m : || = u projVars u m ( ) = projVars 0 m .
  Proof.
    intros. unfold projVars. rewrite skipn_app; [ | easy]. now cbn.
  Qed.


  Lemma projVars_app3 m : || = projVars ( + ) m ( ) = projVars m .
  Proof.
    intros. unfold projVars. erewrite skipn_add; [ easy| lia].
  Qed.


  Lemma projVars_all l m : m = |l| projVars 0 m l = l.
  Proof.
    intros. unfold projVars. cbn. rewrite H. apply firstn_all.
  Qed.


  Lemma projVars_comp m: projVars (projVars m) = projVars ( + ) (min ( - )) m.
  Proof.
    unfold projVars. intros.
    rewrite skipn_firstn_skipn. rewrite firstn_firstn. reflexivity.
  Qed.


  Lemma projVars_add s m : projVars s ( + ) m = projVars s m projVars (s + ) m.
  Proof.
    unfold projVars. rewrite firstn_add, skipn_skipn. now rewrite Nat.add_comm.
  Qed.


  Lemma projVars_length_le start l m: |projVars start l m| |m|.
  Proof.
    unfold projVars.
    rewrite firstn_length. rewrite skipn_length. lia.
  Qed.


  Lemma projVars_length_le2 start l m : |projVars start l m| l.
  Proof.
    unfold projVars. rewrite firstn_length, skipn_length. lia.
  Qed.


  Lemma projVars_in_ge start l m : start |m| projVars start (|l|) m = l |m| start + |l|.
  Proof.
    intros H. unfold projVars in H. rewrite H, firstn_length, skipn_length. lia.
  Qed.


  Definition combinedLength := max ( +) ( + ) - min .
  Definition combinedStart := min .

from the combined assignment for the combination of two formulas, we can restore an assignment for the first formula
  Lemma projVars_combined1 a: explicitAssignment a = projVars ( - combinedStart ) (explicitAssignment a (combinedStart ) (combinedLength )).
  Proof.
    unfold projVars.
    apply list_eq_nth_error. split.
    - rewrite explicitAssignment_length. unfold combinedStart, combinedLength. rewrite firstn_length.
      rewrite skipn_length. rewrite explicitAssignment_length. lia.
    - intros. rewrite explicitAssignment_length in H. unfold combinedStart, combinedLength.
      rewrite nth_error_firstn; [ | apply H].
      rewrite nth_error_skipn. rewrite !explicitAssignment_nth; [easy | lia | lia].
  Qed.


  Lemma projVars_combined2 a: explicitAssignment a = projVars ( - combinedStart ) (explicitAssignment a (combinedStart ) (combinedLength )).
  Proof.
    unfold combinedStart, combinedLength. rewrite Nat.min_comm. rewrite Nat.max_comm. apply projVars_combined1.
  Qed.


  Lemma encodesPredicate_and :
    encodesPredicateAt encodesPredicateAt
     encodesPredicateAt (combinedStart ) (combinedLength ) ( ) ( m ( (projVars ( - combinedStart ) m) (projVars ( - combinedStart ) m))).
  Proof.
    intros .
    intros a. split; intros H.
    + apply evalFormula_and_iff in H as ( & ).
      rewrite projVars_combined1, projVars_combined2.
      unfold encodesPredicateAt in , . rewrite , . easy.
    + rewrite projVars_combined1, projVars_combined2 in H.
      unfold satisfies. apply evalFormula_and_iff.
      split; [apply , H | apply , H].
  Qed.


  Lemma encodesPredicate_or :
    encodesPredicateAt encodesPredicateAt
     encodesPredicateAt (combinedStart ) (combinedLength ) ( ) ( m ( (projVars ( - combinedStart ) m) (projVars ( - combinedStart ) m))).
  Proof.
    intros .
    intros a. split; intros H.
    + apply evalFormula_or_iff in H as [H | H];
      rewrite projVars_combined1, projVars_combined2;
      unfold encodesPredicateAt in , ; rewrite , ; easy.
    + rewrite projVars_combined1, projVars_combined2 in H.
      unfold satisfies. apply evalFormula_or_iff.
      destruct H as [H | H]; [left; apply , H | right; apply , H].
  Qed.


next, we do the same for lists of formulas where the individual formulas are placed in a regular pattern starting at an index s with an offset step
  Fixpoint tabulate_step (step s n : ) :=
    match n with
      | 0 []
      | S n s :: tabulate_step step (step + s) n
    end.
  Definition tabulate_formula s step n (t : formula) := map t (tabulate_step step s n).

  Lemma in_tabulate_step_iff step s n x : x tabulate_step step s n i, i < n x = s + step * i.
  Proof.
    revert s. induction n; cbn; intros.
    - split; [easy | intros (i & H & _); lia ].
    - split.
      + intros [ | H%IHn]; [exists 0; lia | ].
        destruct H as (i & & ). exists (S i). lia.
      + intros (i & & ).
        destruct i.
        * now left.
        * right. apply IHn. exists i. lia.
  Qed.


  Lemma in_tabulate_formula_iff s step n t f:
    f tabulate_formula s step n t i, i < n f = t (s + step * i).
  Proof.
    unfold tabulate_formula. rewrite in_map_iff. setoid_rewrite in_tabulate_step_iff.
    split.
    - intros (x & & (i & & )). exists i. eauto.
    - intros (i & & ). exists (s + step * i). eauto.
  Qed.


  Fact projVars_mul_offset a s step i l n: i < n explicitAssignment a (s + step * i) l = projVars (step * i) l (explicitAssignment a s (step * n + (l - step))).
  Proof.
    intros H. apply list_eq_nth_error.
    split.
    - rewrite projVars_length; rewrite !explicitAssignment_length; [lia | ]. nia.
    - rewrite explicitAssignment_length. intros k .
      unfold projVars. rewrite nth_error_firstn by apply .
      rewrite nth_error_skipn. rewrite !explicitAssignment_nth.
      + easy.
      + nia.
      + apply .
  Qed.


  Lemma encodesPredicate_listOr_tabulate l (t : formula) p :
    ( s, encodesPredicateAt s l (t s) p)
     s step n, encodesPredicateAt s (step * n + (l - step)) (listOr (tabulate_formula s step n t)) ( m i, i < n p (projVars (step * i) l m)).
  Proof.
    intros H s step n. unfold encodesPredicateAt. intros a.
    rewrite listOr_sat_iff. setoid_rewrite in_tabulate_formula_iff.
    split.
    - intros (f & (i & & ) & ). exists i. split; [easy | ].
      specialize (H (s + step * i)). apply H in .
      rewrite projVars_mul_offset by apply . apply .
    - intros (i & & ). exists (t (s + step * i)). split.
      + exists i. eauto.
      + apply H. erewrite projVars_mul_offset by apply . apply .
  Qed.


  Lemma encodesPredicate_listAnd_tabulate l (t : formula) p :
    ( s, encodesPredicateAt s l (t s) p)
     s step n, encodesPredicateAt s (step * n + (l - step)) (listAnd (tabulate_formula s step n t)) ( m i, i < n p (projVars (step * i) l m)).
  Proof.
    intros H s step n. unfold encodesPredicateAt. intros a.
    rewrite listAnd_sat_iff. setoid_rewrite in_tabulate_formula_iff.
    split.
    - intros i . rewrite projVars_mul_offset by apply .
      apply H, . exists i; eauto.
    - intros f (i & & ). apply H.
      erewrite projVars_mul_offset by apply . now apply .
  Qed.


similarly, we can combine multiple formulas at the same position
  Lemma encodesPredicate_listOr_map (X : Type) s l (es : list X) (p : X predicate) (f : X formula) :
    ( e, e es encodesPredicateAt s l (f e) (p e))
     encodesPredicateAt s l (listOr (map f es)) ( m e, e es p e m).
  Proof.
    intros H. unfold encodesPredicateAt. intros a.
    rewrite listOr_sat_iff. setoid_rewrite in_map_iff.
    split.
    - intros (fo & (x & & Hel) & ). apply (H _ Hel) in . eauto.
    - intros (x & Hel & ). apply (H _ Hel) in . exists (f x).
      split; eauto.
  Qed.


  Lemma encodesPredicate_listAnd_map (X : Type) s l (es : list X) (p : X predicate) (f : X formula) :
    ( e, e es encodesPredicateAt s l (f e) (p e))
     encodesPredicateAt s l (listAnd (map f es)) ( m e, e es p e m).
  Proof.
    intros H. unfold encodesPredicateAt. intros a.
    rewrite listAnd_sat_iff. setoid_rewrite in_map_iff.
    split.
    - intros e Hel. apply (H _ Hel). apply . eauto.
    - intros fo (x & & Hel). apply (H _ Hel). now apply .
  Qed.


  Definition encodeLiteral v (sign : bool) : formula := if sign then v else ¬ v.

  Lemma encodeLiteral_correct v sign : a, satisfies a (encodeLiteral v sign) evalVar a v = sign.
  Proof.
    intros. unfold satisfies, encodeLiteral. destruct sign; cbn; [ tauto |simp_bool; tauto ].
  Qed.


  Lemma encodeLiteral_encodesPredicate v sign : encodesPredicateAt v 1 (encodeLiteral v sign) ( l l = [sign]).
  Proof.
    intros. split; intros.
    + apply encodeLiteral_correct in H. specialize (explicitAssignment_length a v 1) as .
      assert (0 < 1) as by lia.
      specialize (@explicitAssignment_nth a v 1 0 ) as .
      destruct explicitAssignment as [ | s l]; cbn in ; [ congruence | destruct l; [ | cbn in ; congruence]].
      cbn in . now inv .
    + apply encodeLiteral_correct. assert (0 < 1) as by lia.
      specialize (@explicitAssignment_nth a v 1 0 ) as .
      rewrite H in ; cbn in . now inv .
  Qed.


  Lemma encodesPredicateAt_extensional s l f : ( m, |m| = l m m) encodesPredicateAt s l f encodesPredicateAt s l f .
  Proof.
    intros. unfold encodesPredicateAt. split; intros; specialize (H (explicitAssignment a s l) (explicitAssignment_length _ _ _)); [now rewrite H | now rewrite H].
  Qed.


  Ltac encodesPredicateAt_comp_simp H :=
    unfold combinedStart, combinedLength in H;
    try (rewrite Nat.min_l in H by nia); try (rewrite Nat.min_r in H by nia);
    try (rewrite Nat.max_l in H by nia); try (rewrite Nat.max_r in H by nia);
    try rewrite !Nat.sub_diag in H.

  Fixpoint encodeListAt (startA : ) (l : list bool) :=
    match l with
    | [] Ftrue
    | x :: l (encodeLiteral startA x) (encodeListAt (1 + startA) l)
    end.

  Lemma encodeListAt_encodesPredicate start l : encodesPredicateAt start (|l|) (encodeListAt start l) ( m m = l).
  Proof.
    revert start. induction l; intros.
    - cbn. split; [eauto | ]. unfold satisfies. intros; easy.
    - cbn. specialize (@encodesPredicate_and start (S start) 1 (|l|) (encodeLiteral start a) (encodeListAt (S start) l) _ _ (encodeLiteral_encodesPredicate start a) (IHl (S start))) as .
      encodesPredicateAt_comp_simp .
      replace (S start - start) with 1 in by lia.
      replace (S start + (|l|) - start) with (S (|l|)) in by lia.
      eapply encodesPredicateAt_extensional; [ | apply ].
      intros m . unfold projVars.
      destruct m; cbn; [split; intros; easy | ].
      split.
      + intros H. inv H. now rewrite firstn_all.
      + intros ( & ). inv . f_equal. apply Nat.succ_inj in . now apply firstn_all_inv.
  Qed.


encoding of windows startA is the position at which the premise is placed, startB the position at which the conclusion is placed
  Definition encodeWindowAt (startA startB : ) (win : PRWin bool) := encodeListAt startA (prem win) encodeListAt startB (conc win).

  Lemma encodeWindowAt_encodesPredicate start len win :
    win windows encodesPredicateAt start (len + width) (encodeWindowAt start (start + len) win) ( m projVars 0 width m = prem win projVars len width m = conc win).
  Proof.
    intros .
    specialize (encodesPredicate_and (encodeListAt_encodesPredicate start (prem win)) (encodeListAt_encodesPredicate (start + len) (conc win))) as H.
    destruct A as (_ & _ & _ & & & _). apply in as ( & ).
    encodesPredicateAt_comp_simp H.
    rewrite ! in H. rewrite ! in H.
    replace (start + len + width - start) with (len + width) in H by lia.
    replace (start + len - start) with len in H by lia.
    unfold encodeWindowAt. eapply encodesPredicateAt_extensional; [ | apply H].
    tauto.
  Qed.


encoding of the disjunction of all windows of the BinaryPR instance
  Definition encodeWindowsAt (startA startB : ) := listOr (map (encodeWindowAt startA startB) windows).

  Lemma encodeWindowsAt_encodesPredicate len start : len width encodesPredicateAt start (len + width) (encodeWindowsAt start (start + len)) ( m win, win windows projVars 0 width m = prem win projVars len width m = conc win).
  Proof.
    intros . apply encodesPredicate_listOr_map.
    intros win Hel. apply encodeWindowAt_encodesPredicate, Hel.
  Qed.


  Fixpoint encodeWindowsInLine' (stepindex l : ) (startA startB : ) :=
    if l <? width then Ftrue
                  else match stepindex with
                    | 0 Ftrue
                    | S stepindex encodeWindowsAt startA startB encodeWindowsInLine' stepindex (l - offset) (startA + offset) (startB + offset)
                    end.

  Lemma encodeWindowsInLineP_stepindex_monotone' index startA startB : n, n index encodeWindowsInLine' index n startA startB = encodeWindowsInLine' (S index) n startA startB.
  Proof.
    destruct A as ( & & _).
    revert startA startB.
    induction index; intros.
    - unfold encodeWindowsInLine'. assert (n = 0) as by lia. cbn; destruct width; [ lia | easy ].
    - unfold encodeWindowsInLine'. destruct (Nat.ltb n width); [ easy | ]. fold encodeWindowsInLine'.
      erewrite IHindex by lia. easy.
  Qed.


  Lemma encodeWindowsInLineP_stepindex_monotone index index' startA startB : index' index encodeWindowsInLine' index index startA startB = encodeWindowsInLine' index' index startA startB.
  Proof.
    intros. revert index H.
    induction index'; intros.
    - assert (index = 0) as by lia. easy.
    - destruct (nat_eq_dec (S index') index).
      + now rewrite e.
      + assert (index' index) as by lia. rewrite encodeWindowsInLineP_stepindex_monotone' by lia. now apply IHindex'.
  Qed.


  Lemma encodeWindowsInLineP_encodesPredicate start l : l llength ( k, l = k * offset) encodesPredicateAt start (l + llength) (encodeWindowsInLine' l l start (start + llength)) ( m valid offset width windows (projVars 0 l m) (projVars llength l m)).
  Proof.
    intros .
    destruct A as ( & & & & & ).
    revert start .
    apply complete_induction with (x := l). clear l. intros l IH. intros.

    destruct l.
    -
      unfold encodeWindowsInLine'. rewrite (proj2 (Nat.ltb_lt _ _) ).
      intros a. split; [ intros; constructor | intros _; unfold satisfies; eauto].
    - destruct (le_lt_dec width (S l)).
      + assert ( (S l) < width) as %Nat.ltb_nlt by lia. cbn -[projVars]; setoid_rewrite .
        destruct offset as [ | offset]; [ lia | ].

        assert (S l - S offset < S l) as by lia. apply IH with (start := start + S offset) in . 2: nia.
        2: { destruct H as (k & H). destruct k; [ lia | ]. exists k. lia. }
        clear H IH.
        assert (llength width) as by lia.
        apply (encodeWindowsAt_encodesPredicate start) in .

        specialize (encodesPredicate_and ) as . clear .
        encodesPredicateAt_comp_simp .

        replace (start + S offset + llength) with (start + llength + S offset) in by lia.
        replace (start + S offset + (S l - S offset + llength) - start) with (S (l + llength)) in .
        2: { destruct as (? & & ). nia. }
        
        rewrite encodeWindowsInLineP_stepindex_monotone with (index' := l) in ; [ | lia].
        eapply encodesPredicateAt_extensional; [ | apply ].

        clear .
        intros.
        destruct as (k & & ).
        assert (S l = S offset + (S l - S offset)) as by nia.
        split; intros.
        * rewrite in . clear .
          rewrite !projVars_add in . inv .
          2 : { exfalso. apply list_eq_length in . rewrite !app_length in . rewrite !projVars_length in ; [ | easy | easy]. nia. }
          1: { exfalso. apply list_eq_length in . rewrite !app_length in . rewrite projVars_length in ; [ | cbn; nia]. cbn in . lia. }

          apply app_eq_length in as ( & ); [ | rewrite projVars_length; [easy | nia] ].
          apply app_eq_length in as ( & ); [ | rewrite projVars_length; [easy | nia]].
          split.
          -- exists win. rewrite !projVars_comp; cbn. rewrite !Nat.min_l by lia.
             rewrite !Nat.add_0_r.
             clear . rewrite !projVars_add in .
             replace (S offset + (l - offset)) with (width + (S l - width)) in by nia.
             rewrite !projVars_add in .
             split; [ easy | split ]; apply in as ( & ).
             ++ destruct as ((b & ) & _). eapply app_eq_length in ; [ easy| rewrite projVars_length; easy ].
             ++ destruct as (_ & (b & )). eapply app_eq_length in ; [ easy | rewrite projVars_length; easy].
          -- clear .
             rewrite !projVars_comp. replace (start + S offset - start) with (S offset) by lia. rewrite !Nat.min_l by lia.
             apply .
        *
          destruct as ( & ).
          rewrite , !projVars_add.
          destruct as (win & & & ).
          econstructor 3.
          -- rewrite !projVars_comp in . rewrite !Nat.min_l in by lia.
             replace (start + S offset - start) with (S offset) in by lia.
             apply .
          -- rewrite projVars_length; lia.
          -- rewrite projVars_length; lia.
          -- clear . apply .
          -- clear . rewrite !projVars_add.
             replace (S offset + (S l - S offset)) with (width + (S l - width)) by lia.
             rewrite !projVars_add.
             rewrite projVars_comp in , . rewrite !Nat.min_l in , by lia.
             rewrite Nat.add_0_r in ; cbn in , .
             rewrite , . split; unfold prefix; eauto.
    +
      clear IH. assert ( (S l) < width) as %Nat.ltb_lt by lia. cbn -[projVars]; setoid_rewrite .
      intros a; split; [intros _ | ].
      * destruct H as (k & H). eapply valid_vacuous.
        -- rewrite !projVars_length; [easy | rewrite explicitAssignment_length; lia | rewrite explicitAssignment_length; lia].
        -- rewrite projVars_length; [ easy | rewrite explicitAssignment_length; lia].
        -- rewrite projVars_length; [ | rewrite explicitAssignment_length; lia].
           apply H.
      * intros _; easy.
  Qed.


the above construction specialized to the setting we need: the conclusion starts exactly one line after the premise
  Definition encodeWindowsInLine start := encodeWindowsInLine' llength llength start (start + llength).
  Lemma encodeWindowsInLine_encodesPredicate start : encodesPredicateAt start (llength + llength) (encodeWindowsInLine start) ( m valid offset width windows (projVars 0 llength m) (projVars llength llength m)).
  Proof.
    unfold encodeWindowsInLine.
    apply (@encodeWindowsInLineP_encodesPredicate start llength); [easy | apply A].
  Qed.


  Definition encodeWindowsInAllLines := listAnd (tabulate_formula 0 llength steps encodeWindowsInLine).
  Lemma encodeWindowsInAllLines_encodesPredicate : encodesPredicateAt 0 ((S steps) * llength) encodeWindowsInAllLines
    ( m ( i, 0 i < steps valid offset width windows (projVars (i * llength) llength m) (projVars (S i * llength) llength m))).
  Proof.
    eapply encodesPredicateAt_extensional.
    2: { unfold encodeWindowsInAllLines.
         replace (S steps * llength) with (llength * steps + ((llength + llength) -llength)) by lia.
         apply encodesPredicate_listAnd_tabulate. intros s. apply encodeWindowsInLine_encodesPredicate.
    }
    intros m Hlen. split.
    - intros H i . specialize (H i ltac:(lia)).
      rewrite !projVars_comp. rewrite !Nat.min_l by lia. cbn. rewrite Nat.mul_comm at 1.
      replace (llength + llength * i) with (S i * llength) by lia. apply H.
    - intros H i (_ & ). specialize (H i ). rewrite !projVars_comp in H. rewrite !Nat.min_l in H by lia.
      cbn in H. cbn. rewrite Nat.mul_comm. apply H.
  Qed.


  Fixpoint encodeSubstringInLine' (s : list bool) (stepindex l : ) (start : ) :=
    if l <? |s| then Ffalse
                  else match stepindex with
                    | 0 (match s with [] Ftrue | _ Ffalse end)
                    | S stepindex encodeListAt start s encodeSubstringInLine' s stepindex (l - offset) (start + offset)
                    end.

  Lemma encodeSubstringInLineP_stepindex_monotone' s index start : n, |s| > 0 n index encodeSubstringInLine' s index n start = encodeSubstringInLine' s (S index) n start.
  Proof.
    destruct A as ( & & _).
    revert start.
    induction index; intros.
    - unfold encodeSubstringInLine'. assert (n = 0) as by lia. cbn; destruct s; [ cbn in *; lia | easy ].
    - unfold encodeSubstringInLine'. destruct (Nat.ltb n (|s|)); [ easy | ]. fold encodeSubstringInLine'.
      erewrite IHindex by lia. easy.
  Qed.


  Lemma encodeSubstringInLineP_stepindex_monotone s start :
    |s| > 0 encodeSubstringInLine' s start = encodeSubstringInLine' s start.
  Proof.
    intros. revert .
    induction ; intros.
    - assert ( = 0) as by lia. easy.
    - destruct (nat_eq_dec (S ) ).
      + now rewrite e.
      + assert ( ) as by lia. rewrite encodeSubstringInLineP_stepindex_monotone' by lia. now apply .
  Qed.


  Lemma encodeSubstringInLineP_encodesPredicate s start l : |s| > 0 l llength
     ( k, l = k * offset) encodesPredicateAt start l (encodeSubstringInLine' s l l start) ( m ( k, k * offset l projVars (k * offset) (|s|) m = s)).
  Proof.
    intros F.
    destruct A as ( & & & & & ).
    revert start.
    apply complete_induction with (x := l). clear l. intros l IH. intros.

    destruct l.
    - cbn -[projVars]. destruct s; cbn -[projVars].
      + cbn in F; easy.
      +
        intros a; split.
        * unfold satisfies; cbn; congruence.
        * intros (k & & ). destruct k; [clear | nia]. cbn in . congruence.
    - destruct (le_lt_dec (|s|) (S l)).
      +
        assert ( (S l) < (|s|)) as %Nat.ltb_nlt by lia. cbn -[projVars]; setoid_rewrite .
        destruct offset as [ | offset]; [ lia | ].

        assert (S l - S offset < S l) as by lia. apply IH with (start := start + S offset) in ; [ | nia | ].
        2: { destruct as (k & ). destruct k; [ lia | ]. exists k. lia. }
        clear H IH.
        specialize (encodeListAt_encodesPredicate start s) as .
        specialize (encodesPredicate_or ) as H. clear .

        encodesPredicateAt_comp_simp H.
        destruct as (k & ).
        assert (k > 0) by nia.         replace (start + S offset + (S l - S offset) - start) with (S l) in H by nia.
        replace (start + S offset - start) with (S offset) in H by lia.

        rewrite encodeSubstringInLineP_stepindex_monotone with ( := l) in H; [ | apply F| lia].
        eapply encodesPredicateAt_extensional; [ | apply H]. clear H.
        intros. split.
        * intros ( & & ).
          destruct ; [ now left | right].
          exists . split; [ easy | ].
          rewrite projVars_comp.
          cbn [Nat.mul] in .
          enough (|s| l - offset - * S offset).
          { rewrite Nat.min_l by lia. rewrite Nat.add_comm. apply . }
          apply projVars_in_ge in ; nia.
        * intros .
          destruct as [ | ( & & )]; [ exists 0; cbn; easy | ].
          exists (S ). split; [ nia | ].
          rewrite projVars_comp in .

          enough (|s| l - offset - * S offset).
          { rewrite Nat.min_l in by lia. cbn [Nat.mul]. rewrite Nat.add_comm. apply . }
          apply list_eq_length in .
          match type of with (|projVars ?a ?b ?c| = _) specialize (projVars_length_le2 a b c) as end.
          lia.
    +
      clear IH. assert ( (S l) < (|s|)) as %Nat.ltb_lt by lia. cbn -[projVars]; setoid_rewrite .
      intros a. unfold satisfies; cbn [negb evalFormula].
      split; [congruence | ].
      intros (k & & ). apply list_eq_length in . specialize (projVars_length_le (k * offset) (|s|) (explicitAssignment a start (S l))) as .
      rewrite explicitAssignment_length in . lia.
  Qed.


  Definition encodeSubstringInLine s start l := match s with [] Ftrue | _ encodeSubstringInLine' s l l start end.

  Lemma encodeSubstringInLine_encodesPredicate s start l : l llength ( k, l = k * offset) encodesPredicateAt start l (encodeSubstringInLine s start l) ( m ( k, k * offset l projVars (k * offset) (|s|) m = s)).
  Proof.
    intros. unfold encodeSubstringInLine. destruct s eqn:.
    - unfold satisfies. cbn. intros; split.
      + intros _. exists 0; cbn; firstorder.
      + intros _. reflexivity.
    - apply encodeSubstringInLineP_encodesPredicate; cbn; easy.
  Qed.


the final constraint now is a disjunction over all given substrings
  Definition encodeFinalConstraint (start : ) := listOr (map ( s encodeSubstringInLine s start llength) final).

  Lemma encodeFinalConstraint_encodesPredicate start : encodesPredicateAt start llength (encodeFinalConstraint start) ( m satFinal offset llength final m).
  Proof.
    unfold encodeFinalConstraint.
    eapply encodesPredicateAt_extensional; [ | apply encodesPredicate_listOr_map].
    2: { intros. apply encodeSubstringInLine_encodesPredicate; [easy | apply A]. }
    intros m . split.
    - intros (subs & k & & & ).
      exists subs. split; [ apply | ].
      exists k. split; [ easy | ].
      destruct as (b & ). unfold projVars. rewrite .
      rewrite firstn_app, firstn_all, Nat.sub_diag; cbn. now rewrite app_nil_r.
    - intros (subs & & (k & & )).
      exists subs, k. split; [apply | split; [ apply | ]].
      unfold prefix. unfold projVars in .
      setoid_rewrite firstn_skipn with (l := skipn (k * offset) m) (n:= |subs|).
      setoid_rewrite . eauto.
  Qed.


  Definition encodeFinalConstraint' := encodeFinalConstraint (steps *llength).
  Lemma encodeFinalConstraint_encodesPredicate' : encodesPredicateAt (steps * llength) llength encodeFinalConstraint' ( m satFinal offset llength final m).
  Proof.
    apply encodeFinalConstraint_encodesPredicate.
  Qed.


  Definition encodeTableau := encodeListAt 0 init encodeWindowsInAllLines encodeFinalConstraint'.
  Lemma encodeTableau_encodesPredicate :
    encodesPredicateAt 0 (S steps * llength) encodeTableau
    ( m projVars 0 llength m = init
       ( i, 0 i < steps valid offset width windows (projVars (i * llength) llength m) (projVars (S i * llength) llength m))
       satFinal offset llength final (projVars (steps * llength) llength m)).
  Proof.
    specialize (encodesPredicate_and (encodeListAt_encodesPredicate 0 init) encodeWindowsInAllLines_encodesPredicate) as H.
    encodesPredicateAt_comp_simp H.
    specialize (encodesPredicate_and H encodeFinalConstraint_encodesPredicate') as .
    clear H.
    encodesPredicateAt_comp_simp .
    unfold encodeTableau.
    rewrite !Nat.sub_0_r in . cbn [Nat.add] in .

    eapply encodesPredicateAt_extensional; [ clear | apply ].
    intros m .
    setoid_rewrite projVars_comp. rewrite !Nat.min_l by nia.
    setoid_rewrite Nat.add_0_r.
    split.
    - intros ( & & ).
      repeat split; [easy | | easy].
      intros. rewrite !Nat.min_l by nia. rewrite !projVars_comp. rewrite !Nat.min_l by nia.
      rewrite !Nat.add_0_r. now apply .
    - rewrite !and_assoc. intros ( & & ). split; [easy |split;[ | easy]].
      intros. specialize ( i H). rewrite !Nat.min_l in by nia.
      rewrite !projVars_comp in . rewrite !Nat.min_l in by nia.
      rewrite !Nat.add_0_r in . apply .
  Qed.


  Lemma relpower_valid_to_assignment n x y:
    relpower (valid offset width windows) n x y |x| = llength
     m, |m| = (S n * llength) projVars 0 llength m = x projVars (n * llength) llength m = y
       ( i, 0 i < n valid offset width windows (projVars (i * llength) llength m) (projVars (S i * llength) llength m)).
  Proof.
    induction 1.
    - cbn. exists a. rewrite projVars_all; [ | lia]. easy.
    - intros. specialize (valid_length_inv H) as . rewrite in ; symmetry in .
      apply IHrelpower in as (m & & & & ). clear IHrelpower.
      exists (a m). repeat split.
      + rewrite app_length. lia.
      + rewrite projVars_app1; easy.
      + cbn. rewrite projVars_app3; easy.
      + intros i . destruct i.
        * cbn. rewrite Nat.add_0_r. setoid_rewrite projVars_app2 at 2; [ | easy].
          rewrite !projVars_app1; [ | easy]. rewrite . apply H.
        * assert (0 i < n) as by lia. apply in ; clear .
          cbn. rewrite !projVars_app3; [ | easy | easy]. apply .
  Qed.


  Lemma reduction_wf : FSAT encodeTableau sf, relpower (valid offset width windows) steps init sf satFinal offset llength final sf.
  Proof with (try (solve [rewrite explicitAssignment_length; cbn; nia | cbn; lia])).
    specialize (encodeTableau_encodesPredicate) as . split; intros.
    - destruct H as (a & H). apply in H as ( & & ).
      exists (projVars (steps * llength) llength (explicitAssignment a 0 (S steps * llength))).
      split; [ | apply ]. rewrite . clear .

      cbn. rewrite projVars_length... rewrite explicitAssignment_app at 1... rewrite projVars_app1...
      rewrite Nat.add_comm. rewrite explicitAssignment_app, projVars_app2, projVars_all...

      apply relpower_relpowerRev. induction steps.
      + cbn. constructor.
      + econstructor.
        * apply IHn. intros. specialize ( i). clear IHn.
          replace (S (S n) * llength) with (i * llength + (llength + (S n - i) * llength)) in at 1 by nia.
          replace (S (S n) * llength) with (S i * llength + (llength + (n - i) * llength)) in by nia.
          rewrite explicitAssignment_app, projVars_app2 in ...
          rewrite explicitAssignment_app, projVars_app1 in ...
          rewrite explicitAssignment_app, projVars_app2 in ...
          rewrite explicitAssignment_app, projVars_app1 in ...

          replace (S n * llength) with (i * llength + (llength + (n - i) * llength)) at 1 by nia.
          replace (S n * llength) with (S i * llength + (llength + (n - S i) * llength)) by nia.
          rewrite explicitAssignment_app, projVars_app2...
          rewrite explicitAssignment_app, projVars_app1...
          rewrite explicitAssignment_app, projVars_app2...
          rewrite explicitAssignment_app, projVars_app1...
          now apply .
        * specialize ( n). clear IHn.
          cbn in . rewrite Nat.add_comm in . setoid_rewrite Nat.add_comm in at 2. setoid_rewrite Nat.add_assoc in at 1.
          rewrite explicitAssignment_app, projVars_app2 in ... cbn in .
          rewrite explicitAssignment_app, projVars_app1 in ...
          rewrite explicitAssignment_app, projVars_app2, projVars_all in ... cbn in . now apply .
    - destruct H as (sf & & ). unfold encodeTableau in *.
      specialize (relpower_valid_to_assignment eq_refl) as (expAssgn & & & & ).
      destruct (expAssgn_to_assgn 0 expAssgn) as (a & ).
      exists a. apply . clear .
      apply expAssgn_to_assgn_inv in . rewrite in . rewrite . easy.
  Qed.

End fixInstance.

Definition BinaryPR_wf_dec (bpr : BinaryPR) :=
  leb 1 (width bpr)
  && leb 1 (offset bpr)
  && Nat.eqb (Nat.modulo (width bpr) (offset bpr)) 0
  && leb (width bpr) (|init bpr|)
  && forallb (PRWin_of_size_dec (width bpr)) (windows bpr)
  && Nat.eqb (Nat.modulo (|init bpr|) (offset bpr)) 0.

Lemma BinaryPR_wf_dec_correct (bpr : BinaryPR) : BinaryPR_wf_dec bpr = true BinaryPR_wellformed bpr.
Proof.
  unfold BinaryPR_wf_dec, BinaryPR_wellformed. rewrite !andb_true_iff, !and_assoc.
  rewrite !leb_iff. rewrite !(reflect_iff _ _ (Nat.eqb_spec _ _ )).
  rewrite !forallb_forall.
  setoid_rewrite PRWin_of_size_dec_correct.
  split; intros; repeat match goal with [H : _ _ |- _] destruct H end;
  repeat match goal with [ |- _ _ ] split end; try easy.
  - apply Nat.mod_divide in as (k & ); [ | lia].
    exists k; split; [ | apply ]. destruct k; cbn in *; lia.
  - apply Nat.mod_divide in as (k & ); [ | lia]. exists k; apply .
  - apply Nat.mod_divide; [ lia | ]. destruct as (k & _ & ). exists k; apply .
  - apply Nat.mod_divide; [ lia | ]. apply .
Qed.


Definition trivialNoInstance := 0 ¬0.
Lemma trivialNoInstance_isNoInstance : not (FSAT trivialNoInstance).
Proof.
  intros (a & H). unfold satisfies in H. cbn in H.
  destruct (evalVar a 0); cbn in H; congruence.
Qed.


Definition reduction (bpr : BinaryPR) := if BinaryPR_wf_dec bpr then encodeTableau bpr else trivialNoInstance.

Lemma BinaryPR_to_FSAT (bpr : BinaryPR) : BinaryPRLang bpr FSAT (reduction bpr).
Proof.
  split.
  - intros ( & ). unfold reduction. rewrite (proj2 (BinaryPR_wf_dec_correct _) ).
    now apply reduction_wf.
  - intros H. unfold reduction in H. destruct (BinaryPR_wf_dec) eqn:.
    + apply BinaryPR_wf_dec_correct in . apply reduction_wf in H; easy.
    + now apply trivialNoInstance_isNoInstance in H.
Qed.


extraction

From Undecidability.L.Tactics Require Import LTactics GenEncode.
From Undecidability.L.Complexity Require Import PolyBounds.
From Undecidability.L.Datatypes Require Import LProd LOptions LBool LSum LLNat LLists.
From Undecidability.L.Functions Require Import EqBool.

listAnd
Definition c__listAnd := 12.
Definition listAnd_time (l : list formula) := (|l| + 1) * c__listAnd.
Instance term_listAnd : computableTime' listAnd ( l _ (listAnd_time l, tt)).
Proof.
  extract. solverec. all: unfold listAnd_time, c__listAnd; solverec.
Qed.


Lemma listAnd_varsIn p l: ( f, f l formula_varsIn p f) formula_varsIn p (listAnd l).
Proof.
  unfold formula_varsIn. intros H; induction l; cbn.
  - intros v . inv .
  - intros v . inv ; [eapply H; eauto | eapply IHl; eauto].
Qed.


Lemma listAnd_size l : formula_size (listAnd l) sumn (map formula_size l) + |l| + 1.
Proof.
  induction l; cbn; [lia | rewrite IHl; lia].
Qed.


listOr
Definition c__listOr := 12.
Definition listOr_time (l : list formula) := (|l| + 1) * c__listOr.
Instance term_listOr : computableTime' listOr ( l _ (listOr_time l, tt)).
Proof.
  extract. solverec. all: unfold listOr_time, c__listOr; solverec.
Qed.


Lemma listOr_varsIn p l: ( f, f l formula_varsIn p f) formula_varsIn p (listOr l).
Proof.
  unfold formula_varsIn. intros H; induction l; cbn.
  - intros v . inv . inv .
  - intros v . inv ; [eapply H; eauto | eapply IHl; eauto].
Qed.


Lemma listOr_size l : formula_size (listOr l) sumn (map formula_size l) + |l| + 2.
Proof.
  induction l; cbn; [lia | rewrite IHl; lia].
Qed.


tabulate_step
Definition c__tabulateStep := 13 + c__add1.
Definition tabulate_step_time (step n : ) := n * (add_time step + c__tabulateStep) + c__tabulateStep.
Instance term_tabulate_step : computableTime' tabulate_step ( step _ (5, s _ (1, n _ (tabulate_step_time step n, tt)))).
Proof.
  extract. solverec.
  all: unfold tabulate_step_time, c__tabulateStep; solverec.
Qed.


Definition poly__tabulateStep n := n * ((n + 1) * c__add + c__tabulateStep) + c__tabulateStep.
Lemma tabulate_step_time_bound step n : tabulate_step_time step n poly__tabulateStep (size (enc step) + size (enc n)).
Proof.
  unfold tabulate_step_time. rewrite size_nat_enc_r with (n := n) at 1.
  unfold add_time. rewrite size_nat_enc_r with (n := step) at 1.
  unfold poly__tabulateStep; solverec.
Qed.

Lemma tabulate_step_poly : monotonic poly__tabulateStep inOPoly poly__tabulateStep.
Proof.
  unfold poly__tabulateStep; split; smpl_inO.
Qed.


Lemma tabulate_step_length step s n: |tabulate_step step s n| = n.
Proof.
  revert step s. induction n; cbn; intros; [lia | rewrite IHn; lia].
Qed.


tabulate_formula
Definition c__tabulateFormula := 8.
Definition tabulate_formula_time (s step n : ) (tf : ) := tabulate_step_time step n + map_time tf (tabulate_step step s n) + c__tabulateFormula.
Instance term_tabulate_formula : computableTime' tabulate_formula ( s _ (1, step _ (1, n _ (1, t tf (tabulate_formula_time s step n (callTime tf), tt))))).
Proof.
  extract. solverec.
  unfold tabulate_formula_time, c__tabulateFormula; solverec.
Qed.


Lemma formula_varsIn_monotonic ( : Prop) : ( n, n n) f, formula_varsIn f formula_varsIn f.
Proof.
  intros H f. unfold formula_varsIn. eauto.
Qed.


Lemma tabulate_formula_varsIn s step n t (g : ):
  ( start, formula_varsIn ( n n < g start) (t start))
   monotonic g
   f, f tabulate_formula s step n t formula_varsIn ( v v < g (s + step * (n -1))) f.
Proof.
  intros H f Hel. unfold tabulate_formula in Hel. apply in_map_iff in Hel as (i & & Hel).
  apply in_tabulate_step_iff in Hel as (i' & & ).
  eapply formula_varsIn_monotonic.
  2: apply H.
  cbn. intros n' Hn'. unfold monotonic in . specialize ( (s + step * i') (s + step * (n -1)) ltac:(nia)). nia.
Qed.


Lemma tabulate_formula_length s step n t : |tabulate_formula s step n t| = n.
Proof.
  revert s step. induction n; cbn; intros; [lia | ].
  unfold tabulate_formula in IHn. now rewrite IHn.
Qed.


encodeLiteral
Definition c__encodeLiteral := 6.
Instance term_encodeLiteral : computableTime' encodeLiteral ( n _ (1, b _ (c__encodeLiteral, tt))).
Proof.
  extract. solverec. all: unfold c__encodeLiteral; solverec.
Qed.


Lemma encodeLiteral_size v b : formula_size (encodeLiteral v b) 2.
Proof.
  unfold encodeLiteral. destruct b; cbn; lia.
Qed.


Ltac varsIn_inv :=
  repeat match goal with
    | [ H: formula_varsIn ?f |- _] inv H
    | [ H: varInFormula ?v ?f|- _] inv H
  end.

Lemma encodeLiteral_varsIn v b : formula_varsIn ( n n = v) (encodeLiteral v b).
Proof.
  unfold encodeLiteral. destruct b; cbn; intros v' H; varsIn_inv; lia.
Qed.


encodeListAt
Definition c__encodeListAt := c__encodeLiteral + c__add1 + add_time 1 + 15.
Definition encodeListAt_time (l : list bool) := (|l| + 1) * c__encodeListAt.
Instance term_encodeListAt : computableTime' encodeListAt ( s _ (5, l _ (encodeListAt_time l, tt))).
Proof.
  extract. solverec.
  all: unfold encodeListAt_time, c__encodeListAt; solverec.
Qed.


Definition poly__encodeListAt n := (n + 1) * c__encodeListAt.
Lemma encodeListAt_time_bound l : encodeListAt_time l poly__encodeListAt (size (enc l)).
Proof.
  unfold encodeListAt_time. rewrite list_size_length.
  unfold poly__encodeListAt; solverec.
Qed.

Lemma encodeListAt_poly : monotonic poly__encodeListAt inOPoly poly__encodeListAt.
Proof.
  unfold poly__encodeListAt; split; smpl_inO.
Qed.


Lemma encodeListAt_varsIn start l: formula_varsIn ( n n start n < start + |l|) (encodeListAt start l).
Proof.
  revert start; induction l; cbn; intros.
  - intros v H; varsIn_inv.
  - intros v H. inv H.
    + apply encodeLiteral_varsIn in . lia.
    + apply IHl in . lia.
Qed.


Lemma encodeListAt_size start l : formula_size (encodeListAt start l) 3 * (|l|) + 1.
Proof.
  revert start; induction l; cbn -[Nat.mul]; intros; [lia | rewrite IHl]. rewrite encodeLiteral_size. lia.
Qed.


encodeWindowAt
Definition c__encodeWindowAt := FlatPR.cnst_prem + FlatPR.cnst_conc + 13.
Definition encodeWindowAt_time (win : PRWin bool) := encodeListAt_time (prem win) + encodeListAt_time (conc win) + c__encodeWindowAt.
Instance term_encodeWindowAt : computableTime' encodeWindowAt ( _ (1, _ (1, win _ (encodeWindowAt_time win, tt)))).
Proof.
  extract. solverec.
  unfold encodeWindowAt_time, c__encodeWindowAt; solverec.
Qed.


Definition poly__encodeWindowAt k := (k + 1) * c__encodeListAt * 2 + c__encodeWindowAt.
Lemma encodeWindowAt_time_bound win k: PRWin_of_size win k encodeWindowAt_time win poly__encodeWindowAt k.
Proof.
  unfold PRWin_of_size. intros [ ].
  unfold encodeWindowAt_time. unfold encodeListAt_time. rewrite , .
  unfold poly__encodeWindowAt; solverec.
Qed.

Lemma encodeWindowAt_poly : monotonic poly__encodeWindowAt inOPoly poly__encodeWindowAt.
Proof.
  unfold poly__encodeWindowAt; split; smpl_inO.
Qed.


Lemma encodeWindowAt_varsIn k win : PRWin_of_size win k formula_varsIn ( n (n n < + k) (n n < + k)) (encodeWindowAt win).
Proof.
  intros [ ]. unfold encodeWindowAt. intros v H. inv H.
  - apply encodeListAt_varsIn in . lia.
  - apply encodeListAt_varsIn in . lia.
Qed.


Lemma encodeWindowAt_size k win : PRWin_of_size win k formula_size (encodeWindowAt win) 6 * k + 3.
Proof.
  intros [ ]. unfold encodeWindowAt. cbn -[Nat.mul]. rewrite !encodeListAt_size. rewrite , . nia.
Qed.


encodeWindowsAt
Definition c__encodeWindowsAt := 4 + c__windows.
Definition encodeWindowsAt_time (bpr : BinaryPR) ( : ) := map_time encodeWindowAt_time (windows bpr) + listOr_time (map (encodeWindowAt ) (windows bpr)) + c__encodeWindowsAt.
Instance term_encodeWindowsAt : computableTime' encodeWindowsAt ( bpr _ (1, _ (1, _ (encodeWindowsAt_time bpr , tt)))).
Proof.
  extract. solverec.
  unfold encodeWindowsAt_time, c__encodeWindowsAt; solverec.
Qed.


Definition poly__encodeWindowsAt n := n * (poly__encodeWindowAt n + c__map) + c__map + (n + 1) * c__listOr + c__encodeWindowsAt.
Lemma encodeWindowsAt_time_bound bpr :
  BinaryPR_wellformed bpr
   encodeWindowsAt_time bpr poly__encodeWindowsAt (size (enc bpr)).
Proof.
  intros H. unfold encodeWindowsAt_time.
  rewrite map_time_mono. 2: { intros win . apply H in . instantiate (1 := _ _). cbn.
    rewrite encodeWindowAt_time_bound by apply . reflexivity.
  }
  rewrite map_time_const.
  unfold listOr_time. rewrite map_length.
  poly_mono encodeWindowAt_poly. 2: { rewrite size_nat_enc_r. instantiate (1 := size (enc bpr)). rewrite BinaryPR_enc_size. lia. }
  rewrite list_size_length. replace_le (size (enc (windows bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
  unfold poly__encodeWindowsAt. solverec.
Qed.

Lemma encodeWindowsAt_poly : monotonic poly__encodeWindowsAt inOPoly poly__encodeWindowsAt.
Proof.
  unfold poly__encodeWindowsAt; split; smpl_inO; apply encodeWindowAt_poly.
Qed.


Lemma encodeWindowsAt_varsIn bpr :
  BinaryPR_wellformed bpr
   formula_varsIn ( n (n n < + width bpr) (n n < + width bpr)) (encodeWindowsAt bpr ).
Proof.
  intros (_ & _ & _ & _ & & _).
  unfold encodeWindowsAt. apply listOr_varsIn.
  intros f (win & & Hel)%in_map_iff. apply encodeWindowAt_varsIn, , Hel.
Qed.


Lemma encodeWindowsAt_size bpr :
  BinaryPR_wellformed bpr
   formula_size (encodeWindowsAt bpr ) |windows bpr| * (6 * width bpr + 4) + 2.
Proof.
  intros (_ &_ & _ & _ & & _).
  unfold encodeWindowsAt. rewrite listOr_size.
  rewrite sumn_map_mono. 2: { intros f (win & & Hel)%in_map_iff. instantiate (1 := _ _); cbn -[formula_size].
    rewrite encodeWindowAt_size by apply , Hel. reflexivity.
  }
  rewrite sumn_map_const, !map_length. nia.
Qed.


encodeWindowsInLine'
Definition c__encodeWindowsInLineP := c__width + c__sub1 + 3 * c__offset + 2 * c__add1 + 24.
Fixpoint encodeWindowsInLineP_time (bpr : BinaryPR) (stepindex l startA startB : ) :=
  match stepindex with
  | 0 0
  | S stepi encodeWindowsAt_time bpr startA startB + sub_time l (offset bpr) + add_time startA + add_time startB + encodeWindowsInLineP_time bpr stepi (l - offset bpr) (startA + offset bpr) (startB + offset bpr)
  end + ltb_time l (width bpr) + c__encodeWindowsInLineP.
Instance term_encodeWindowsInLine': computableTime' encodeWindowsInLine' ( bpr _ (1, stepi _ (5, l _ (1, _ (5, _ (encodeWindowsInLineP_time bpr stepi l , tt)))))).
Proof.
  extract.
  solverec. all: unfold c__encodeWindowsInLineP; solverec.
Qed.


we first bound the components that can be accounted for by the pr instance and bound the start indices inductively; we have the invariant that start' <= start + stepindex * offset for every start' obtained by recursion
Definition poly__encodeWindowsInLineP1 n := poly__encodeWindowsAt n + (n + 1) * c__sub + (c__leb * (1 + n) + c__ltb) + c__encodeWindowsInLineP.
Lemma encodeWindowsInLineP_time_bound1 bpr stepindex l startA startB :
  BinaryPR_wellformed bpr
   encodeWindowsInLineP_time bpr stepindex l startA startB (stepindex + 1) * poly__encodeWindowsInLineP1 (size (enc bpr))
    + stepindex * (stepindex * (offset bpr) + startA + stepindex * (offset bpr) + startB + 2) * c__add.
Proof.
  intros H.
  revert l startA startB. unfold encodeWindowsInLineP_time. induction stepindex; intros.
  - unfold ltb_time, leb_time. rewrite Nat.le_min_r.
    rewrite size_nat_enc_r with (n := width bpr) at 1.
    replace_le (size (enc (width bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; cbn; lia).
    unfold poly__encodeWindowsInLineP1. leq_crossout.
  - rewrite IHstepindex. clear IHstepindex.
    rewrite encodeWindowsAt_time_bound by apply H.
    unfold sub_time. rewrite Nat.le_min_r.
    unfold ltb_time, leb_time. rewrite Nat.le_min_r.
    rewrite size_nat_enc_r with (n := offset bpr) at 1.
    replace_le (size (enc (offset bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; cbn; lia).
    rewrite size_nat_enc_r with (n := width bpr) at 1.
    replace_le (size (enc (width bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; cbn; lia).
    unfold poly__encodeWindowsInLineP1. unfold add_time. nia.
Qed.

Lemma encodeWindowsInLineP1_poly : monotonic poly__encodeWindowsInLineP1 inOPoly poly__encodeWindowsInLineP1.
Proof.
  unfold poly__encodeWindowsInLineP1; split; smpl_inO; apply encodeWindowsAt_poly.
Qed.


in a second step, we also bound the numbers by their encoding size
Definition poly__encodeWindowsInLine' n := (n + 1) * poly__encodeWindowsInLineP1 n + n * (n * n + n + 1) * c__add * 2.
Lemma encodeWindowsInLineP_time_bound bpr stepindex l startA startB :
  BinaryPR_wellformed bpr encodeWindowsInLineP_time bpr stepindex l startA startB poly__encodeWindowsInLine' (size (enc bpr) + size (enc stepindex) + size (enc startA) + size (enc startB)).
Proof.
  intros H. rewrite encodeWindowsInLineP_time_bound1 by easy.
  rewrite size_nat_enc_r with (n := stepindex) at 1 2 3 4.
  rewrite size_nat_enc_r with (n := offset bpr) at 1 2.
  replace_le (size (enc (offset bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
  rewrite size_nat_enc_r with (n := startA) at 1.
  rewrite size_nat_enc_r with (n := startB) at 1.
  pose (g := size (enc bpr) + size (enc stepindex) + size (enc startA) + size (enc startB)).
  poly_mono encodeWindowsInLineP1_poly. 2: { instantiate (1 := g). subst g; lia. }
  replace_le (size (enc stepindex)) with g by (subst g; lia) at 1 2 3 4.
  replace_le (size (enc bpr)) with g by (subst g; lia) at 1 2.
  replace_le (size (enc startA)) with g by (subst g; lia) at 1. replace_le (size (enc startB)) with g by (subst g; lia) at 1.
  fold g.
  unfold poly__encodeWindowsInLine'. nia.
Qed.

Lemma encodeWindowsInLineP_poly : monotonic poly__encodeWindowsInLine' inOPoly poly__encodeWindowsInLine'.
Proof.
  unfold poly__encodeWindowsInLine'; split; smpl_inO; apply encodeWindowsInLineP1_poly.
Qed.


Lemma encodeWindowsInLineP_varsIn bpr stepi l startA startB :
  BinaryPR_wellformed bpr
   formula_varsIn ( n (n startA n < startA + l) (n startB n < startB + l)) (encodeWindowsInLine' bpr stepi l startA startB).
Proof.
  intros . revert startA startB l. induction stepi; cbn; intros.
  - destruct width; [ | destruct Nat.leb]; cbn; intros a ; inv .
  - destruct width eqn:; [destruct ; lia | ].
    destruct leb eqn:.
    + apply Nat.leb_le in . intros a H. inv H.
    + apply leb_complete_conv in . intros a H. inv H.
      * apply encodeWindowsAt_varsIn in ; [ | apply ]. rewrite ! in . nia.
      * apply IHstepi in . nia.
Qed.


Lemma encodeWindowsInLineP_size bpr stepi l startA startB :
  BinaryPR_wellformed bpr formula_size (encodeWindowsInLine' bpr stepi l startA startB) stepi * (|windows bpr| * (6 * width bpr + 4) + 3) + 1.
Proof.
  intros . revert l startA startB. induction stepi; cbn -[Nat.mul Nat.add]; intros.
  - destruct width; [ | destruct leb]; cbn; lia.
  - destruct width eqn:; [ destruct ; lia | ].
    destruct leb eqn:.
    + cbn. nia.
    + apply leb_complete_conv in . cbn [formula_size]. rewrite IHstepi. clear IHstepi.
      rewrite encodeWindowsAt_size by apply . nia.
Qed.


encodeWindowsInLine
Definition c__encodeWindowsInLine := 3 * c__init + 3 * c__length + c__add1 + 13.
Definition encodeWindowsInLine_time (bpr : BinaryPR) (s : ) :=
  3 * c__length * (| init bpr |) + add_time s +
  encodeWindowsInLineP_time bpr (| init bpr |) (| init bpr |) s (s + (| init bpr |)) + c__encodeWindowsInLine.
Instance term_encodeWindowsInLine :computableTime' encodeWindowsInLine ( bpr _ (1, s _ (encodeWindowsInLine_time bpr s, tt))).
Proof.
  extract. solverec.
  unfold encodeWindowsInLine_time, c__encodeWindowsInLine. solverec.
Qed.


Definition poly__encodeWindowsInLine n := 3 * c__length * n + (n + 1) * c__add + poly__encodeWindowsInLine' (n * (c__natsizeS + 2) + c__natsizeO) + c__encodeWindowsInLine.
Lemma encodeWindowsInLine_time_bound bpr s :
  BinaryPR_wellformed bpr
   encodeWindowsInLine_time bpr s poly__encodeWindowsInLine (size (enc bpr) + size (enc s)).
Proof.
  intros H. unfold encodeWindowsInLine_time.
  rewrite encodeWindowsInLineP_time_bound by easy.
  poly_mono encodeWindowsInLineP_poly.
  2: { setoid_rewrite size_nat_enc at 3. rewrite list_size_enc_length, list_size_length.
       rewrite size_nat_enc_r with (n := s) at 2.
       replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
       instantiate (1 := (size (enc bpr) + size (enc s)) * ( c__natsizeS + 2) + c__natsizeO). lia.
  }
  rewrite list_size_length. replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
  unfold add_time. rewrite size_nat_enc_r with (n := s) at 1.
  unfold poly__encodeWindowsInLine; solverec.
Qed.

Lemma encodeWindowsInLine_poly : monotonic poly__encodeWindowsInLine inOPoly poly__encodeWindowsInLine.
Proof.
  unfold poly__encodeWindowsInLine; split; smpl_inO.
  - apply encodeWindowsInLineP_poly.
  - apply inOPoly_comp; smpl_inO; apply encodeWindowsInLineP_poly.
Qed.


Lemma encodeWindowsInLine_varsIn bpr s :
  BinaryPR_wellformed bpr
   formula_varsIn ( n (n s n < s + 2 * (|init bpr|))) (encodeWindowsInLine bpr s).
Proof.
  intros H. eapply formula_varsIn_monotonic.
  2: { unfold encodeWindowsInLine. apply encodeWindowsInLineP_varsIn, H. }
  cbn. intros n. lia.
Qed.


Lemma encodeWindowsInLine_size bpr s:
  BinaryPR_wellformed bpr
   formula_size (encodeWindowsInLine bpr s) (|init bpr|) * (|windows bpr| * (6 * width bpr + 4) + 3) +1.
Proof. apply encodeWindowsInLineP_size. Qed.

encodeWindowsInAllLines
Definition c__encodeWindowsInAllLines := c__init + c__length + c__steps + 5.
Definition encodeWindowsInAllLines_time (bpr : BinaryPR) := c__length * (|init bpr|) + tabulate_formula_time 0 (|init bpr|) (steps bpr) (encodeWindowsInLine_time bpr) + listAnd_time (tabulate_formula 0 (|init bpr|) (steps bpr) (encodeWindowsInLine bpr)) + c__encodeWindowsInAllLines.
Instance term_encodeWindowsInAllLines : computableTime' encodeWindowsInAllLines ( bpr _ (encodeWindowsInAllLines_time bpr, tt)).
Proof.
  extract. solverec.
  unfold encodeWindowsInAllLines_time, c__encodeWindowsInAllLines. nia.
Qed.


Fact nat_size_mul a b: size (enc (a * b)) size (enc a) * size (enc b).
Proof.
  rewrite !size_nat_enc. unfold c__natsizeS. nia.
Qed.


Definition poly__encodeWindowsInAllLines n :=
  c__length * n + (poly__tabulateStep n + (n * (poly__encodeWindowsInLine (n + n * n) + c__map) + c__map)) + c__tabulateFormula + (n + 1) * c__listAnd + c__encodeWindowsInAllLines.
Lemma encodeWindowsInAllLines_time_bound bpr : BinaryPR_wellformed bpr encodeWindowsInAllLines_time bpr poly__encodeWindowsInAllLines (size (enc bpr)).
Proof.
  intros H. unfold encodeWindowsInAllLines_time.
  rewrite list_size_length at 1. replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
  unfold tabulate_formula_time. rewrite tabulate_step_time_bound.
  poly_mono tabulate_step_poly. 2: { rewrite list_size_enc_length. instantiate (1 := size (enc bpr)). rewrite BinaryPR_enc_size; lia. }
  rewrite map_time_mono.
  2: { intros start Hel. instantiate (1 := _ _). cbn -[Nat.add Nat.mul].
       rewrite encodeWindowsInLine_time_bound by apply H.
       apply in_tabulate_step_iff in Hel as (i & & ).
       poly_mono encodeWindowsInLine_poly.
       2: { rewrite nat_size_le with (a := 0 + (|init bpr|) * i). 2: { instantiate (1 := (|init bpr|) * steps bpr). nia. }
            rewrite nat_size_mul. rewrite list_size_enc_length.
            replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
            replace_le (size (enc (steps bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
            reflexivity.
       }
       reflexivity.
  }
  rewrite map_time_const.
  rewrite tabulate_step_length.
  unfold listAnd_time, tabulate_formula. rewrite map_length, tabulate_step_length.
  rewrite size_nat_enc_r with (n := steps bpr) at 1 2. replace_le (size (enc (steps bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
  unfold poly__encodeWindowsInAllLines. nia.
Qed.

Lemma encodeWindowsInAllLines_poly : monotonic poly__encodeWindowsInAllLines inOPoly poly__encodeWindowsInAllLines.
Proof.
  unfold poly__encodeWindowsInAllLines; split; smpl_inO; try apply inOPoly_comp; smpl_inO; first [apply tabulate_step_poly | apply encodeWindowsInLine_poly].
Qed.


Lemma encodeWindowsInAllLines_varsIn bpr :
  BinaryPR_wellformed bpr
   formula_varsIn ( n n < (1 + steps bpr) * (|init bpr|)) (encodeWindowsInAllLines bpr).
Proof.
  intros H. unfold encodeWindowsInAllLines. apply listAnd_varsIn.
  intros f . destruct steps; [now cbn in | ].
  eapply tabulate_formula_varsIn in .
  2: { intros s. eapply formula_varsIn_monotonic. 2: apply encodeWindowsInLine_varsIn, H.
       intros n'. cbn. lia.
  }
  2: smpl_inO.
  cbn in . eapply formula_varsIn_monotonic. 2 : apply . intros n'. cbn.
  nia.
Qed.


Lemma encodeWindowsInAllLines_size bpr :
  BinaryPR_wellformed bpr
   formula_size (encodeWindowsInAllLines bpr) ((steps bpr) * ((|init bpr|) * (|windows bpr| * (6 * width bpr + 4) + 3) +2)) + 1.
Proof.
  intros H. unfold encodeWindowsInAllLines. rewrite listAnd_size.
  rewrite tabulate_formula_length.
  rewrite sumn_map_mono. 2: { intros f . unfold tabulate_formula in . apply in_map_iff in as (i & & ).
    instantiate (1 := _ _). cbn. rewrite encodeWindowsInLine_size by apply H. reflexivity.
  }
  rewrite sumn_map_const, tabulate_formula_length. nia.
Qed.


encodeSubstringInLine'
Definition c__encodeSubstringInLine' := c__length + 23 + c__sub1 + 2 * c__offset.
Fixpoint encodeSubstringInLineP_time (bpr : BinaryPR) (s : list bool) (stepindex l start : ) :=
 match stepindex with
  | 0 0
  | S stepi encodeListAt_time s + sub_time l (offset bpr) + c__add1 + add_time start + encodeSubstringInLineP_time bpr s stepi (l - offset bpr) (start + offset bpr)
 end + c__length * (|s|) + ltb_time l (|s|) + c__encodeSubstringInLine'.
Instance term_encodeSubstringInLine' : computableTime' encodeSubstringInLine' ( bpr _ (1, s _ (5, stepi _ (1, l _ (1, start _ (encodeSubstringInLineP_time bpr s stepi l start, tt)))))).
Proof.
  extract. solverec.
  all: unfold encodeSubstringInLineP_time, c__encodeSubstringInLine'. all: solverec.
Qed.


Definition poly__encodeSubstringInLineP1 n := poly__encodeListAt n + (n + 1) * c__sub + c__add1 + c__length * n + (c__leb * (1 + n) + c__ltb) + c__encodeSubstringInLine'.
Lemma encodeSubstringInLineP_time_bound1 bpr s stepindex l start :
  BinaryPR_wellformed bpr
   encodeSubstringInLineP_time bpr s stepindex l start (stepindex + 1) * poly__encodeSubstringInLineP1 (size (enc bpr) + size (enc s)) + stepindex * (stepindex * (offset bpr) + start + 1) * c__add.
Proof.
  intros H.
  revert l start. unfold encodeSubstringInLineP_time. induction stepindex; intros.
  - unfold ltb_time, leb_time. rewrite Nat.le_min_r.
    rewrite list_size_length.
    unfold poly__encodeSubstringInLineP1. nia.
  - rewrite IHstepindex. clear IHstepindex.
    rewrite encodeListAt_time_bound.
    unfold sub_time. rewrite Nat.le_min_r.
    unfold ltb_time, leb_time. rewrite Nat.le_min_r.
    rewrite list_size_length.
    rewrite size_nat_enc_r with (n := offset bpr) at 1.
    replace_le (size (enc (offset bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; cbn; lia).
    poly_mono encodeListAt_poly. 2: { instantiate (1 := size (enc bpr) + size (enc s)). lia. }
    unfold poly__encodeSubstringInLineP1. unfold add_time. nia.
Qed.

Lemma encodeSubstringInLineP_poly1 : monotonic poly__encodeSubstringInLineP1 inOPoly poly__encodeSubstringInLineP1.
Proof.
  unfold poly__encodeSubstringInLineP1; split; smpl_inO; apply encodeListAt_poly.
Qed.


Definition poly__encodeSubstringInLine' n := (n + 1) * poly__encodeSubstringInLineP1 n + n * (n * n + n + 1) * c__add.
Lemma encodeSubstringInLineP_time_bound bpr s stepindex l start :
  BinaryPR_wellformed bpr encodeSubstringInLineP_time bpr s stepindex l start poly__encodeSubstringInLine' (size (enc bpr) + size (enc s) + size (enc stepindex) + size (enc start)).
Proof.
  intros H. rewrite encodeSubstringInLineP_time_bound1 by apply H.
  pose (g := size (enc bpr) + size (enc s) + size (enc stepindex) + size (enc start)).
  poly_mono encodeSubstringInLineP_poly1. 2 : { instantiate (1 := g). subst g; lia. }
  rewrite size_nat_enc_r with (n := stepindex) at 1 2 3.
  rewrite size_nat_enc_r with (n := offset bpr). replace_le (size (enc (offset bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
  rewrite size_nat_enc_r with (n := start) at 1.
  replace_le (size (enc stepindex)) with g by (unfold g; lia) at 1 2 3.
  replace_le (size (enc bpr)) with g by (unfold g; lia) at 1.
  replace_le (size (enc start)) with g by (unfold g; lia) at 1.
  fold g. unfold poly__encodeSubstringInLine'. nia.
Qed.

Lemma encodeSubstringInLineP_poly : monotonic poly__encodeSubstringInLine' inOPoly poly__encodeSubstringInLine'.
Proof.
  unfold poly__encodeSubstringInLine'; split; smpl_inO; apply encodeSubstringInLineP_poly1.
Qed.


Lemma encodeSubstringInLineP_varsIn bpr s stepindex l start: formula_varsIn ( n n start n < start + l) (encodeSubstringInLine' bpr s stepindex l start).
Proof.
  revert l start. induction stepindex; cbn; intros.
  - destruct s; cbn.
    + intros a H. inv H.
    + destruct leb; intros a H; varsIn_inv.
  - destruct s; cbn.
    + intros a . inv ; [varsIn_inv | ].
      apply IHstepindex in . lia.
    + destruct leb eqn:; [intros a H; varsIn_inv | ].
      apply leb_complete_conv in .
      intros a H. inv H.
      * apply (@encodeListAt_varsIn start (b :: s)) in . cbn in . nia.
      * apply IHstepindex in . nia.
Qed.


Lemma encodeSubstringInLineP_size bpr s stepindex l start : formula_size (encodeSubstringInLine' bpr s stepindex l start) stepindex * (3 * |s| + 1) + 2.
Proof.
  revert l start. induction stepindex; cbn; intros.
  - destruct s; cbn; [lia | ]. destruct leb; cbn; lia.
  - destruct s; cbn.
    + rewrite IHstepindex. cbn. nia.
    + destruct leb eqn:; cbn -[Nat.add Nat.mul]; [lia | ]. rewrite IHstepindex.
      rewrite encodeListAt_size, encodeLiteral_size. cbn. nia.
Qed.


encodeSubstringInLine
Definition c__encodeSubstringInLine := 14.
Definition encodeSubstringInLine_time (bpr : BinaryPR) (s : list bool) (start l : ) := encodeSubstringInLineP_time bpr s l l start + c__encodeSubstringInLine.
Instance term_encodeSubstringInLine : computableTime' encodeSubstringInLine ( bpr _ (1, s _ (1, start _ (1, l _ (encodeSubstringInLine_time bpr s start l, tt))))).
Proof.
  extract. solverec. all: unfold encodeSubstringInLine_time, c__encodeSubstringInLine; solverec.
Qed.


Lemma encodeSubstringInLine_varsIn bpr s start l: formula_varsIn ( n n start n < start + l) (encodeSubstringInLine bpr s start l).
Proof.
  unfold encodeSubstringInLine. destruct s.
  - intros a H. inv H.
  - apply encodeSubstringInLineP_varsIn.
Qed.


Lemma encodeSubstringInLine_size bpr s start l : formula_size (encodeSubstringInLine bpr s start l) l * (3 * |s| + 1) + 2.
Proof.
  unfold encodeSubstringInLine. destruct s.
  - cbn. lia.
  - apply encodeSubstringInLineP_size.
Qed.


encodeFinalConstraint
Definition encodeFinalConstraint_step (bpr : BinaryPR) (start : ) (s : list bool) := encodeSubstringInLine bpr s start (|init bpr|).

Definition c__encodeFinalConstraintStep := c__init + c__length + 4.
Definition encodeFinalConstraint_step_time (bpr : BinaryPR) (start : ) (s : list bool) := c__length * (|init bpr|) + encodeSubstringInLine_time bpr s start (|init bpr|) + c__encodeFinalConstraintStep.
Instance term_encodeFinalConstraint_step : computableTime' encodeFinalConstraint_step ( bpr _ (1, start _ (1, s _ (encodeFinalConstraint_step_time bpr start s, tt)))).
Proof.
  extract. solverec.
  unfold encodeFinalConstraint_step_time, c__encodeFinalConstraintStep; solverec.
Qed.


Definition c__encodeFinalConstraint := c__final + 4.
Definition encodeFinalConstraint_time (bpr : BinaryPR) (start : ) := map_time (encodeFinalConstraint_step_time bpr start) (final bpr) + listOr_time (map (encodeFinalConstraint_step bpr start) (final bpr)) + c__encodeFinalConstraint.
Instance term_encodeFinalConstraint : computableTime' encodeFinalConstraint ( bpr _ (1, start _ (encodeFinalConstraint_time bpr start, tt))).
Proof.
  apply computableTimeExt with (x := bpr start listOr (map (encodeFinalConstraint_step bpr start) (final bpr))).
  1: easy.
  extract. solverec.
  unfold encodeFinalConstraint_time, c__encodeFinalConstraint; solverec.
Qed.


Definition poly__encodeFinalConstraint n := n * (c__length * n + poly__encodeSubstringInLine' (2 * n) + c__encodeSubstringInLine + c__encodeFinalConstraintStep + c__map) + c__map + (n + 1) * c__listOr + c__encodeFinalConstraint.
Lemma encodeFinalConstraint_time_bound bpr start :
  BinaryPR_wellformed bpr
   encodeFinalConstraint_time bpr start poly__encodeFinalConstraint (size (enc bpr) + size (enc start)).
Proof.
  intros H. unfold encodeFinalConstraint_time.
  rewrite map_time_mono.
  2: { intros l Hel. unfold encodeFinalConstraint_step_time, encodeSubstringInLine_time.
       instantiate (1 := _ _). cbn -[Nat.mul Nat.add].
       rewrite encodeSubstringInLineP_time_bound by apply H.
       rewrite list_size_length at 1. replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
       poly_mono encodeSubstringInLineP_poly.
       2: { instantiate (1 := 2 * (size (enc bpr) + size (enc start))).
            rewrite (list_el_size_bound Hel), list_size_enc_length.
            cbn. rewrite BinaryPR_enc_size at 2. lia.
       }
       reflexivity.
  }
  rewrite map_time_const, list_size_length. unfold listOr_time. rewrite map_length, list_size_length.
  replace_le (size (enc (final bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
  unfold poly__encodeFinalConstraint; nia.
Qed.

Lemma encodeFinalConstraint_poly : monotonic poly__encodeFinalConstraint inOPoly poly__encodeFinalConstraint.
Proof.
  unfold poly__encodeFinalConstraint; split; smpl_inO.
  - apply encodeSubstringInLineP_poly.
  - apply inOPoly_comp; smpl_inO; apply encodeSubstringInLineP_poly.
Qed.


Lemma encodeFinalConstraint_varsIn bpr start : formula_varsIn ( n n start n < start + (|init bpr|)) (encodeFinalConstraint bpr start).
Proof.
  unfold encodeFinalConstraint. apply listOr_varsIn.
  intros f (s & & Hel)%in_map_iff. apply encodeSubstringInLine_varsIn.
Qed.


Lemma encodeFinalConstraint_size bpr start :
  formula_size (encodeFinalConstraint bpr start) sumn (map ( s ((|init bpr|) * (3 * |s| + 1) + 2)) (final bpr)) + (|final bpr|) + 2.
Proof.
  unfold encodeFinalConstraint. rewrite listOr_size.
  rewrite map_map, sumn_map_mono.
  2: { intros x _. instantiate (1 := x _ ). cbn. rewrite encodeSubstringInLine_size. reflexivity. }
  rewrite map_length. lia.
Qed.


Definition c__encodeTableau := 2 * c__init + c__steps + c__mult1 + c__length + 11.
Definition encodeTableau_time (bpr : BinaryPR) := encodeListAt_time (init bpr) + encodeWindowsInAllLines_time bpr + c__length * (|init bpr|) + mult_time (steps bpr) (|init bpr|) + encodeFinalConstraint_time bpr (steps bpr * (|init bpr|)) + c__encodeTableau.
Instance term_encodeTableau : computableTime' encodeTableau ( bpr _ (encodeTableau_time bpr, tt)).
Proof.
  unfold encodeTableau, encodeFinalConstraint'. extract. solverec.
  unfold encodeTableau_time, c__encodeTableau. solverec.
Qed.


Definition poly__encodeTableau n := poly__encodeListAt n + poly__encodeWindowsInAllLines n + c__length * n + (n * n * c__mult + c__mult * (n + 1)) + poly__encodeFinalConstraint (n + n * n) + c__encodeTableau.
Lemma encodeTableau_time_bound bpr : BinaryPR_wellformed bpr encodeTableau_time bpr poly__encodeTableau (size (enc bpr)).
Proof.
  intros H. unfold encodeTableau_time.
  rewrite encodeListAt_time_bound. poly_mono encodeListAt_poly. 2: { instantiate (1:= size (enc bpr)). rewrite BinaryPR_enc_size; lia. }
  rewrite encodeWindowsInAllLines_time_bound by apply H.
  rewrite encodeFinalConstraint_time_bound by apply H.
  poly_mono encodeFinalConstraint_poly.
  2: { rewrite nat_size_mul. rewrite list_size_enc_length.
       replace_le (size (enc (steps bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
       replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
       reflexivity.
  }
  unfold mult_time. rewrite list_size_length at 1 2. rewrite size_nat_enc_r with (n := steps bpr).
  replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia) at 1 2.
  replace_le (size (enc (steps bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia) at 1 2.
  unfold poly__encodeTableau; nia.
Qed.

Lemma encodeTableau_poly : monotonic poly__encodeTableau inOPoly poly__encodeTableau.
Proof.
  unfold poly__encodeTableau; split; smpl_inO; try apply inOPoly_comp; smpl_inO; first [apply encodeListAt_poly | apply encodeWindowsInAllLines_poly | apply encodeFinalConstraint_poly].
Qed.


Lemma encodeTableau_varsIn bpr: BinaryPR_wellformed bpr formula_varsIn ( n n < (1 + steps bpr) * (|init bpr|)) (encodeTableau bpr).
Proof.
  intros H.
  unfold encodeWindowsInAllLines. intros a . inv .
  - inv .
    + apply encodeListAt_varsIn in . nia.
    + apply encodeWindowsInAllLines_varsIn in ; [ | apply H]. nia.
  - unfold encodeFinalConstraint' in . apply encodeFinalConstraint_varsIn in . nia.
Qed.


Lemma encodeTableau_size : {f : & ( bpr, BinaryPR_wellformed bpr formula_size (encodeTableau bpr) f (size (enc bpr))) monotonic f inOPoly f}.
Proof.
  eexists. split; [ | split].
  - intros bpr H. unfold encodeTableau. cbn.
    rewrite encodeListAt_size. rewrite encodeWindowsInAllLines_size by apply H.
    unfold encodeFinalConstraint'. rewrite encodeFinalConstraint_size.
    rewrite !list_size_length. rewrite sumn_map_mono.
    2: { intros s Hel. instantiate (1 := _ _). cbn -[Nat.mul Nat.add].
         rewrite list_size_length with (l := s). rewrite list_size_length.
         replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
         erewrite list_el_size_bound with (a := s) by apply Hel.
         replace_le (size (enc (final bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
         reflexivity.
    }
    rewrite sumn_map_const. rewrite list_size_length.
    replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
    replace_le (size (enc (final bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
    replace_le (size (enc (windows bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
    rewrite size_nat_enc_r with (n := width bpr).
    replace_le (size (enc (width bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
    rewrite size_nat_enc_r with (n := steps bpr).
    replace_le (size (enc (steps bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
    instantiate (1 := n _). cbn -[Nat.add Nat.mul]. generalize (size (enc bpr)). reflexivity.
  - smpl_inO.
  - smpl_inO.
Defined.


Lemma encodeTableau_enc_size : {f : & ( bpr, BinaryPR_wellformed bpr size (enc (encodeTableau bpr)) f (size (enc bpr))) inOPoly f monotonic f}.
Proof.
  destruct encodeTableau_size as (f' & & & ).
  eexists. split; [ | split].
  - intros bpr H. rewrite formula_enc_size_bound.
        rewrite by apply H. erewrite formula_varsIn_bound.
    2: { eapply formula_varsIn_monotonic. 2: apply encodeTableau_varsIn, H. intros n. cbn. apply Nat.lt_le_incl. }
    instantiate (1 := n _). cbn -[Nat.add Nat.mul].
    rewrite list_size_length. replace_le (size (enc (init bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
    rewrite size_nat_enc_r with (n := steps bpr). replace_le (size (enc (steps bpr))) with (size (enc bpr)) by (rewrite BinaryPR_enc_size; lia).
    generalize (size (enc bpr)). reflexivity.
  - smpl_inO.
  - smpl_inO.
Qed.


BinaryPR_wf_dec

Definition c__BinaryPRWfDec := 3 * c__leb2 + 4 * c__width + 3 * c__offset + 2 * 5 + 2 * c__init + 2 * c__length + c__windows + 32 + 4 * c__leb + 2 * c__eqbComp * size (enc 0).
Definition BinaryPR_wf_dec_time x := 2 * c__length * (|init x|) + leb_time (width x) (|init x|) + forallb_time (@FlatPR.PRWin_of_size_dec_time bool (width x)) (windows x) + modulo_time (|init x|) (offset x) + modulo_time (width x) (offset x) + c__BinaryPRWfDec.
Instance term_BinaryPR_wf_dec : computableTime' BinaryPR_wf_dec ( bpr _ (BinaryPR_wf_dec_time bpr, tt)).
Proof.
  extract. solverec.
  unfold BinaryPR_wf_dec_time, c__BinaryPRWfDec, leb_time. rewrite !eqbTime_le_r.
  do 2 rewrite Nat.le_min_l. leq_crossout.
Qed.


Definition c__BinaryPRWfDecBound := 2*(2 * c__length + c__leb + FlatPR.c__prwinOfSizeDecBound + c__forallb + 2 * c__moduloBound + c__BinaryPRWfDec).
Definition poly__BinaryPRWfDec n := (n*n + 2* n + 1) * c__BinaryPRWfDecBound.
Lemma BinaryPR_wf_dec_time_bound fpr :
  BinaryPR_wf_dec_time fpr poly__BinaryPRWfDec (size (enc fpr)).
Proof.
  unfold BinaryPR_wf_dec_time. rewrite leb_time_bound_l.
  rewrite !modulo_time_bound. rewrite list_size_enc_length.
  rewrite list_size_length.
  erewrite forallb_time_bound_env.
  2: {
    split; [intros | ].
    - specialize (FlatPR.PRWin_of_size_dec_time_bound (X := bool) y a) as .
      instantiate (2:= n (n + 1) * FlatPR.c__prwinOfSizeDecBound). nia.
    - smpl_inO.
  }
  rewrite list_size_length.
  replace_le (size(enc (windows fpr))) with (size (enc fpr)) by (rewrite BinaryPR_enc_size; lia).
  replace_le (size(enc (init fpr))) with (size (enc fpr)) by (rewrite BinaryPR_enc_size; lia).
  replace_le (size(enc (width fpr))) with (size (enc fpr)) by (rewrite BinaryPR_enc_size; lia).
  replace_le (size(enc(offset fpr))) with (size (enc fpr)) by (rewrite BinaryPR_enc_size; lia).
  unfold poly__BinaryPRWfDec, c__BinaryPRWfDecBound. nia.
Qed.

Lemma BinaryPR_wf_dec_poly : monotonic poly__BinaryPRWfDec inOPoly poly__BinaryPRWfDec.
Proof.
  unfold poly__BinaryPRWfDec; split; smpl_inO.
Qed.


reduction
Definition c__reduction := 4.
Definition reduction_time (bpr : BinaryPR) := (if BinaryPR_wf_dec bpr then encodeTableau_time bpr else 0) +BinaryPR_wf_dec_time bpr + c__reduction.
Instance term_reduction : computableTime' reduction ( bpr _ (reduction_time bpr, tt)).
Proof.
  extract. unfold reduction_time, c__reduction; solverec.
Qed.


full reduction statement
Theorem BinaryPR_to_FSAT_poly : reducesPolyMO (unrestrictedP BinaryPRLang) (unrestrictedP FSAT).
Proof.
  eapply reducesPolyMO_intro_unrestricted with (f := reduction).
  - exists ( n poly__encodeTableau n + poly__BinaryPRWfDec n + c__reduction).
    + eexists.
      eapply computesTime_timeLeq. 2: { apply term_reduction. }
      intros bpr _. cbn -[Nat.add Nat.mul]. split; [ | easy].
      unfold reduction_time. destruct BinaryPR_wf_dec eqn:.
      * apply BinaryPR_wf_dec_correct in . rewrite encodeTableau_time_bound, BinaryPR_wf_dec_time_bound by easy.
        generalize (size (enc bpr)). reflexivity.
      * rewrite BinaryPR_wf_dec_time_bound. lia.
    + smpl_inO; first [apply encodeTableau_poly | apply BinaryPR_wf_dec_poly].
    + smpl_inO; first [apply encodeTableau_poly | apply BinaryPR_wf_dec_poly].
    + destruct encodeTableau_enc_size as (f & & & ).
      exists ( n f n + size (enc trivialNoInstance)).
      * intros bpr. unfold reduction. destruct BinaryPR_wf_dec eqn:.
        -- apply BinaryPR_wf_dec_correct in . rewrite by apply . lia.
        -- lia.
      * smpl_inO.
      * smpl_inO.
  - apply BinaryPR_to_FSAT.
Qed.