module Python

  use int.Int
  use ref.Ref
  use seq.Seq
  use int.EuclideanDivision as E

  type list 'a = private {
    mutable elts: seq 'a;
    mutable len: int;
  } invariant { len = length elts }

  meta coercion function elts

  function ([]) (l: list 'a) (i: int) : 'a
  = [@inline:trivial]
    if i >= 0 then Seq.(l[i]) else Seq.(l[l.len + i])

  val ghost function create (s: seq 'a) : list 'a
    ensures { result = s }
    ensures { len result = length s }

  function ([<-]) (l: list 'a) (i: int) (v: 'a) : list 'a
  = [@inline:trivial]
    create (if i >= 0 then Seq.(l.elts[i <- v]) else Seq.(l.elts[l.len + i <- v]))

  let ([]) (l: list 'a) (i: int) : 'a
    requires { [@expl:index in array bounds] -len l <= i < len l }
    ensures  { result = l[i] }
  = if i >= 0 then Seq.(l.elts[i]) else Seq.(l.elts[len l + i])

  val ([]<-) (l: list 'a) (i: int) (v: 'a) : unit
    requires { [@expl:index in array bounds] -len l <= i < len l }
    writes   { l.elts }
    ensures  { l.elts = (old l)[i <- v].elts }

  val empty () : list 'a
    ensures { result = Seq.empty }

  val make (n: int) (v: 'a) : list 'a
    requires { n >= 0 }
    ensures  { len result = n }
    ensures  { forall i. -n <= i < n -> result[i] = v }


  (* ad-hoc facts about exchange *)

  use seq.Occ

  function occurrence (v: 'a) (l: list 'a) (lo hi: int): int =
    Occ.occ v l lo hi

  use int.Abs

  (* Python's division and modulus according are neither Euclidean division,
   nor computer division, but something else defined in
   https://docs.python.org/3/reference/expressions.html *)

  function (//) (x y: int) : int =
    let q = E.div x y in
    if y >= 0 then q else if E.mod x y > 0 then q-1 else q
  function (%) (x y: int) : int =
    let r = E.mod x y in
    if y >= 0 then r else if r > 0 then r+y else r

  lemma div_mod:
    forall x y:int. y <> 0 -> x = y * (x // y) + (x % y)
  lemma mod_bounds:
    forall x y:int. y <> 0 -> 0 <= abs (x % y) < abs y
  lemma mod_sign:
    forall x y:int. y <> 0 -> if y < 0 then x % y <= 0 else x % y >= 0

  val (//) (x y: int) : int
    requires { [@expl:check division by zero] y <> 0 }
    ensures  { result = x // y }

  val (%) (x y: int) : int
    requires { [@expl:check division by zero] y <> 0 }
    ensures  { result = x % y }

  (* random.randint *)
  val randint (l u: int) : int
    requires { [@expl:valid range] l <= u }
    ensures  { l <= result <= u }

  use int.Power

  function pow (x y: int) : int = power x y

  val input () : int

  val int (n: int) : int
    ensures { result = n }

  (* ajouts réalisés *)

  val range (lo hi: int) : list int
    requires { [@expl:valid range] lo <= hi }
    ensures  { len result = hi - lo }
    ensures  { forall i. 0 <= i < hi - lo -> result[i] = i + lo }

  let range3 (le ri step: int) : list int
    requires { [@expl:valid range] (le <= ri /\ step > 0) \/ (ri <= le /\ step < 0) }
    ensures  { len result = E.div (ri - le) step + if E.mod (ri - le) step <> 0 then 1 else 0 }
    ensures  { forall i. 0 <= i < len result -> result[i] = le + i * step }
  = let n = E.div (ri - le) step + if E.mod (ri - le) step <> 0 then 1 else 0 in
    let a = make n 0 in
    for i=0 to n-1 do
      invariant { forall j. 0 <= j < i -> a[j] = le + j * step }
      a[i] <- le + i * step;
    done;
    assert { step > 0 -> le +  n    * step >= ri
          /\ step > 0 -> le + (n-1) * step <  ri
          /\ step < 0 -> le +  n    * step <= ri
          /\ step < 0 -> le + (n-1) * step >  ri };
    return a

  val pop (l: list 'a) : 'a
    requires { len l > 0 }
    writes   { l }
    ensures  { len l = len (old l) - 1 }
    ensures  { result = (old l)[len l] }
    ensures  { forall i. 0 <= i < len l -> l[i] = (old l)[i] }

  val append (l: list 'a) (v: 'a) : unit
    writes   { l }
    ensures  { len l = len (old l) + 1 }
    ensures  { l[len (old l)] = v }
    ensures  { forall i. 0 <= i < len (old l) -> l[i] = (old l)[i] }

  val function add_list (a1: list 'a) (a2: list 'a) : list 'a
    ensures { len result = len a1 + len a2 }
    ensures { forall i. 0 <= i < len a1 -> result[i] = a1[i] }
    ensures { forall i. len a1 <= i < len result -> result[i] = a2[i-len a1] }

  function transform_idx (l: list 'a) (i: int): int
  = [@inline:trivial]
    if i < 0 then len l + i else i

  val function slice (l: list 'a) (lo hi: int) : list 'a
    requires { transform_idx l lo >= 0 }
    requires { transform_idx l hi <= len l }
    requires { transform_idx l lo <= transform_idx l hi }
    ensures  { len result = transform_idx l hi - transform_idx l lo }
    ensures  { forall i. 0 <= i < len result -> result[i] = l[transform_idx l lo + i] }

  val clear (l : list 'a) : unit
    writes  { l }
    ensures { len l = 0 }

  val copy (l: list 'a): list 'a
    ensures { len result = len l }
    ensures { result.elts = l.elts }

  use seq.Permut

  predicate is_permutation (l1 l2: list 'a)
  = ([@expl:equality of length] length l1 = length l2)
      /\ ([@expl:equality of elements] permut l1 l2 0 (length l2))

  val sort (l : list int) : unit
    writes  { l.elts }
    ensures { is_permutation l (old l) }
    ensures { forall i, j. 0 <= i <= j < len(l) -> l[i] <= l[j] }

  val reverse (l: list 'a) : unit
    writes  { l.elts }
    ensures { len l = len (old l) }
    ensures { forall i. 0 <= i < len l -> l[i] = (old l)[-(i+1)] }
    ensures { forall i. 0 <= i < len l -> (old l)[i] = l[-(i+1)] }
    ensures { is_permutation l (old l) }

end
