package expAbstractData

/** A base class consisting of
 *   - a root trait (i.e. abstract class) `Exp' with an `eval' function
 *   - an abstract type `exp' bounded by `Exp'
 *   - a concrete instance class `Num' of `Exp' for numeric literals
 */
trait Base {
  type exp <: Exp

  trait Exp { def eval: int }

  trait Num extends Exp {
    val value: int
    def eval = value
  }
}

object testBase extends Base with Application {
  type exp = Exp
  val term = new Num { val value = 1 }
  System.out.println(term.eval)
}

/** Data extension 1: An extension of `Base' with `Plus' expressions
 */
trait BasePlus extends Base {
  trait Plus extends Exp {
    val left: exp
    val right: exp
    def eval = left.eval + right.eval
  }
}

/** Data extension 2: An extension of `Base' with negation expressions
 */
trait BaseNeg extends Base {
  trait Neg extends Exp {
    val operand: exp
    def eval = - operand.eval
  }
}

/** Combining the plus and negation data extensions
 */
trait BasePlusNeg extends BasePlus with BaseNeg

/** A test object for the combination class.
 *  It ``ties the knot'' by equating the abstract type `exp' with
 *  the root class `Exp'.
 */
object testBasePlusNeg extends BasePlusNeg with Application {
  type exp = Exp
  val term = new Neg {
    val operand = new Plus {
      val left = new Num { val value = 1 }
      val right = new Num { val value = 2 }
    }
  }
  System.out.println(term.eval)
}

///////////////////////////////////////////////////////////////////

/** Operation extension 1: An extension of `Base' with a `show' function.
 *  This class needs
 *    - a new root class `Exp' which adds a `show' method to `super.Exp',
 *      the old root class in `Base', 
 *    - a rebinding of the abstract type `exp' to be a subtype of the new root class,
 *    - a new class for numeric literals which adds a `show' method to `super.Num'.
 */
trait Show extends Base {
  type exp <: Exp

  trait Exp extends super.Exp {
    def show: String
  }
  trait Num extends super.Num with Exp {
    def show = value.toString()
  }
}

/** Combining operation extension 1 with the data extensions:
 *    - we only need to implement `show' for the two extension classes 
 *      `Plus` and `Neg`.
 */
trait ShowPlusNeg extends BasePlusNeg with Show {
  trait Plus extends super.Plus with Exp {
    def show = left.show + "+" + right.show
  }
  trait Neg extends super.Neg with Exp {
    def show = "-(" + operand.show + ")"
  }
}

/** Testing the resulting combination.
 */
object testShowPlusNeg extends ShowPlusNeg with Application {
  type exp = Exp
  val term = new Neg {
    val operand = new Plus {
      val left = new Num { val value = 1 }
      val right = new Num { val value = 2 }
    }
  }
  System.out.println(term.show + " = " + term.eval)
}

/*
object error {
  val t1 = new testShowPlusNeg.Num { val value = 1 }
  val t2 = new testDblePlusNeg.Neg { val value = t1 }
  val t3 = t1.dble
}
*/  

////////////////////////////////////////////////////////////////////////

/** Operation extension 2: An extension of `BasePlusNeg' with a `dble' method,
 *  which returns an expression tree representing the original tree times two.
 *  - The extension class redefines all data classes of `BasePlusNeg', adding
 *    a `dble' method to each.
 *  - Note that we need factory methods to build expression trees of the right
 *    type (either DblePlusNeg.Exp, or an extension thereof).
 */
trait DblePlusNeg extends BasePlusNeg {
  type exp <: Exp

  trait Exp extends super.Exp {
    def dble: exp
  }

  def Num(v: int): exp
  def Plus(l: exp, r: exp): exp
  def Neg(t: exp): exp

  trait Num extends super.Num with Exp {
    def dble = Num(value * 2)
  }
  trait Plus extends super.Plus with Exp {
    def dble = Plus(left.dble, right.dble)
  }
  trait Neg extends super.Neg with Exp {
    def dble = Neg(operand.dble)
  }
}

/** Testing the resulting combination
 */
object testDblePlusNeg extends DblePlusNeg with Application {
  type exp = Exp
  def Num(v: int): exp = new Num { val value = v }
  def Plus(l: exp, r: exp): exp = new Plus { val left = l; val right = r}
  def Neg(t: exp): exp = new Neg { val operand = t }
  val term = Neg(Plus(Num(1), Num(2)))
  System.out.println(term.dble.eval)
}

/** Combining both operation extensions:
 *  - This is done by a composing corresponding data classes of each extension.
 */
trait ShowDblePlusNeg extends ShowPlusNeg with DblePlusNeg {
  type exp <: Exp

  trait Exp extends super[ShowPlusNeg].Exp with super[DblePlusNeg].Exp

  trait Num 
    extends super[ShowPlusNeg].Num 
       with super[DblePlusNeg].Num 
       with Exp

  trait Plus 
    extends super[ShowPlusNeg].Plus
       with super[DblePlusNeg].Plus
       with Exp

  trait Neg
    extends super[ShowPlusNeg].Neg 
       with super[DblePlusNeg].Neg
       with Exp
}

/** Testing the resulting combination
 */
object testShowDblePlusNeg extends ShowDblePlusNeg with Application {
  type exp = Exp
  def Num(v: int): exp = new Num { val value = v }
  def Plus(l: exp, r: exp): exp = new Plus { val left = l; val right = r }
  def Neg(t: exp): exp = new Neg { val operand = t }
  val term = Neg(Plus(Num(1), Num(2)))
  System.out.println("2 * (" + term.show + ") = " + term.dble.eval)
}

/////////////////////////////////////////////////////////////

/** Binary methods
*/
trait Equals extends Base {
  type exp <: Exp
  trait Exp extends super.Exp {
    def eql(other: exp): boolean
    def isNum(v: int) = false
  }
  trait Num extends super.Num with Exp {
    def eql(other: exp): boolean = other.isNum(value)
    override def isNum(v: int) = v == value
  }
}

trait EqualsPlusNeg extends BasePlusNeg with Equals {
  type exp <: Exp
  trait Exp extends super[BasePlusNeg].Exp with super[Equals].Exp {
    def isPlus(l: exp, r: exp): boolean = false
    def isNeg(t: exp): boolean = false
  }
  trait Num extends super[Equals].Num with Exp
  trait Plus extends Exp with super.Plus {
    def eql(other: exp): boolean = other.isPlus(left, right)
    override def isPlus(l: exp, r: exp) = (left eql l) && (right eql r)
  }
  trait Neg extends Exp with super.Neg {
    def eql(other: exp): boolean = other.isNeg(operand)
    override def isNeg(t: exp) = operand eql t
  }
}

/** Testing the resulting combination
 */
object testEqualsPlusNeg extends EqualsPlusNeg with Application {
  type exp = Exp
  val term = new Neg {
    val operand = new Plus {
      val left = new Num { val value = 1 }
      val right = new Num { val value = 2 }
    }
  }
  System.out.println(term eql term)
  System.out.println(term eql new Num { val value = 1 })
}

////////////////////////////////////////////////////////////////////

trait EqualsShowPlusNeg extends EqualsPlusNeg with ShowPlusNeg {
  type exp <: Exp

  trait Exp extends super[EqualsPlusNeg].Exp with super[ShowPlusNeg].Exp

  trait Num
    extends super[EqualsPlusNeg].Num
       with super[ShowPlusNeg].Num
       with Exp

  trait Plus 
    extends super[EqualsPlusNeg].Plus
       with super[ShowPlusNeg].Plus
       with Exp

  trait Neg 
    extends super[EqualsPlusNeg].Neg
       with super[ShowPlusNeg].Neg
       with Exp
}

/** Testing the resulting combination
 */
object testEqualsShowPlusNeg extends EqualsPlusNeg with Application {
  type exp = Exp
  val term = new Neg {
    val operand = new Plus {
      val left = new Num { val value = 1 }
      val right = new Num { val value = 2 }
    }
  }
  val term2 = new Neg { val operand = new Num { val value = 1 } }
  System.out.println(term eql term)
  System.out.println(term eql term2)
}


