/*
  (Functional) Data representation in Scala
 
 
A few syntax basics:
 
 */
 
// we define a class (called Point)
// we define the constructor for the class:
// Point(1,2)
class Point(x: Int, y: Int){
 
  def xVal:Int  = x
  def yVal:Int = y
  // checks if a point is higher on the y axis than our current point
 // higher is method of the class Point
  def higher(p: Point): Boolean =
   p.yVal > y
 
  override def toString : String =
    s"({},{})"
}
 
 
val p = new Point(1,2)
p.higher(new Point(2,3))
 
new Point(2,1) // this is an expression that evaluates to an object
new Point(2,1) == new Point(2,1) // evaluates to FALSE
 
// classes in Scala are VERY similar to those in Java
 
/*
Traits in Scala are VERY similar to Java interfaces
 
 */
 
 
trait FTree {
  /* why do we not define methods, such as
    isEmpty and size as MEMBERS of FTree?
 
    is it possible? (YES)
   */
}
 
case object FEmpty extends FTree {}
case class FNode(key: Int, left: FTree, right: FTree) extends FTree {}
 
FEmpty == FEmpty // should return true
 
val t1 = FNode(7,
          FNode(1,FEmpty,FEmpty),
          FNode(2,FEmpty,FEmpty))
/*
   We have created several identical object called FEmpty previously
   We would like to have a single object instead of 4
   We can use the "object" keyword.
 
 */
 
FNode(1,FEmpty,FEmpty) == FNode(1,FEmpty,FEmpty) // will return true
 
/* How to we DECOMPOSE or "LOOK INTO" objects?
*  using pattern matching
* */
 
def isEmpty(t: FTree): Boolean =
  t match {
    case FNode(_,_,_) => false
    case FEmpty => true
    //case _ => false
 
  }
 
def size(t: FTree): Int =
  t match{
    case FEmpty => 0
    case FNode(_,left,right) => 1 + size(left) + size(right)
  }
 
/* ANOTHER implementation of Trees
   which is structurally equivalent, but the type
   is differently organised.
 */
 
trait OOTree {
  def isEmpty: Boolean
  def size: Int
}
 
case object OOEmpty extends OOTree {
  override def isEmpty: Boolean = true
  override def size: Int = 0
}
 
case class OONode (key: Int, left: OOTree, right: OOTree) extends OOTree {
  override def isEmpty: Boolean = false
  override def size: Int = 1 + left.size + right.size
}
 
 
val t2 = OONode(7,
          OONode(1,OOEmpty,OOEmpty),
          OONode(2,OOEmpty,OOEmpty))
 
/*
*  What is the difference between: FTree and OOTree?
*
*  isEmpty and size are MEMBERS of OOTree while
*   ----- || ------ are just function of FTree
*
*  THE FIRST IMPLEMEMENTATION (FTree), relies on
*  pattern-matching, to DECOMPOSE the object
*
*  THE SND IMPL. (OOTree), uses "methods"
*  */
 
t2.isEmpty
t2.size
 
 
trait Expr {
  def eval: Int
}
 
case class Val(i: Int) extends Expr{
  override def eval:Int = i
}
 
case class Add(left: Expr, right: Expr) extends Expr {
  override def eval: Int = left.eval + right.eval
}
 
case class Mult(left: Expr, right: Expr) extends Expr {
  override def eval: Int = left.eval * right.eval
}
 
val e = Add(Val(2),Mult(Val(3),Val(4)))
e.eval