// RUN: %dafny /compile:0 /dprint:"%t.dprint" "%s" > "%t"
// RUN: %diff "%s.expect" "%t"

// Basics.v done in Dafny

// Enumerated Types

// Days of the Week

datatype day =
    monday
  | tuesday
  | wednesday
  | thursday
  | friday
  | saturday
  | sunday

// See method test_next_weekday() below for an explanation of why this function
// is declared a non-ghost (by "function method").
function method next_weekday (d:day) : day
{
  match d
  case monday    => tuesday
  case tuesday   => wednesday
  case wednesday => thursday
  case thursday  => friday
  case friday    => monday
  case saturday  => monday
  case sunday    => monday
}

method test_next_weekday()
{
  assert next_weekday(friday) == monday;
  assert next_weekday(next_weekday(saturday)) == tuesday;
  // Note, if you're not sure what some expression will compute, you
  // can assert that it equals some arbitrary value of the correct type.
  // For example:
  var d := next_weekday(next_weekday(tuesday));
  assert d == friday;   // error (use the Verification Debugger to view the correct value)
  // You will then get an error message (unless you were lucky and
  // happened to pick the right value).  If you're running the Dafny IDE
  // in Visual Studio, you can then click on the red circle that signals
  // the error.  This will bring up the Verification Debugger, where you
  // can inspect the value of d.  Then try changing the assertion in
  // the program to assert d to equal it.  In some cases, depending on
  // how many Dafny knows about the value you're trying to get it to
  // compute, you may need to build a disjunction of values for d in the
  // assertion.
  // Or, just write a program that prints the value, compile the program,
  // and run it -- Dafny is a programming language after all.  (Note, by
  // default, functions in Dafny of "ghost", which means they don't turn
  // into compiled code.  If you want to compile code for next_weekday, so
  // that you can use it in a print statement, change "function" to
  // "function method" in the declaration of next_weekday.)  To try this
  // out on rise4fun.com/dafny:  (0) comment out (or fix!) the assert above
  // so that the program will verify, (1) fill in a body of the uninterpreted
  // function f on line 396 below (any body will do; for example, uncomment
  // line 400), and (2) change the name of this method to Main().
  print next_weekday(wednesday);
}



// Booleans

// Booleans are built into Dafny as primitives.  Nevertheless, here is
// another definition, using an enumeration.

datatype Bool = True | False

function negb (b:Bool) : Bool
{
  match b
  case True => False
  case False => True
}

function andb (b1:Bool, b2:Bool) : Bool
{
  match b1
  case True => b2 
  case False => False
}

function orb (b1:Bool, b2:Bool) : Bool
{
  match b1
  case True => True
  case False => b2
}

method test_orb()
{
  assert orb(True, False) == True;
  assert orb(False, False) == False;
  assert orb(False, True) == True;
  assert orb(True, True) == True;
}

// Exercise: 1 star (nandb)

function nandb (b1:Bool, b2:Bool) : Bool
{
  negb(andb(b1, b2))
}

method test_nandb()
{
  assert nandb(True, False) == True;
  assert nandb(False, False) == True;
  assert nandb(False, True) == True;
  assert nandb(True, True) == False;
}

// Exercise: 1 star (andb3)

function andb3 (b1:Bool, b2:Bool, b3:Bool) : Bool
{
  andb(andb(b1, b2), b3)
}

method test_andb3()
{
  assert andb3(True, True, True) == True;
  assert andb3(False, True, True) == False;
  assert andb3(True, False, True) == False;
  assert andb3(True, True, False) == False;
}

// Function Types

// Numbers

datatype Nat =
    O
  | S(Nat)

function pred (n : Nat) : Nat
{
  match n
  case O => O
  case S(n') => n'
}

function minustwo (n : Nat) : Nat
{
  match n
  case O => O
  case S(nn) =>
    // Note, Dafny currently does not support nested patterns in match expressions, but
    // this will change soon.
    match nn
    case O => O
    case S(n') => n'
}

function evenb (n:Nat) : Bool
{
  match n
  case O       => True
  case S(nn) =>
    match nn
    case O     => False
    case S(n') => evenb(n')
}

function oddb (n:Nat) : Bool
{
  negb(evenb(n))
}

method test_oddb()
{
  assert oddb(S(O)) == True;
  assert oddb(S(S(S(S(O))))) == False;
}

function plus (n : Nat, m : Nat) : Nat
{
  match n
  case O => m
  case S(n') => S(plus(n', m))
}

method test_plus()
{
  assert plus(S(S(S(O))), S(S(O))) == S(S(S(S(S(O)))));
}

function mult (n: Nat, m: Nat) : Nat
{
  match n
  case O => O
  case S(n') => plus(m, mult(n', m))
}

method test_mult()
{
  var n3 := S(S(S(O)));
  var n9 := S(S(S(S(S(S(S(S(S(O)))))))));
  assert mult(n3, n3) == n9;
}

function minus (n: Nat, m: Nat) : Nat
{
  match n
  case O => O
  case S(n') =>
    match m
    case O => n
    case S(m') => minus(n', m')
}

function exp (base: Nat, power: Nat) : Nat
{
  match power
  case O => S(O)
  case S(p) => mult(base, exp(base, p))
}

// Exercise: 1 star (factorial)

function factorial(n: Nat): Nat
{
  match n
  case O => S(O)
  case S(n') => mult(n, factorial(n'))
}

method test_factorial1()
{
  var n2 := S(S(O));
  var n3 := S(n2);
  var n5 := S(S(n3));
  var n6 := S(n5);
  assert factorial(n3) == n6;

  var n10 := S(S(S(S(n6))));
  var n12 := S(S(n10));

  assert factorial(n5)==mult(n10, n12);
}

method test_factorial1_old()
{
  var n2 := S(S(O));
  var n3 := S(n2);
  var n5 := S(S(n3));
  var n6 := S(n5);
  assert factorial(n3) == n6;

  var n10 := S(S(S(S(n6))));
  var n12 := S(S(n10));

  // proving that 5! == 10*12 _used to_ take some effort...
  calc {
    factorial(n5);
    { mult_lemma(n2, n6); }
    mult(n5, plus(plus(n6, n6), plus(plus(n6, n6), O)));
    { mult_lemma(n5, plus(n6, n6)); }
    mult(n10, n12);
  }
}

// This lemma expresses:  m*(2*n) == (2*m)*n
lemma mult_lemma(m: Nat, n: Nat)
  ensures mult(m, plus(n, n)) == mult(plus(m, m), n);
{
  match m {
    case O =>
    case S(m') =>
      calc {
        mult(m, plus(n, n));
        plus(plus(n, n), mult(m', plus(n, n)));
        // induction hypothesis
        plus(plus(n, n), mult(plus(m', m'), n));
        { assert forall a,b,c :: plus(plus(a, b), c) == plus(a, plus(b, c)); }
        plus(n, plus(n, mult(plus(m', m'), n)));
        mult(S(S(plus(m', m'))), n);
        mult(S(plus(S(m'), m')), n);
        { assert forall a,b :: S(plus(a, b)) == plus(a, S(b)); }
        mult(plus(S(m'), S(m')), n);
      }
  }
}

function beq_nat (n: Nat, m: Nat) : Bool
{
  match n
  case O => (
         match m
         case O => True
         case S(m') => False
         )
  case S(n') =>
         match m
         case O => False
         case S(m') => beq_nat(n', m')
}

function ble_nat (n: Nat, m: Nat) : Bool
{
  match n
  case O => True
  case S(n') =>
      match m
      case O => False
      case S(m') => ble_nat(n', m')
}

method test_ble_nat1()
{
  var n2 := S(S(O));
  var n4 := S(S(n2));
  assert ble_nat(n2, n2) == True;
  assert ble_nat(n2, n4) == True;
  assert ble_nat(n4, n2) == False;
}

// Exercise: 2 stars (blt_nat)

function blt_nat (n: Nat, m: Nat) : Bool
{
  andb(ble_nat(n, m), negb(beq_nat(n, m)))
}

method test_blt_nat1()
{
  var n2 := S(S(O));
  var n4 := S(S(n2));
  assert blt_nat(n2, n2) == False;
  assert blt_nat(n2, n4) == True;
  assert blt_nat(n4, n2) == False;
}

// Proof by Simplification

lemma plus_O_n (n: Nat)
  ensures plus(O, n) == n;
{
}

lemma plus_1_l (n: Nat)
  ensures plus(S(O), n) == S(n);
{
}

lemma mult_0_l (n: Nat)
  ensures mult(O, n) == O;
{
}

// Proof by Rewriting

lemma plus_id_example (n: Nat, m: Nat)
  ensures n == m ==> plus(n, n) == plus(m, m);
{
}

// Exercise: 1 star (plus_id_exercise)

lemma plus_id_exercise (n: Nat, m: Nat, o: Nat)
  ensures n == m ==> m == o ==> plus(n, m) == plus(m, o);
{
}

lemma mult_0_plus (n: Nat, m: Nat)
  ensures mult(plus(O, n), m) == mult(n, m);
{
}

// Exercise: 2 stars (mult_S_1)

lemma mult_S_1 (n: Nat, m: Nat)
  ensures m == S(n) ==> mult(m, plus(S(O), n)) == mult(m, m);
{
}


// Proof by Case Analysis

lemma plus_1_neq_0_firsttry (n: Nat)
  ensures beq_nat(plus(n, S(O)), O) == False;
{
}

lemma plus_1_neq_0 (n: Nat)
  ensures beq_nat(plus(n, S(O)), O) == False;
{
}

lemma negb_involutive (b: Bool)
  ensures negb(negb(b)) == b;
{
}

// Exercise: 1 star (zero_nbeq_plus_1)

lemma zero_nbeq_plus_1 (n: Nat)
  ensures beq_nat(O, plus(n, S(O))) == False;
{
}

// More Exercises

// Exercise: 2 stars (boolean functions)

// Since Dafny currently does not allow functions as parameters, we instead declare
// a global function f for use in the following lemmas.  Since f is given no body,
// it will be treated as an uninterpreted function.
function f(x: Bool): Bool
// Note:  If you want to compile and run the program, as suggested above in method
// test_next_weekday, then you must supply some body for f.  Here is one way to do
// that:
//      { x }

lemma identity_fn_applied_twice(b: Bool)
  requires forall x :: f(x) == x;
  ensures f(f(b)) == b;
{
}
  
    
lemma negation_fn_applied_twice(b: Bool)
  requires forall x :: f(x) == negb(x);
  ensures f(f(b)) == b;
{
}

// Exercise: 2 stars (andb_eq_orb)

lemma andb_true (b : Bool)
  ensures andb(True, b) == b;
{
}

lemma orb_false (b : Bool)
  ensures orb(False, b) == b;
{
}

lemma andb_eq_orb (b: Bool, c: Bool)
  ensures andb(b, c) == orb(b, c) ==> b == c;
{
}

// Exercise: 3 stars (binary)

datatype bin = Zero | Twice(bin) | TwicePlusOne(bin)

function increment(b: bin): bin
{
  match b
  case Zero => TwicePlusOne(Zero)
  case Twice(b') => TwicePlusOne(b')
  case TwicePlusOne(b') => Twice(increment(b'))
}

function BinToUnary(b: bin): Nat
{
  match b
  case Zero => O
  case Twice(b') =>
    var t := BinToUnary(b');
    plus(t, t)
  case TwicePlusOne(b') =>
    var t := BinToUnary(b');
    plus(t, plus(t, S(O)))
}

method test_bin()
{
  var n6 := S(S(S(S(S(S(O))))));
  var n13 := S(S(S(S(S(S(S(n6)))))));
  assert BinToUnary(Twice(TwicePlusOne(TwicePlusOne(Zero)))) == n6;
  assert BinToUnary(TwicePlusOne(Twice(TwicePlusOne(TwicePlusOne(Zero))))) == n13;
}

method test_increment()
{
  var b13 := TwicePlusOne(Twice(TwicePlusOne(TwicePlusOne(Zero))));
  var n13 := S(S(S(S(S(S(S(S(S(S(S(S(S(O)))))))))))));
  var n14 := S(n13);

  assert increment(Twice(TwicePlusOne(TwicePlusOne(Zero)))) == TwicePlusOne(TwicePlusOne(TwicePlusOne(Zero)));
  assert increment(b13) == Twice(TwicePlusOne(TwicePlusOne(TwicePlusOne(Zero))));

  assert BinToUnary(increment(b13)) == plus(BinToUnary(b13), S(O));
}

// Optional Material

// [Fixpoint]s and Structural Recursion

function plus' (n : Nat, m : Nat) : Nat
{
  match n
  case O => m
  case S(n') => S(plus'(n', m))
}

// Exercise: 2 stars, optional (decreasing)

function decreasingOnTwo (n: Nat, m: Nat, p: Nat) : Nat
{
  match p
  case O => (
      match n
      case O     => O
      case S(n') => decreasingOnTwo(n', m, S(O))
      )
  case S(_) =>
      match m
      case O     => S(O)
      case S(m') => decreasingOnTwo(n, m', O)
}
