/* Movable and Named Objects on the Cartesian Plane
   H. Conrad Cunningham
   Version #0:  Feb 17 - Mar 4 2012

   This Scala case study is based on a similar Haskell case study
   given in Section 14.6 of the textbook: Simon Thompson. Haskell: The
   Craft of Functional Programming, Third Edition, Addison Wesley,
   2011.

123456789012345678901234567890123456789012345678901234567890123456789012345678

   The Scala code in this case study uses the following Scala features
   and concepts to reimmplment the Haskell program:

   * uses Double instead of Float to represent the values of the x-
     and y-coordinates on the Cartesian plane.

   * uses Scala traits (e.g., Movable, Named) to express the Haskell
     class concept.
   
   * "mixes in" Scala traits to class definitions to express the
     Haskell instance concept.  

   * uses an abstract base class (e.g., Figure) with appropriate cases
     subclasses (e.g., Line, Circle) to express a Haskell algebraic
     data type and its constructors. (Note that, unlike the Haskell
     code, Point is integrated into the Figure algebraic data type.)

   * uses object-oriented function methods instead of the
     multiparameter functions in the Haskell version.
*/


// Movable Objects

/* Vector represents a displacement on the Cartesian plane. The x and
   y coordintes on the Cartesian plane are represented as double
   precision floating point numbers.

   The Haskell code for Vector is as follows:     data Vector = Vec Float Float

   In the Scala code, Vector is both the name of the type and of the
   constructor.
*/

case class Vector(x: Double, y: Double)


/* Movable objects are objects that can be moved on the Cartesian
   plane. 

   The Haskell code for Movable is as follows:

     class Movable a where
       move      :: Vector -> a -> a
       reflectX  :: a -> a
       reflectY  :: a -> a
       rotate180 :: a -> a
       rotate180 = reflectX . reflectY

   The Scala code makes reflectX, reflectY, and rotate180
   parameterless mutator methods for which the Movable object (i.e.,
   the first parameter of the Haskell functions) is the implicit
   parameter (i.e., the receiver or target of the method).

   Similarly, the Scala code makes move a single parameter mutator
   method for which the Movable object (i.e., the second argument of
   the Haskell function) is the implicit parameter. The Scala code makes
   the Vector its explicit argument.

   The Movable trait has a type parameter that is intended to be the
   type of the class that mixes in the trait.
*/

trait Movable[T <: Movable[T]] {
   def move(v: Vector): T
   def reflectX:  T
   def reflectY:  T
   def rotate180: T = this.reflectX.reflectY 
}


/* Figure is a geometric figure on the Cartesian plane. 

   The Haskell code for Figure and Point is as follows:

     data Point  = Point Float Float
                   deriving Show
     data Figure = Line Point Point |
                   Circle Point Float
                   deriving Show

   The Scala code integrates Point into the Figure algebraic data type
   instead of leaving it as a separate algebraic data type. (This was
   motivated by the desire to allow the Scala MovableLists to hold all
   the various Movable objects.) Thus note that the Haskell type Point
   and constructors Line and Circle become Scala subtypes of Figure
   (and, hence, constructors).
*/

abstract class Figure extends Movable[Figure]


/* Point represents a point in the Cartesian plane.  

   The Haskell code for Point, in addition to that given above in the
   comment for Figure, is as follows:

     instance Movable Point where
       move (Vec v1 v2) (Point c1 c2) = Point (c1+v1) (c2+v2)   
       reflectX (Point c1 c2)         = Point c1 (-c2)
       reflectY (Point c1 c2)         = Point (-c1) c2
       rotate180 (Point c1 c2)        = Point (-c1) (-c2)
*/

case class Point(x: Double, y: Double) extends Figure {
  def move(v: Vector)   = Point(x+v.x, y+v.y)
  def reflectX          = Point( x, -y)
  def reflectY          = Point(-x,  y)
  override def rotte180 = Point(-x, -y)
}


/* Line represents a line passing through the two given distinct
   points on the Cartesian plane.

   The Haskell code for Line and Circle, in addition to that given
   above in the comment for Figure, is as follows:

     instance Movable Figure where
       move v (Line p1 p2)   = Line (move v p1) (move v p2)
       move v (Circle p r)   = Circle (move v p) r

       reflectX (Line p1 p2) = Line (reflectX p1) (reflectX p2)
       reflectX (Circle p r) = Circle (reflectX p) r
       
       reflectY (Line p1 p2) = Line (reflectY p1) (reflectY p2)
       reflectY (Circle p r) = Circle (reflectY p) r

   The Scala constructor for Line checks to ensure the defining Points
   are distinct, which is not done by the Haskell code.
*/

case class Line(p1: Point, p2: Point) extends Figure {
  require (p1 != p2)
  def move(v: Vector) = Line(p1 move v,   p2 move v)
  def reflectX        = Line(p1.reflectX, p2.reflectX)
  def reflectY        = Line(p1.reflectY, p2.reflectY)
}


/* Circle represents a circle on the Cartesian plane with the given
   centerpoint and radius.

   The Haskell code for Circle is given above in the comments for
   Figure and Line.

   The Scala constructor checks to ensure the radius is not negative,
   which is not done by the Haskell code.
*/

case class Circle(center: Point, radius: Double) extends Figure {
  require (radius >= 0.0)
  def move(v: Vector) = Circle(center move v,   radius)
  def reflectX        = Circle(center.reflectX, radius) 
  def reflectY        = Circle(center.reflectY, radius)
}


/* MovableList represents a List of Movable objects on the Cartesian
   plane.  The list is moved by moving all the objects together.

   The Haskell code for lists that are Movable is as follows:

     instance Movable a => Movable [a] where
       move v   = map (move v)
       reflectX = map reflectX
       reflectY = map reflectY

   The Scala code causes a MovableList object to be created that wraps
   the list. (The Haskell instance of "movable list" seems to just be
   a compile-time entity.)

   The implicit conversions defined in the object MovableListImplicits
   enable the Scala compiler to use MovableLists as Scala Lists and
   Scala Lists of Movable objects as MovableLists. Note the use of the
   import to bring the conversion functions into scope.
*/

import MovableListImplicits._

case class MovableList[T <: Movable[T]](l: List[T]) 
     extends Movable[MovableList[T]] {
  def move(v: Vector) = MovableList( l map (_ move v)   )
  def reflectX        = MovableList( l map (_.reflectX) )
  def reflectY        = MovableList( l map (_.reflectY) )
}

object MovableListImplicits {
  implicit def toMovableList[T <: Movable[T]] (l: List[T]): MovableList[T] 
    = MovableList(l)
  implicit def toList[T <: Movable[T]] (ml: MovableList[T]): List[T]
    = ml.l
}


// Named Objects

/* Named objects are objects with associated name strings.

   The Haskell code for Named is as follows:

     class Named a where
       lookName :: a -> String
       giveName :: String -> a -> a

   The Scala code makes lookName a parameterless accessor method for
   which the Named object (i.e., parameter of the Haskell function) is
   the implicit parameter. 

   Smilarly, the Scala code makes giveName a single parameter mutator
   method for which the Named object (i.e., second parameter of the
   Haskell function) is the implicit parameter and the new name string
   (i.e., first parameter of the Haskell function) is the explicit
   parameter.

   The Named trait has a type parameter that is intended to be the
   type of the class that mixes in the trait.

*/

trait Named[T <: Named[T]] {
  def lookName: String
  def giveName(n: String): T
}


/* Class Name defines a concrete class of the Named type.

   The Haskell code for Name is as follows:

     data Name = Pair a String
 
     instance Named (Name a) where]
       lookName (Pair obj nm)   = nm
       giveName nm (Pair obj _) = (Pair obj nm)

     mapName :: (a -> b) -> Name a -> Name b
     mapName f (Pair obj nm) = Pair (f obj) nm

   The Scala code incorporates mapName into the Name class as a
   mutator method that takes the element transformation function as
   its explicit parameter.

   The type parameter of the Name class is the object type that is
   named by the name string.

   Issues: 

   (1) Should mapName be incorporated into the Named trait? That seems
   to make the generics complex for that trait.

   (2) If Name is to be the only subclass of Named, do we really need
   Named in teh Scala design?

*/

case class Name[T](obj: T, name: String) extends Named[Name[T]] {
  def lookName                    = name
  def giveName(nm: String)        = Name(obj, nm)
  def mapName[U](f:T=>U): Name[U] = Name(f(obj),name)
}


// Named Movable Objects

/* MovableNames are Names that are also Movable.

   The Haskell code for "movable names" is as follows:

     instance Movable a => Movable (Name a) where
       move v   = mapName (move v)
       reflectX = mapName reflectX
       reflectY = mapName reflectY

     class (Movable b, Named b) => NamedMovable b

     instance Movable a => NamedMovable (Name a)

   CURRENTLY, the NamedMovable class and its instance are not implemented.

   The Scala code causes a MovableName object to be created that wraps
   the Name. (The Haskell instance of "movable name" seems to just be
   a compile-time entity.)

   The implicit conversions defined in the object MovableNameImplicits
   enable the Scala compiler to use MovableNames as Names and Names as
   MovableNames. Note the use of the import to bring the conversion
   functions into scope.

*/

import MovableNameImplicits._

case class MovableName[T <: Movable[T]] (n: Name[T]) 
    extends Movable[MovableName[T]] {
  def move(v: Vector) = MovableName( n mapName (_ move v)   )
  def reflectX        = MovableName( n mapName (_.reflectX) )
  def reflectY        = MovableName( n mapName (_.reflectY) )
}
  
object MovableNameImplicits {
  implicit def toMovableName[T <: Movable[T]] (n: Name[T]): MovableName[T]
    = MovableName(n)
  implicit def toName[T <: Movable[T]](m: MovableName[T]): Name[T]
    = m.n
}


/* Some PRELIMINARY testing code  */

object MovableTest {

  def main(args: Array[String]) {

    // Define test data
    val disp: Vector               = Vector(10.0, 10.0)
    val origin: Point              = Point(0.0,0.0)
    val unit: Point                = Point(1.0,1.0)
    val diag: Line                 = Line(origin,unit)
    val circ02: Circle             = Circle(origin,2.0)
    val circ12: Circle             = Circle(unit,2.0)
    val list: List[Figure]         = List[Figure](diag,circ12,unit)
    val mlist: MovableList[Figure] = MovableList(list)

    // Display test data
    println("\nTest data for Figures")
    println("disp             == " + disp)
    println("origin           == " + origin)
    println("unit             == " + unit)
    println("diag             == " + diag)
    println("circ02           == " + circ02)
    println("circ12           == " + circ12)
    println("list             == " + list)
    println("mlist            == " + mlist)

    // Test Point's Movable methods
    println("\nTesting Movable methods of Point")
    println("disp             == " + disp)
    println("origin           == " + origin)
    println("unit             == " + unit)
    println("origin move disp == " + (origin move disp))
    println("unit.reflectX    == " + (unit.reflectX))
    println("unit.reflectY    == " + (unit.reflectY))
    println("unit.rotate180   == " + (unit.rotate180))

    // Test Line's Movable methods
    println("\nTesting Movable methods of Line")
    println("disp             == " + disp)
    println("diag             == " + diag)
    println("diag move disp   == " + (diag move disp))
    println("diag.reflectX    == " + (diag.reflectX))
    println("diag.reflectY    == " + (diag.reflectY))
    println("diag.rotate180   == " + (diag.rotate180))

    // Test Circle Movable methods
    println("\nTesting Movable methods of Circle")
    println("disp             == " + disp)
    println("circ02           == " + circ02)
    println("circ12           == " + circ12)
    println("circ02 move disp == " + (circ02 move disp))
    println("circ12.reflectX  == " + (circ12.reflectX))
    println("circ12.reflectY  == " + (circ12.reflectY))
    println("circ12.rotate180 == " + (circ12.rotate180))

    // Test MovableList Movable methods
    println("\nTesting Movable methods of MovableList")
    println("disp             == " + disp)
    println("mlist            == " + mlist)
    println("mlist move disp  == " + (mlist move disp))
    println("mlist.reflectX   == " + (mlist.reflectX))
    println("mlist.reflectY   == " + (mlist.reflectY))
    println("mlist.rotate180  == " + (mlist.rotate180))

    // Teat MovableList methods with implicit conversion from lists
    println("\nTesting Movable methods of MovableList with conversions")
    println("disp             == " + disp)
    println("list             == " + list)
    println("list move disp   == " + (list move disp))
    println("list.reflectX    == " + (list.reflectX))
    println("list.reflectY    == " + (list.reflectY))
    println("list.rotate180   == " + (list.rotate180))

    // Teat List methods with implicit conversions from MovableLists
    println("\nTesting List methods of MovableList with conversions")
    println("mlist            == " + mlist)
    println("mlist.head       == " + (mlist.head))
    println("mlist.tail       == " + (mlist.tail))
    println("mlist.tail       == " + (mlist.reverse))

    // Test Name's Named methods
    println("\nTesting Named methods of Name")
    val aa: Name[Point] = Name(unit,"AA")
    println("aa               == " + aa)
    println("aa.lookName      == " + (aa.lookName))
    println("aa giveName \"BB\" == " + (aa giveName "BB"))

    // Test Name's mapName method
    println("\nTesting mapName method of Name")
    println("aa               == " + aa)
    println("aa.mapName((p:Point) => Point(100.0,100.0))")
    println("                 == " + 
        aa.mapName((p: Point) => Point(100.0,100.0)))

    // Test Movable methods of MovableName
    println("\nTesting Movable methods of MovableName")
    val mn = MovableName(aa)
/*
    println("disp             == " + disp)
    println("mn               == " + mn)
    println("mn move disp     == " + (mn move disp))
    println("mn.reflectX      == " + (mn.reflectX))
    println("mn.reflectY      == " + (mn.reflectY))
    println("mn.rotate180     == " + (mn.rotate180))

    // Test Named methods of MovableName using conversions
    println("\nTesting Named methods of MovableName using conversions")
    println("aa               == " + aa)
    println("mn               == " + mn)
    println("mn.lookName      == " + (mn.lookName))
    println("mn giveName \"BB\" == " + (mn giveName "BB"))
*/

  } 
}

