
namespace lec3

/- A little magic ... -/
local attribute [intro] and.intro
local attribute [intro] or.inl
local attribute [intro] or.inr

meta def auto := tactic.back_chaining_using_hs
/- End magic ... -/

-- `Prop` is the 'type' of propositions.
#print Prop

/-- `true` is reserved for the type of true propositions, the `true` defined
     in Lean is not built in and is defined exactly the same. -/
inductive true : Prop
| intro : true

lemma true_is_easy :
    true :=
begin
    exact true.intro
end

#print true

inductive false : Prop

#print false

lemma bogus :
    false → 1 = 2 :=
begin
    intros,
    -- There is no way to build a value of `false`, if
    -- we have one in our context we must of derived a
    -- contradiction which allowed us to prove
    -- false.
    --
    -- `cases` can do case analysis to prove that a
    -- type with zero constructors allows us to prove
    -- anything we want and discharge the goal.
    cases a,
end

lemma foo' : Type :=
begin
  exact bool,
end

lemma also_bogus:
  1 = 2 → false :=
begin
    intros,
    cases a,
end

/- Note: even equality is defined in Lean and not baked into the theory. -/
#print eq

/- Here's `yo`, another definition of an empty type. -/
inductive yo : Prop
| yolo : yo → yo

/- We will have to do a little more work to
  show that `yo` is empty though. -/
lemma yo_implies_false :
  yo → false :=
begin
  intros,
  cases a,
  cases a,
  cases a,
  -- Now this works!
  induction a,
  assumption
end.

lemma foo :
  yo -> true :=
begin
  intros,
  induction a,
  assumption,
  -- alternatively...
  -- exact true.intro,

end

/- Logical negation in lean is encoded in the `not` type.
   It work similarly to our `yo_implies_false` proof above.
-/
#print not

/- Expression Syntax -/

#check (1 + 2).

#check (1 + (2 : int)).

inductive expr : Type
| int : ℤ → expr
| var : string → expr
| add : expr → expr → expr
| mul : expr → expr → expr
| lte : expr → expr → expr

/-
  On paper, we would typically write this
  type down using a "BNF grammar" as:
<<
    expr ::= Z
          |  Var
          |  expr + expr
          |  expr * expr
          |  expr <= expr
>>
-/

instance nat_to_expr : has_coe ℕ expr :=
⟨ fun n, expr.int n ⟩

instance Z_to_expr : has_coe ℤ expr :=
⟨ fun z, expr.int z ⟩

instance string_to_expr : has_coe string expr :=
⟨ fun str, expr.var str ⟩

notation x `[+]` y := expr.add x y
notation x `[*]` y := expr.mul x y
notation x `[<=]` y := expr.lte x y

#check ((1 : nat) [+] (2 : nat))
#check ("x" [+] (2 : nat))
#check ("x" [+] (2 : nat) [<=] "y")

#check (expr.lte (expr.add (expr.var "x") (expr.int 2)) (expr.var "y")).

/- weird -/
lemma add_comm_bogus :
  (forall e1 e2, expr.add e1 e2 = expr.add e2 e1) ->
  false :=
begin
  intros,
  specialize (a nat.zero "foo"),
  cases a,
end

def number_of_consts : expr → nat
| (expr.int _) := 1
| (expr.var _) := 0
| (expr.add e1 e2) := number_of_consts e1 + number_of_consts e2
| (expr.mul e1 e2) := number_of_consts e1 + number_of_consts e2
| (expr.lte e1 e2) := number_of_consts e1 + number_of_consts e2

#reduce (number_of_consts ((1: nat) [*] (2 : nat) [+] "x" [<=] (5 : nat)))

#print Exists

lemma expr_with_3_consts :
  ∃ e, number_of_consts e = 3 :=
begin
  existsi ((1 : nat) [+] (2 : nat) [+] (3 : nat)),
  -- we need a tactic for chewing through coercions
  simp [coe, lift_t],
  unfold has_lift_t.lift,
  unfold coe_t has_coe_t.coe coe_b has_coe.coe,
  simp [number_of_consts],
end

/- Compute the size of an expression. -/
def expr.size : expr → nat
| (expr.int _) := 1
| (expr.var _) := 1
| (expr.add e1 e2) := expr.size e1 + expr.size e2
| (expr.mul e1 e2) := expr.size e1 + expr.size e2
| (expr.lte e1 e2) := expr.size e1 + expr.size e2

lemma number_of_consts_le_size :
∀ e, number_of_consts e <= e.size :=
begin
  intros, induction e,
  { simp [number_of_consts, expr.size] },
  { simp [number_of_consts, expr.size], tactic.comp_val },
  { simp [number_of_consts, expr.size],
    apply add_le_add ; assumption },
  { simp [number_of_consts, expr.size],
    apply add_le_add ; assumption },
  { simp [number_of_consts, expr.size],
    apply add_le_add ; assumption },
end.

#check (<=) -- hover over me

#print has_le.le

/-
  `le` is a relation defined as an "inductive predicate".

  We give rules for when the relation holds:
  (1) all nats are less than or equal to themselves and
  (2) if n <= m, then also n <= S m.

  All proofs of [le] are built up from just these
  two constructors!

  We can define our own relations
  to encode properties of expressions.
  In the [has_const] inductive predicate
  below, each constructor corresponds to
  one way you could prove that an expression
  has a constant.
-/

inductive has_const : expr → Prop
| int : ∀ z, has_const (expr.int z)
| add_l :
    ∀ e1 e2,
    has_const e1 →
    has_const (expr.add e1 e2)
| add_r :
    ∀ e1 e2,
    has_const e2 ->
    has_const (expr.add e1 e2)
| mul_l :
    ∀ e1 e2,
    has_const e1 ->
    has_const (expr.mul e1 e2)
| mul_r :
    ∀ e1 e2,
    has_const e2 ->
    has_const (expr.mul e1 e2)
| cmp_l :
    ∀ e1 e2,
    has_const e1 ->
    has_const (expr.lte e1 e2)
| cmp_r :
    ∀ e1 e2,
    has_const e2 ->
    has_const (expr.lte e1 e2)

/- Similarly, we can define a relation
  that holds on expressions that contain
  a variable. -/
inductive has_var : expr -> Prop
| var :
    ∀ s, has_var (expr.var s)
| add_l :
    ∀ e1 e2,
    has_var e1 →
    has_var (expr.add e1 e2)
| add_r :
    ∀ e1 e2,
    has_var e2 →
    has_var (expr.add e1 e2)
| mul_l :
    ∀ e1 e2,
    has_var e1 →
    has_var (expr.mul e1 e2)
| mul_r :
    ∀ e1 e2,
    has_var e2 →
    has_var (expr.mul e1 e2)
| cmp_l :
    ∀ e1 e2,
    has_var e1 →
    has_var (expr.lte e1 e2)
| cmp_r :
    ∀ e1 e2,
    has_var e2 →
    has_var (expr.lte e1 e2).

/- A little more magic. -/
attribute [intro] has_var.var
attribute [intro] has_var.add_r
attribute [intro] has_var.add_l
attribute [intro] has_var.add_l
attribute [intro] has_var.mul_l
attribute [intro] has_var.mul_r
attribute [intro] has_var.add_l
attribute [intro] has_var.cmp_r
attribute [intro] has_var.cmp_l
attribute [intro] has_const.int
attribute [intro] has_const.add_l
attribute [intro] has_const.mul_l
attribute [intro] has_const.mul_r
attribute [intro] has_const.add_l
attribute [intro] has_const.cmp_r
attribute [intro] has_const.cmp_l

/- We can also write boolean functions
   that check the same properties.

    Note that `bor` is disjuction over
    booleans: -/

#print ||

def hasConst : expr → bool
| (expr.int _) := bool.tt
| (expr.var _) := bool.ff
| (expr.add e1 e2) := (hasConst e1) || (hasConst e2)
| (expr.mul e1 e2) := (hasConst e1) || (hasConst e2)
| (expr.lte e1 e2) := (hasConst e1) || (hasConst e2)

/- We can write that a little more compactly using
   the `||` notation for `orb` provided by
   the Bool library.
-/

def hasVar : expr → bool
| (expr.int _) := bool.ff
| (expr.var _) := bool.tt
| (expr.add e1 e2) := hasVar e1 || hasVar e2
| (expr.mul e1 e2) := hasVar e1 || hasVar e2
| (expr.lte e1 e2) := hasVar e1 || hasVar e2

/-
  That looks way easier!
  However, as the quarter progresses,
  we'll see that sometime defining a
  property as an inductive relation
  is more convenient.
-/

-- #print yo.induction_on
-- #print yo.rec_on

/-
  We can prove that our relational
  and functional versions agree.
  This shows that the `hasConst` _function_ is COMPLETE
  with respect to the relation `has_const`.
  Thus, anything that satisfies the relation evaluates
  to "true" under the function `hasConst`.
-/
lemma has_const_hasConst:
  ∀ e,
  has_const e ->
  hasConst e = bool.tt :=
begin
  intros,
  induction e,
  case expr.int { simp [hasConst] },
  case expr.var { cases a },
  case expr.add {
    simp [hasConst],
    cases a,
    left,
    apply ih_1,
    assumption,
    right,
    apply ih_2,
    assumption
  },
  case expr.mul {
    simp [hasConst],
    cases a,
    left,
    apply ih_1,
    assumption,
    right,
    apply ih_2,
    assumption
  },
  case expr.lte {
    simp [hasConst],
    cases a,
    left,
    apply ih_1,
    assumption,
    right,
    apply ih_2,
    assumption
  }
end.

/-
  Now for the other direction.

  Here we'll prove that the [hasConst] _function_
  is SOUND with respect to the relation.
  That is, if [hasConst] produces true,
  then there is some proof of the inductive
  relation [has_const].
-/
lemma hasConst_has_const:
  ∀ e,
  hasConst e = bool.tt →
  has_const e :=
begin
  intros,
  induction e,
  case expr.int {
    constructor
  },
  /- Uh oh, no constructor for has_const
     can possibly produce a value of our
     goal type! It's OK though because
     we have a bogus hypothesis. -/
  case expr.var b {
    cases b,
  },
  case expr.add {
    simp [hasConst] at a,
    cases a,
    apply has_const.add_l,
    apply ih_1,
    assumption,
    apply has_const.add_r,
    apply ih_2,
    assumption,
  },
  case expr.mul {
    simp [hasConst] at a,
    cases a,
    apply has_const.mul_l,
    apply ih_1,
    assumption,
    apply has_const.mul_r,
    apply ih_2,
    assumption,
  },
  case expr.lte {
    simp [hasConst] at a,
    cases a,
    apply has_const.cmp_l,
    apply ih_1,
    assumption,
    apply has_const.cmp_r,
    apply ih_2,
    assumption,
  },
end.

-- We can stitch these two lemmas together
lemma has_const_iff_hasConst:
  forall e,
  has_const e <-> hasConst e = bool.tt :=
begin
  intros,
  split,
  apply has_const_hasConst,
  apply hasConst_has_const,
end

/-
  Notice all that work was only for the "true" cases!

  We can prove analogous facts for the "false" cases too.

  Here we will prove the "false" cases directly.
  However, note that you could use [has_const_iff_hasConst]
  to get a much simpler proof. -/
lemma not_has_const_hasConst:
  ∀ e,
  ¬ has_const e ->
  hasConst e = bool.ff :=
begin
  delta not, intros,
  induction e,
  case expr.int {
    exfalso,
    apply a,
    constructor,
  },
  case expr.var { simp [hasConst] },
  case expr.add {
    simp [hasConst],
    split,
    apply ih_1,
    intros,
    apply a,
    constructor,
    exact a_2,
    apply ih_2,
    intros,
    apply a,
    apply has_const.add_r,
    exact a_2,
  },
  case expr.mul {
    simp [hasConst],
    split,
    apply ih_1,
    intros,
    apply a,
    constructor,
    exact a_2,
    apply ih_2,
    intros,
    apply a,
    apply has_const.mul_r,
    exact a_2,
  },
  case expr.lte {
    simp [hasConst],
    split,
    apply ih_1,
    intros,
    apply a,
    constructor,
    exact a_2,
    apply ih_2,
    intros,
    apply a,
    apply has_const.cmp_r,
    exact a_2,
  }
end

/- Here is a more direct proof based on the
   iff we proved for the true case. -/
lemma not_has_const_hasConst':
  ∀ e,
  ¬ has_const e →
  hasConst e = bool.ff :=
begin
  intros,
  destruct (hasConst e); intros,
  { rw has_const_iff_hasConst at a,
    assumption
  },
  { rw has_const_iff_hasConst at a,
    contradiction }
end

/- Now the other direction of the false case -/
lemma false_hasConst_hasConst:
  ∀ e,
  hasConst e = bool.ff ->
  ¬ has_const e :=
begin
  delta not, intros,
  induction e ; intros,
  case expr.int {
    simp [hasConst] at *,
    assumption
  },
  case expr.var {
    cases a_1,
  },
  case expr.add {
    simp [hasConst] at *,
    -- DISCUSS: how do we know to do this?
    cases a; cases a_1 ; auto
  },
  case expr.mul {
    simp [hasConst] at *,
    cases a; cases a_1 ; auto
  },
  case expr.lte {
    simp [hasConst] at *,
    cases a; cases a_1 ; auto
  }
end

/- Since we've proven the ↔ for the true case, we can use
   it to prove the false case. This is the same lemma as above,
   but re-using our previous results. -/
lemma false_hasConst_hasConst':
  ∀ e,
  hasConst e = bool.ff →
  ¬  has_const e :=
begin
  intros,
  -- ¬ X is just X → Fals
  delta not,
  intros,
  rewrite [has_const_iff_hasConst] at a_1,
  rewrite a_1 at a,
  cases a,
end

/- We can also do all the same
   sorts of proofs for has_var and hasVar -/
lemma has_var_hasVar:
  forall e,
  has_var e ->
  hasVar e = bool.tt :=
begin
  intros, induction e,
  case expr.int { cases a },
  case expr.var { simp [hasVar] },
  case expr.add { simp [hasVar], cases a; auto },
  case expr.mul { simp [hasVar], cases a; auto },
  case expr.lte { simp [hasVar], cases a; auto },
end

#print has_var

lemma hasVar_has_var:
  forall e,
  hasVar e = bool.tt ->
  has_var e :=
begin
  intros,
  induction e,
  { cases a, },
  { constructor },
  { simp [hasVar] at a,
    cases a,
    { auto, },
    { auto, },  }
    ...
end

lemma has_var_iff_hasVar:
  ∀ e,
  has_var e <-> hasVar e = bool.tt :=
begin
admit
  -- TODO: try this without copying from above *)
end

/- we can also prove things about expressions -/
lemma expr_bottoms_out:
  forall e,
  has_const e \/ has_var e :=
begin
  intros, induction e,
  case expr.int { left, constructor },
  case expr.var { right, constructor },
  case expr.add { cases ih_1 ; cases ih_2 ; auto },
  case expr.mul { cases ih_1 ; cases ih_2 ; auto },
  case expr.lte { cases ih_1 ; cases ih_2 ; auto },
end

/- `e` could have gotten some of the
    `has_const` lemmas by being a little clever!
    (but then we wouldn't have
     learned as many tactics ;) )
-/

lemma has_const_hasConst':
  forall e,
  has_const e ->
  hasConst e = tt :=
begin
  intros ; induction e ; simp [hasConst] at *,
  cases a,
  cases a; auto,
  cases a; auto,
  cases a; auto
end

-- or even better
lemma has_const_hasConst'':
  forall e,
  has_const e ->
  hasConst e = tt :=
begin
  intros,
  induction a; simp [hasConst]; auto
end

lemma not_has_const_hasConst'':
  forall e,
  ¬ has_const e →
  hasConst e = ff :=
begin
  delta not, intros,
  destruct (hasConst e) ; intros,
  { auto },
  { exfalso, apply a,
    rw has_const_iff_hasConst,
    assumption }
end

lemma false_hasConst_hasConst'':
  forall e,
  hasConst e = ff →
  ¬ has_const e :=
begin
  delta not; intros,
  destruct (hasConst e) ; intros,
  {  rw has_const_hasConst at a,
     contradiction,
     assumption },
  { rw has_const_hasConst at a,
    { contradiction },
    { assumption }
  }
end

/- In general:

   Relational definitions are nice for case analysis.

   Functional defns are nice when you want to
   compute/rewrite with them.
-/

namespace solutions

lemma hasVar_has_var:
  forall e,
  hasVar e = bool.tt ->
  has_var e :=
begin
  intros, induction e,
  case expr.int { cases a },
  all_goals { simp [hasVar] at a; cases a ; auto },
end

lemma has_var_iff_hasVar:
  ∀ e,
  has_var e <-> hasVar e = bool.tt :=
begin
  intros, constructor,
  apply has_var_hasVar,
  apply hasVar_has_var,
end

end solutions

end lec3
