// ------------------------------------------------------------------
// Hotel Key Problem: Chapter 6 and Appendix E of Daniel Jackson, 
// Software Abstractions - Logic, Language, and Analysis,
// Revised edition, MIT Press, 2012.
// ------------------------------------------------------------------

val R: ℕ; // number of rooms
val C: ℕ; // number of key cards
val K: ℕ; // number of key values
axiom notZero ⇔ R > 0 ∧ C ≥ R ∧ K > R;

type Room = ℕ[R-1];
type Card = ℕ[C-1];
type Key = ℕ[K-1];
type KeyPair = Tuple[Key,Key];

type Hotel = Record[
  // the information hidden in the hardware
  locks:  Array[R,Key],     // key stored in every lock
  cards:  Array[C,KeyPair], // two keys stored in every card

  // the records kept at the desk
  previous: Array[R,Key],   // previous key assigned to every room
  roomused: Array[R,Bool],  // is room occupied?
  cardused: Array[C,Bool],  // has card been issued?
  assigned: Array[C,Room]   // which room is assigned to every card?
];

// the chosen values on which the actions depend
rectype (1) Action = 
  checkin(Card,Key,Room) | enter(Card,Room) | checkout(Card);

// ------------------------------------------------------------------
// model predicates
// ------------------------------------------------------------------

// key value k is currently in use
pred keyused(k:Key, h:Hotel) ⇔
  (∃r:Room. h.locks[r] = k ∨ h.previous[r] = k) ∨
  (∃c:Card. h.cards[c].1 = k ∨ h.cards[c].2 = k);

// card c opens room r
pred opens(c:Card, r:Room, h:Hotel) ⇔
  h.locks[r] = h.cards[c].1 ∨ h.locks[r] = h.cards[c].2;

// new guest is given card c with fresh key k and asssigned room r
fun checkin(c:Card, k:Key, r:Room, h:Hotel): Hotel
// ensures print "checkin {1}", result in true;
= h with .cards = h.cards with [c] = ⟨k,h.previous[r]⟩
    with .previous = h.previous with [r] = k
    with .roomused = h.roomused with [r] = ⊤
    with .cardused = h.cardused with [c] = ⊤
    with .assigned = h.assigned with [c] = r;

// guest with card c enters room r
fun enter(c:Card, r:Room, h:Hotel): Hotel
// ensures print "enter {1}", result in true;
= if h.locks[r] = h.cards[c].1 then
    h
  else
    h with .locks  = h.locks with [r] = h.cards[c].1;

// guest with card c checks out, we may reuse its card and room
fun checkout(c:Card, h:Hotel): Hotel
// ensures print "checkout {1}", result in true;
= h with .cardused = h.cardused with [c] = ⊥
    with .roomused = h.roomused with [h.assigned[c]] = ⊥;

// -----------------------------------------------------------------
// relational model of hotel
// -----------------------------------------------------------------

// the condition on the initial state of the hotel
pred init(h:Hotel) ⇔
  (∀r1:Room, r2:Room with r1 < r2. (h.locks[r1] ≠ h.locks[r2])) ∧
  (∀r:Room. ¬h.roomused[r] ∧ h.locks[r] = h.previous[r]) ∧
  (∀c:Card. ¬h.cardused[c]);

pred checkin(h:Hotel, h0:Hotel) ⇔
  ∃c:Card, k:Key, r:Room with
     ¬h.cardused[c] ∧ ¬keyused(k, h) ∧ ¬h.roomused[r].
   h0 = checkin(c, k, r, h);
   
pred enter(h:Hotel, h0:Hotel) ⇔
  ∃c:Card, r:Room with h.cardused[c] ∧ opens(c, r, h).
    h0 = enter(c, r, h);
    
pred checkout(h:Hotel, h0:Hotel) ⇔
  ∃c:Card with h.cardused[c].
    h0 = checkout(c, h);

pred next(h:Hotel, h0:Hotel) ⇔
  checkin(h, h0) ∨ enter(h, h0) ∨ checkout(h, h0);
 
// -----------------------------------------------------------------
// functional model of hotel
// -----------------------------------------------------------------

// is action a admissible?
pred admissible(a:Action, h:Hotel) ⇔
  match a with
  {
    checkin(c:Card, k:Key, r:Room) -> 
      ¬h.cardused[c] ∧ ¬keyused(k, h) ∧ ¬h.roomused[r];
    enter(c:Card, r:Room) -> 
      h.cardused[c] ∧ opens(c, r, h);
    checkout(c:Card) -> 
      h.cardused[c];
  };

// get new state for hotel h when performing action a
fun next(a:Action, h:Hotel): Hotel =
  match a with
  {
    checkin(c:Card, k:Key, r:Room) -> checkin(c, k, r, h);
    enter(c:Card, r:Room) -> enter(c, r, h);
    checkout(c:Card) -> checkout(c, h);
  };
 
// the action oriented model is equivalent to the original one
theorem equiv(h:Hotel, h0:Hotel) ⇔
  next(h, h0) ⇔ ∃a:Action with admissible(a, h). h0 = next(a, h);

// ------------------------------------------------------------------
// analyzing the model
// ------------------------------------------------------------------

val N:ℕ;
type Index = ℕ[N];
   
// the safety properties required by the hotel
pred safe(h:Hotel) ⇔
  // no room is opened by two issued cards
  (¬∃r:Room, c1: Card, c2: Card.
    h.cardused[c1] ∧ opens(c1, r, h) ∧ 
    h.cardused[c2] ∧ opens(c2, r, h) ∧ c1 ≠ c2)
  // number of cards issued equals number of occupied rooms that can be opened
  // ∧ (#c: Card with h.cardused[c]) = 
  //  (#r: Room with h.roomused[r] ∧ ∃c:Card. opens(c,r,h))
  // every issued card opens some room
  // ∧ (∀c:Card. h.cardused[c] ⇒ ∃r:Room. opens(c, r, h))
  // every occupied room is opened by some issued card
  // ∧ (∀r:Room. h.roomused[r] ⇒ ∃c:Card. opens(c, r, h))
;

// compute the state of hotel after n steps starting with h0
proc run(h0:Hotel, n:Index): Hotel
  requires init(h0);
{
  var h:Hotel = h0;
  var as: Array[N,Action] = Array[N,Action](Action!enter(0,0));
  var i:Index = 0;
  if ¬safe(h) then { print as; print "{1}: {2}", i, h; assert ⊥; }
  while i < n do
  {
    choose a:Action with admissible(a, h);
    h ≔ next(a, h);
    as[i] := a;
    i ≔ i+1;
    if ¬safe(h) then { print as; print "{1}: {2}", i, h; assert ⊥; }
  }
  return h;
}

// compute state of hotel after N steps
fun run(h:Hotel): Hotel 
  requires init(h);
= run(h, N);
  
// compute state of hotel after specific initial state
proc hotel(): Hotel
{
  
  var h:Hotel = 
    ⟨locks:Array[R,Key](0),
     cards:Array[C,KeyPair](⟨0,0⟩),
     previous:Array[R,Key](0),
     roomused:Array[R,Bool](⊥),
     cardused:Array[C,Bool](⊥),
     assigned:Array[C,Room](0)⟩;
  for var r:ℕ[R] = 0; r < R; r ≔ r+1 do
  {
    h.locks[r] ≔ r;
    h.previous[r] := r;
    h.roomused[r] ≔ ⊥;
  }
  for var c:ℕ[C] = 0; c < C; c ≔ c+1 do
  {
    h.cards[c] ≔ ⟨c,c⟩;
    h.cardused[c] ≔ ⊥;
  }
  return h;
}
fun run0(): Hotel = run(hotel());

// ------------------------------------------------------------------
// end of file
// ------------------------------------------------------------------





