val index_generate_blocks:
    #t:Type0
  -> len:size_pos
  -> max:nat
  -> n:pos{n <= max}
  -> f:(i:nat{i < max} -> unit -> unit & s:seq t{length s == len})
  -> i:nat{i < n * len}
  -> Lemma (Math.Lemmas.lemma_mult_le_right len n max;
           div_mul_lt len i max;
           let a_spec (i:nat{i <= max}) = unit in
           let _,s1 = generate_blocks len max n a_spec f () in
           let _,s2 = f (i / len) () in
           Seq.index s1 i == Seq.index s2 (i % len))
let rec index_generate_blocks #t len max n f i =
  assert (0 < n);
  let a_spec (i:nat{i <= max}) = unit in
  let _,s = generate_blocks #t len max (n-1) a_spec f () in
  let _,s' = f (n-1) () in
  let _,s1 = generate_blocks #t len max n a_spec f () in
  unfold_generate_blocks #t len max a_spec f () (n-1);
  Seq.Properties.lemma_split s1 (n * len - len);
  Seq.Properties.lemma_split (Seq.append s s') (n * len - len);
  Seq.lemma_eq_intro s1 (Seq.append s s');
  if i < (n-1) * len then
    begin
    Seq.lemma_index_app1 s s' i;
    index_generate_blocks len max (n-1) f i
    end
  else
    begin
    Seq.lemma_index_app2 s s' i;
    mod_prop len (n-1) i
    end
val index_generate_blocks:
    #t:Type0
  -> len:size_pos
  -> max:nat
  -> n:pos{n <= max}
  -> f:(i:nat{i < max} -> unit -> unit & s:seq t{length s == len})
  -> i:nat{i < n * len}
  -> Lemma (Math.Lemmas.lemma_mult_le_right len n max;
           div_mul_lt len i max;
           let a_spec (i:nat{i <= max}) = unit in
           let _,s1 = generate_blocks len max n a_spec f () in
           let _,s2 = f (i / len) () in
           Seq.index s1 i == Seq.index s2 (i % len))
let rec index_generate_blocks #t len max n f i =
assert (0 < n);
let a_spec (i: nat{i <= max}) = unit in
let _, s = generate_blocks #t len max (n - 1) a_spec f () in
let _, s' = f (n - 1) () in
let _, s1 = generate_blocks #t len max n a_spec f () in
unfold_generate_blocks #t len max a_spec f () (n - 1);
Seq.Properties.lemma_split s1 (n * len - len);
Seq.Properties.lemma_split (Seq.append s s') (n * len - len);
Seq.lemma_eq_intro s1 (Seq.append s s');
if i < (n - 1) * len
then
  (Seq.lemma_index_app1 s s' i;
    index_generate_blocks len max (n - 1) f i)
else
  (Seq.lemma_index_app2 s s' i;
    mod_prop len (n - 1) i)
Lib.Sequence.fst.checked
prims.fst.checked Lib.LoopCombinators.fsti.checked Lib.IntTypes.fsti.checked FStar.Seq.Properties.fsti.checked FStar.Seq.fst.checked FStar.Pervasives.Native.fst.checked FStar.Pervasives.fsti.checked FStar.Mul.fst.checked FStar.Math.Lemmas.fst.checked FStar.List.Tot.fst.checked FStar.Calc.fsti.checked
Lib.Sequence.fst
Lib.IntTypes.size_pos Prims.nat Prims.pos Prims.b2t Prims.op_LessThanOrEqual Prims.op_LessThan Prims.unit FStar.Pervasives.Native.tuple2 Lib.Sequence.seq Prims.eq2 Prims.int Prims.l_or Prims.op_GreaterThanOrEqual Prims.l_and Prims.op_GreaterThan Lib.IntTypes.max_size_t Lib.Sequence.length FStar.Mul.op_Star Prims.op_Subtraction Lib.Sequence.index_generate_blocks FStar.Seq.Base.lemma_index_app1 Prims.bool Lib.Sequence.mod_prop FStar.Seq.Base.lemma_index_app2 FStar.Seq.Base.lemma_eq_intro FStar.Seq.Base.append FStar.Seq.Properties.lemma_split Lib.Sequence.unfold_generate_blocks Prims.op_Multiply Lib.Sequence.generate_blocks Prims.pow2 Prims.eqtype Prims._assert
module Lib.Sequence

open FStar.Mul
open Lib.IntTypes
open Lib.LoopCombinators

#set-options "--z3rlimit 30 --max_fuel 0 --max_ifuel 0 --using_facts_from '-* +Prims +FStar.Pervasives +FStar.Math.Lemmas +FStar.Seq +Lib.IntTypes +Lib.Sequence'"

let index #a #len s n = Seq.index s n

let create #a len init = Seq.create #a len init

let concat #a #len0 #len1 s0 s1 = Seq.append s0 s1

let to_list #a s = Seq.seq_to_list s

let of_list #a l = Seq.seq_of_list #a l

let of_list_index #a l i =
  Seq.lemma_seq_of_list_index #a l i

let equal #a #len s1 s2 =
  forall (i:size_nat{i < len}).{:pattern (index s1 i); (index s2 i)} index s1 i == index s2 i

let eq_intro #a #len s1 s2 =
  assert (forall (i:nat{i < len}).{:pattern (Seq.index s1 i); (Seq.index s2 i)}
    index s1 i == index s2 i);
  Seq.lemma_eq_intro #a (to_seq s1) (to_seq s2)

let eq_elim #a #len s1 s2 =
  assert (forall (i:nat{i < len}).{:pattern (Seq.index s1 i); (Seq.index s2 i)}
    index s1 i == index s2 i);
  Seq.lemma_eq_elim #a s1 s2

let upd #a #len s n x = Seq.upd #a s n x

let member #a #len x l = Seq.count x l > 0

let sub #a #len s start n = Seq.slice #a s start (start + n)

let update_sub #a #len s start n x =
  let o =
    Seq.append
      (Seq.append (Seq.slice s 0 start) x)
      (Seq.slice s (start + n) (length s)) in
  Seq.lemma_eq_intro (Seq.slice o start (start + n)) x;
  o

let lemma_update_sub #a #len dst start n src res =
  let res1 = update_sub dst start n src in
  Seq.lemma_split (sub res 0 (start + n)) start;
  Seq.lemma_split (sub res1 0 (start + n)) start;
  Seq.lemma_split res (start + n);
  Seq.lemma_split res1 (start + n);
  Seq.lemma_eq_intro res (update_sub dst start n src)

let lemma_concat2 #a len0 s0 len1 s1 s =
  Seq.Properties.lemma_split s len0;
  Seq.Properties.lemma_split (concat s0 s1) len0;
  Seq.lemma_eq_intro s (concat s0 s1)

let lemma_concat3 #a len0 s0 len1 s1 len2 s2 s =
  let s' = concat (concat s0 s1) s2 in
  Seq.Properties.lemma_split (sub s 0 (len0 + len1)) len0;
  Seq.Properties.lemma_split (sub s' 0 (len0 + len1)) len0;
  Seq.Properties.lemma_split s (len0 + len1);
  Seq.Properties.lemma_split s' (len0 + len1);
  Seq.lemma_eq_intro s (concat (concat s0 s1) s2)

let createi_a (a:Type) (len:size_nat) (init:(i:nat{i < len} -> a)) (k:nat{k <= len}) =
  lseq a k

let createi_pred (a:Type) (len:size_nat) (init:(i:nat{i < len} -> a)) (k:nat{k <= len})
  (s:createi_a a len init k) =
  forall (i:nat).{:pattern (index s i)} i < k ==> index s i == init i

let createi_step (a:Type) (len:size_nat) (init:(i:nat{i < len} -> a)) (i:nat{i < len})
                 (si:createi_a a len init i)
  : r:createi_a a len init (i + 1)
      {createi_pred a len init i si ==> createi_pred a len init (i + 1) r}
  =
  assert (createi_pred a len init i si ==> (forall (j:nat). j < i ==> index si j == init j));
  Seq.snoc si (init i)

#push-options "--max_fuel 1 --using_facts_from '+Lib.LoopCombinators +FStar.List'"

let createi #a len init_f =
  repeat_gen_inductive len
    (createi_a a len init_f)
    (createi_pred a len init_f)
    (createi_step a len init_f)
    (of_list [])

#pop-options

inline_for_extraction
let mapi_inner (#a:Type) (#b:Type) (#len:size_nat)
  (f:(i:nat{i < len} -> a -> b)) (s:lseq a len) (i:size_nat{i < len}) =
  f i s.[i]

let mapi #a #b #len f s =
  createi #b len (mapi_inner #a #b #len f s)

inline_for_extraction
let map_inner (#a:Type) (#b:Type) (#len:size_nat)
  (f:(a -> Tot b)) (s:lseq a len) (i:size_nat{i < len}) =
  f s.[i]

let map #a #b #len f s =
  createi #b len (map_inner #a #b #len f s)

let map2i #a #b #c #len f s1 s2 =
  createi #c len (fun i -> f i s1.[i] s2.[i])

inline_for_extraction
let map2_inner (#a:Type) (#b:Type) (#c:Type) (#len:size_nat)
  (f:(a -> b -> Tot c)) (s1:lseq a len) (s2:lseq b len) (i:size_nat{i < len}) =
  f s1.[i] s2.[i]

let map2 #a #b #c #len f s1 s2 =
  createi #c len (map2_inner #a #b #c #len f s1 s2)

let for_all #a #len f x = Seq.for_all f x

let for_all2 #a #b #len f x y =
  let r = map2 (fun xi yi -> f xi yi) x y in
  Seq.for_all (fun bi -> bi = true) r

(** Selecting a subset of an unbounded Sequence *)
val seq_sub:
    #a:Type
  -> s1:seq a
  -> start:nat
  -> n:nat{start + n <= length s1}
  -> s2:seq a{length s2 == n /\
             (forall (k:nat{k < n}). {:pattern (Seq.index s2 k)} Seq.index s2 k == Seq.index s1 (start + k))}
let seq_sub #a s start n =
  Seq.slice #a s start (start + n)

(** Updating a subset of an unbounded Sequence with another Sequence *)
val seq_update_sub:
    #a:Type
  -> i:seq a
  -> start:nat
  -> n:nat{start + n <= length i}
  -> x:seq a{length x == n}
  -> o:seq a{length o == length i /\ seq_sub o start n == x /\
    (forall (k:nat{(0 <= k /\ k < start) \/ (start + n <= k /\ k < length i)}).
      {:pattern (Seq.index o k)} Seq.index o k == Seq.index i k)}
let seq_update_sub #a s start n x =
  let o =
    Seq.append
      (Seq.append (Seq.slice s 0 start) x)
      (Seq.slice s (start + n) (length s)) in
  Seq.lemma_eq_intro (Seq.slice o start (start + n)) x;
  o

val repeati_blocks_f:
    #a:Type0
  -> #b:Type0
  -> blocksize:size_nat{blocksize > 0}
  -> inp:seq a
  -> f:(i:nat{i < length inp / blocksize} -> lseq a blocksize -> b -> b)
  -> nb:nat{nb == length inp / blocksize}
  -> i:nat{i < nb}
  -> acc:b
  -> b
let repeati_blocks_f #a #b bs inp f nb i acc =
  assert ((i+1) * bs <= nb * bs);
  let block = seq_sub inp (i * bs) bs in
  f i block acc

let repeati_blocks #a #b bs inp f g init =
  let len = length inp in
  let nb = len / bs in
  let rem = len % bs in
  let acc = repeati nb (repeati_blocks_f bs inp f nb) init in
  let last = seq_sub inp (nb * bs) rem in
  g nb rem last acc

let repeat_blocks #a #b #c bs inp f l init =
  let len = length inp in
  let nb = len / bs in
  let rem = len % bs in
  let acc = repeati nb (repeat_blocks_f bs inp f nb) init in
  let last = seq_sub inp (nb * bs) rem in
  l rem last acc

let lemma_repeat_blocks #a #b #c bs inp f l init = ()

let repeat_blocks_multi #a #b bs inp f init =
  let len = length inp in
  let nb = len / bs in
  repeati nb (repeat_blocks_f bs inp f nb) init

let lemma_repeat_blocks_multi #a #b bs inp f init = ()

let generate_blocks_a (t:Type) (blocklen:size_nat) (max:nat) (a:(i:nat{i <= max} -> Type)) (i:nat{i <= max}) = a i & s:seq t{length s == i * blocklen}

let generate_blocks_inner (t:Type) (blocklen:size_nat) (max:nat) (a:(i:nat{i <= max} -> Type)) (f:(i:nat{i < max} -> a i -> a (i + 1) & s:seq t{length s == blocklen})) (i:nat{i < max}) (acc:generate_blocks_a t blocklen max a i) : generate_blocks_a t blocklen max a (i + 1) =
    let acc, o = acc in
    let acc', block = f i acc in
    let o' : s:seq t{length s == ((i + 1) * blocklen)} = Seq.append o block in
    acc', o'

let generate_blocks #t len max n a f acc0 =
  let a0  = (acc0, (Seq.empty <: s:seq t{length s == 0 * len}))  in
  repeat_gen n (generate_blocks_a t len max a) (generate_blocks_inner t len max a f) a0

let generate_blocks_simple_a (a:Type) (bs:size_nat) (max:nat) (i:nat{i <= max}) = s:seq a{length s == i * bs}

let generate_blocks_simple_f
 (#a:Type)
 (bs:size_nat{bs > 0})
 (max:nat)
 (f:(i:nat{i < max} -> lseq a bs))
 (i:nat{i < max})
 (acc:generate_blocks_simple_a a bs max i) : generate_blocks_simple_a a bs max (i + 1)
=
 Seq.append acc (f i)

let generate_blocks_simple #a bs max nb f =
 repeat_gen nb (generate_blocks_simple_a a bs max)
   (generate_blocks_simple_f #a bs max f) Seq.empty

#restart-solver
let div_interval b n i =
  Math.Lemmas.lemma_div_le (n * b) i b;
  Math.Lemmas.cancel_mul_div n b

let mod_interval_lt b n i j =
  div_interval b n i;
  div_interval b n j

let div_mul_lt b a n = ()

let mod_div_lt b i j =
  mod_interval_lt b (j / b) i j

let div_mul_l a b c d =
  calc (==) {
    a / (c * d);
    == { }
    a / (d * c);
    == { Math.Lemmas.division_multiplication_lemma a d c }
    (a / d) / c;
    == { }
    (b / d) / c;
    == { Math.Lemmas.division_multiplication_lemma b d c }
    b / (d * c);
    == { }
    b / (c * d);
  }


let map_blocks_multi #a bs max nb inp f =
  repeat_gen nb (map_blocks_a a bs max)
    (map_blocks_f #a bs max inp f) Seq.empty

let lemma_map_blocks_multi #a bs max nb inp f = ()

private
val mod_prop: n:pos -> a:nat -> b:nat{a * n <= b /\ b < (a + 1) * n} ->
  Lemma (b - a * n == b % n)
let mod_prop n a b =
  Math.Lemmas.modulo_lemma (b - a * n) n;
  Math.Lemmas.lemma_mod_sub b n a

#push-options "--z3rlimit 200"

let rec index_map_blocks_multi #a bs max n inp f i =
  let map_blocks_a = map_blocks_a a bs max in
  let map_blocks_f = map_blocks_f #a bs max inp f in
  let acc0 = Seq.empty #a in
  let s1 = repeat_gen n map_blocks_a map_blocks_f acc0 in
  unfold_repeat_gen n map_blocks_a map_blocks_f acc0 (n-1);
  let s = repeat_gen (n-1) map_blocks_a map_blocks_f acc0 in
  //assert (s1 == map_blocks_f (n-1) s);
  let s' = f (n-1) (Seq.slice inp ((n-1)*bs) (n*bs)) in
  //assert (s1 == Seq.append s s');
  if i < (n-1)*bs then begin
    Seq.lemma_index_app1 s s' i;
    index_map_blocks_multi #a bs max (n-1) inp f i end
  else begin
    Seq.lemma_index_app2 s s' i;
    mod_prop bs (n-1) i
  end

let map_blocks #a blocksize inp f g =
  let len = length inp in
  let nb = len / blocksize in
  let rem = len % blocksize in
  let blocks = Seq.slice inp 0 (nb * blocksize) in
  let last = Seq.slice inp (nb * blocksize) len in
  let bs = map_blocks_multi #a blocksize nb nb blocks f in
  if (rem > 0) then
    Seq.append bs (g nb rem last)
  else bs

let lemma_map_blocks #a blocksize inp f g = ()

let index_map_blocks #a bs inp f g i =
  let len = length inp in
  let nb  = len / bs in
  let rem = len % bs in
  let blocks = Seq.slice inp 0 (nb * bs) in
  if rem > 0 then
    begin
    let s1 = map_blocks_multi #a bs nb nb blocks f in
    let last = Seq.slice inp (nb * bs) len in
    calc (==) {
      length last;
      == { Seq.lemma_len_slice inp (nb * bs) len }
      len - nb * bs;
      == {mod_prop bs nb len}
      len % bs;
      == { }
      rem;
    };
    let s2 = g nb rem last in
    assert (Seq.equal (map_blocks bs inp f g) (Seq.append s1 s2));
    if i < nb * bs then
      begin
      div_mul_lt bs i nb;
      Seq.lemma_index_app1 s1 s2 i;
      index_map_blocks_multi bs nb nb blocks f i
      end
    else
      begin
      Seq.lemma_index_app2 s1 s2 i;
      mod_prop bs nb i
      end
    end
  else index_map_blocks_multi #a bs nb nb blocks f i

let eq_generate_blocks0 #t len n a f acc0 =
  let a0  = (acc0, (Seq.empty <: s:seq t{length s == 0 * len}))  in
  assert (generate_blocks #t len n 0 a f acc0 ==
          repeat_gen 0 (generate_blocks_a t len n a) (generate_blocks_inner t len n a f) a0);
  eq_repeat_gen0 0 (generate_blocks_a t len n a) (generate_blocks_inner t len n a f) a0

let unfold_generate_blocks #t len n a f acc0 i =
  let a0  = (acc0, (Seq.empty <: s:seq t{length s == 0 * len}))  in
  assert (generate_blocks #t len n (i+1) a f acc0 ==
          repeat_gen (i+1) (generate_blocks_a t len n a) (generate_blocks_inner t len n a f) a0);
  unfold_repeat_gen (i+1) (generate_blocks_a t len n a) (generate_blocks_inner t len n a f) a0 i
val index_generate_blocks:
    #t:Type0
  -> len:size_pos
  -> max:nat
  -> n:pos{n <= max}
  -> f:(i:nat{i < max} -> unit -> unit & s:seq t{length s == len})
  -> i:nat{i < n * len}
  -> Lemma (Math.Lemmas.lemma_mult_le_right len n max;
           div_mul_lt len i max;
           let a_spec (i:nat{i <= max}) = unit in
           let _,s1 = generate_blocks len max n a_spec f () in
           let _,s2 = f (i / len) () in
           Seq.index s1 i == Seq.index s2 (i % len))

    len: Lib.IntTypes.size_pos ->
    max: Prims.nat ->
    n: Prims.pos{n <= max} ->
    f:
      (i: Prims.nat{i < max} -> _: Prims.unit
          -> Prims.unit * s: Lib.Sequence.seq t {Lib.Sequence.length s == len}) ->
    i: Prims.nat{i < n * len}
  -> FStar.Pervasives.Lemma
    (ensures
      ([@@ FStar.Pervasives.inline_let ]let _ = FStar.Math.Lemmas.lemma_mult_le_right len n max in
        [@@ FStar.Pervasives.inline_let ]let _ = Lib.Sequence.div_mul_lt len i max in
        let a_spec i = Prims.unit in
        let _ = Lib.Sequence.generate_blocks len max n a_spec f () in
        (let FStar.Pervasives.Native.Mktuple2 #_ #_ _ s1 = _ in
          let _ = f (i / len) () in
          (let FStar.Pervasives.Native.Mktuple2 #_ #_ _ s2 = _ in
            FStar.Seq.Base.index s1 i == FStar.Seq.Base.index s2 (i % len))
          <:
          Type0)
        <:
        Type0))