/*
CS:5810 Formal Methods in Software Engineering
Fall 2022
The University of Iowa

Instructor: Cesare Tinelli
*/

// Parametric FIFO queue
// Implementation uses an array to store the queue elements
// in order of arrival

class Queue<T> {
// Abtract representation of the queue's elements
ghost var Content: seq<T>;

// Concrete implementation of the queue
// container for the queue's elements
var a: array<T>;
// size of the queue
var n: nat;

predicate Valid()
reads this, a;
{
// Concrete class invariant
n <= a.Length &&
0 < a.Length &&

// Connection between abstract and concrete state
Content == a[0..n]
}

constructor (d: T)
ensures fresh(a);

ensures Valid();
ensures Content == [];
{
a := new T[5](_ => d); // initializes every array element to d
n := 0;

// ghost code
Content := [];
}

method front() returns (e: T)
requires Content != [];
requires Valid();
ensures Valid();
ensures Content == old(Content);
ensures e == Content[0];

//ensures a == old(a);
{
e := a[0];
}

method enqueue(e: T)
modifies this, a;
ensures old(n) < old(a.Length) ==> a == old(a);
ensures old(n) == old(a.Length) ==> fresh(a);

requires Valid();
ensures Valid();
ensures Content == old(Content) + [e];
{
if n == a.Length {
var b := new T[2 * a.Length](_ => e);
forall i:int | 0 <= i < n {
b[i] := a[i];
}
a := b;
}
a[n] := e;
n := n + 1;

// ghost code
Content := Content + [e];
}

method dequeue() returns (e: T)
modifies this, a;
ensures a == old(a);

requires Content != [];
requires Valid();
ensures Valid();
ensures e == old(Content[0]);
ensures Content == old(Content[1..]);
{
e := a[0];
n := n - 1;
forall i:int | 0 <= i < n {
a[i] := a[i + 1];
}

// ghost code
Content := Content[1..];
}
}

method test ()
{
var q := new Queue<int>(0); assert q.Content == [];
q.enqueue(1); assert q.Content == [1];
q.enqueue(2); assert q.Content == [1,2];
q.enqueue(3); assert q.Content == [1,2,3];
q.enqueue(2); assert q.Content == [1,2,3,2];
q.enqueue(4); assert q.Content == [1,2,3,2,4];

var f := q.front(); assert f == 1 && q.Content == [1,2,3,2,4];
f := q.dequeue(); assert f == 1 && q.Content == [2,3,2,4];
q.enqueue(5); assert q.Content == [2,3,2,4,5];
}