/*
--- CSU213 Spring 2006 Lecture Notes ---------
Copyright 2006  Viera K. Proulx

Lecture 8: How Do We Compare???

Goals:

 - Designing methods that produce instances of a variant in a union
 - Comparing instances of a union

Introduction:

The following class diagram represents geometric shapes - such as we may 
use to draw scenes in a game. The square is given by its North West corner
and its size, the trianlge will always be a right-angle triangle with the
given South West corner and the given width and height:

   |\
   | \
   |  \
   -----

Here are some example of shapes:

  Posn p1 = new Posn(100, 100);
  Posn p2 = new Posn(80, 120);
  
  Shape s1 = new Square(this.p1, 20);
  Shape c1 = new Circle(this.p1, 30);
  Shape t1 = new Triangle(this.p2, 40, 50);

To program simple animations we need to be able to move a shape - to 
produce the another shape of the same size at a new location. The purpose
and the header for this method is:

  // produce a new shape by moving this shape a given distance 
  Shape move(int dx, int dy)

Here are some example of the use of the move method:

s1.move(10, 20) ---> new Square(new Posn(110, 120), 20);
c1.move(-20, 20) ---> new Circle(this.p2, 30);
t1.move(20, 10) ---> new Triangle(new Posn(100, 130), 40, 50);

Here are the templates for the methods in each class:

// Template for the class Square:
  ... this.nw ...       -- Posn
  ... this.size ...     -- int

// Template for the class Circle:
  ... this.center ...   -- Posn
  ... this.radius ...   -- int
  
// Template for the class Triangle:
  ... this.sw ...       -- Posn
  ... this,height ...   -- int
  ... this.width ...    -- int

In each case we need a new Posn, moved by the given distance, but there
is no such data in the template. We need to delegate to the class Posn
to produce a new Posn moved by a given distance. (Imagine, if the 
location of the Posn was recorded in polar coordinates -- it is not
our business to worry about that - the class Posn needs to be in control.)

We leave it to the reader to design the method 'move' in the class Posn.

We get:

  // move this Posn by the given distance
  Posn move(int dx, int dy){
    return new Posn(this.x + dx, this.y + dy);
  }

with the following examples:

this.p1.move(10, 20) ---> new Posn(110, 120)
this.p1.move(-20, 20) ---> this.p2
this.p2.move(20, 10) ---> new Posn(100, 130)

To test this method we need to compare two instances of the Posn class. 
We need to make sure the values of the two fields are the same. This 
suggest we need the method 'samePosn' that compares this Posn with 
that given Posn:

  // is this Posn the same as the given Posn?
  boolean samePosn(Posn that) {
    return this.x == that .x && this.y == that.y;
  }

We can now test our examples: 

  // tests for the method samePosn in the class Posn
  boolean testSamePosn1 = this.p1.samePosn(this.p2) == false;
  boolean testSamePosn2 = this.p1.samePosn(new Posn(100, 100)) == true;

  // tests for the method move in the class Posn
  boolean testMovePosn1 = this.p1.move(10, 20).samePosn(new Posn(110, 120));
  boolean testMovePosn2 = this.p1.move(-20, 20).samePosn(this.p2);
  boolean testMovePosn3 = this.p2.move(20, 10).samePosn(new Posn(100, 130));

Now we can get back to designing the 'move' method for the classes that
represent Shape-s. Here are the templates for each class --- taking into 
account the fact that the Posn class now has a methods 'move' and 'samePosn':


// Template for the class Square:
  ... this.nw ...                 -- Posn
  ... this.size ...               -- int

  ... this.nw.move(int, int) ...  -- Posn
  ... this.nw.samePosn(Posn) ...  -- boolean

// Template for the class Circle:
  ... this.center ...                 -- Posn
  ... this.radius ...                 -- int

  ... this.center.move(int, int) ...  -- Posn
  ... this.center.samePosn(Posn) ...  -- boolean

// Template for the class Triangle:
  ... this.sw ...                 -- Posn
  ... this.height ...             -- int
  ... this.width ...              -- int

  ... this.sw.move(int, int) ...  -- Posn
  ... this.nw.samePosn(Posn) ...  -- boolean


and the method bodies follow:

  // produce a new shape by moving this square a given distance 
  Shape move(int dx, int dy) {
    return new Square(this.nw.move(dx, dy), this.size);
  }

  // produce a new shape by moving this circle a given distance 
  Shape move(int dx, int dy) {
    return new Circle(this.center.move(dx, dy), this.radius);
  }

  // produce a new shape by moving this triangle a given distance 
  Shape move(int dx, int dy) {
    return new Triangle(this.sw.move(dx, dy), this.height, this.width);
  }
  
Of course, we need to rewrite the examples as tests. This is what we expect:

  // tests for the method move in the Shape classes
  boolean testMoveSquare = 
    this.s1.move(10, 20).sameShape(new Square(new Posn(110, 120), 20));
  boolean testMoveCircle = 
    this.c1.move(-20, 20).sameShape(new Circle(this.p2, 30));
  boolean testMoveTriangle = 
    this.t1.move(20, 10).sameShape(new Triangle(new Posn(100, 130), 40, 50));

However, just as in the case of Posn-s, we need to design the method
'sameShape' that compares this shape with the given shape and determines
whether they represent the same shape.

The method header and purpose are easy:

  // is this shape the same as that given shape
  boolean sameShape(Shape that)

We recall our examples of shapes and add a couple more:

  Shape s1 = new Square(this.p1, 20);
  Shape c1 = new Circle(this.p1, 30);
  Shape t1 = new Triangle(this.p2, 40, 50);
  
  Shape s2 = new Square(this.p2, 20);
  Shape c2 = new Circle(this.p2, 50);
  Shape t2 = new Triangle(this.p2, 30, 50);

We now can make examples for the method sameShape:

  s1.sameShape(s2) ---> false
  s1.sameShape(new Square(p1, 20)) ---> true

  c1.sameShape(c2) --> false
  c1.sameShape(new Circle(p1, 30)) ---> true

  t1.sameShape(t2) ---> false
  t1.sameShape(new Triangle(p2, 40, 50)) ---> true)

  s1.sameShape(c1) ---> false
  c1.sameShape(t1) ---> false
  t1.sameShape(s1) ---> false

We now look at the templates for the three classes, that includes
the argument to the method sameShape:

// Template for the class Square:
  ... this.nw ...                 -- Posn
  ... this.size ...               -- int

  ... this.nw.move(int, int) ...  -- Posn
  ... this.nw.samePosn(Posn) ...  -- boolean

  ... that ...                    -- Shape
  ... that.move(int, int)         -- Shape
  ... that.sameShape(Shape)       -- boolean

// Template for the class Circle:
  ... this.center ...                 -- Posn
  ... this.radius ...                 -- int

  ... this.center.move(int, int) ...  -- Posn
  ... this.center.samePosn(Posn) ...  -- boolean

  ... that ...                    -- Shape
  ... that.move(int, int)         -- Shape
  ... that.sameShape(Shape)       -- boolean

// Template for the class Triangle:
  ... this.sw ...                 -- Posn
  ... this.height ...             -- int
  ... this.width ...              -- int

  ... this.sw.move(int, int) ...  -- Posn
  ... this.nw.samePosn(Posn) ...  -- boolean

  ... that ...                    -- Shape
  ... that.move(int, int)         -- Shape
  ... that.sameShape(Shape)       -- boolean

But the example hint that we need to first know whether we are comparing 
a square with a square, or a square with some shape, a circle with another 
circle, or with some other shape, etc. Let us see, whether adding a helper 
method that compares one Square with another Square can help.

The design of the method is just like what we did for Posn and we get:

  // is this square the same as the given square?
  boolean sameSquare(Square s) {
    return this.nw.samePosn(that.nw) &&
           this.size == that.size;
  }

We need to convert the first two tests for sameShape to tests for
the method sameSquare. However, in the test 

  s1.sameSquare(s2) ---> false

the only thing we can tell about s2 is that its type is declared to be
'Shape'. We could have defined its value to be an instance of a Circle,
and that would make our test faulty. So, we must make sure that both
pieces of data involved in the test can only be instances of the Square
class. The valid tests will then be:

  s1.sameSquare(new Square(p2, 20)) ---> false
  s1.sameSquare(new Square(p1, 20)) ---> true

The problem we face that the method sameSquare has to be invoked
by an instance of the class Square, and the only kind of data
available in the template is the Shape 'that'. We decide to 
define the method 'sameSquare' for all three classes (and include it
in the interface Shape. Of course, comparing a Circle with a Square,
of a Triangle with a given Square always results in false:

  // in the class Square:
  // is this square the same as the given square?
  boolean sameSquare(Square s) {
    return this.nw.samePosn(that.nw) &&
           this.size == that.size;
  }

  // in the class Circle:
  // is this circle the same as the given square?
  boolean sameSquare(Square s) { return false; }

  // in the class Triangle:
  // is this triangle the same as the given square?
  boolean sameSquare(Square s) { return false; }

We need to run all the tests to make sure these methods work as
expected.

Now that we have this method, let us look at the template for the 
class Square, when designing the method sameShape:

// Template for the class Square:
  ... this.nw ...                 -- Posn
  ... this.size ...               -- int

  ... this.nw.move(int, int) ...  -- Posn
  ... this.nw.samePosn(Posn) ...  -- boolean

  ... that ...                    -- Square
  ... that.move(int, int)         -- Shape
  ... that.sameShape(Shape)       -- boolean

  ... that.sameSquare(Square)     -- boolean

the last entry says that the object 'that' of the type Square can invoke 
the method sameSquare, as long as we can supply an argument of the type 
Square. But 'this' is a Square, and so 

   that.sameSquare(this) 

compares the instance of Square represented by 'that' with the instance
of Square represented by 'this' - which is our goal. If 'that' is a Circle
or a Triangle, it will invoke the method sameSquar in the classes Circle
or Triangle respectively, producing 'false' in either case. If both 'that'
and 'this' are squares, a true comparison of the fields will be done to 
determine whether these two square shapes are the same. This completes the 
body of the method 'sameShape' in the class Square:

  // in the class Square:
  // is this shape the same as that shape?
  boolean sameShape(Shape that){
    return that.sameSquare(this);
  }

To complete the design, we need to do the same in the Circle class, and 
add the method 'sameCircle' to all classes, as well as add the method 
'sameTriangle' to all classes. Do not forget the tests!!! 

The complete code with examples is shown below. Of course, we can now, finally, 
run the tests for the method 'move'.

           +--------------------------------------+
           | interface:                           |
           | Shape                                |
           +--------------------------------------+
           | Shape move(int dx, int dy);          |
           | boolean sameShape(Shape that);       |
           | boolean sameSquare(Square that);     |
           | boolean sameCircle(Circle that);     |
           | boolean sameTriangle(Triangle that); |
           +--------------------------------------+
                         / \                         
                          |                          
             - - - - - - - - - - - - - - - - - - - -       
             |                   |                 |         
  +---------------------------+  |     +---------------------------+  
  | Square                    |  |     | Triangle                  |  
  +---------------------------+  |     +---------------------------+  
+-| Posn nw                   |  |  +--| Posn sw                   |  
| | int size                  |  |  |  | int height                |  
| +---------------------------+  |  |  | int weight                |  
| | Shape move(...)           |  |  |  +---------------------------+ 
| | boolean sameShape(...)    |  |  |  | Shape move(...)           |
| | boolean sameSquare(...)   |  |  |  | boolean sameShape(...)    |    
| | boolean sameCircle(...)   |  |  |  | boolean sameSquare(...)   |  
| | boolean sameTriangle(...) |  |  |  | boolean sameCircle(...)   |  
| +---------------------------+  |  |  | boolean sameTriangle(...) |
|                                |  |  +--------------------------+
|                                |  +------+
|                                v         |
|          +---------------------------+   |
|          | Circle                    |   |
|          +---------------------------+   |
|      +---| Posn center               |   |
|      |   | int radius                |   |
|      |   +---------------------------+   |
|      |   | Shape move(...)           |   |
|      |   | boolean sameShape(...)    |   |
|      |   | boolean sameSquare(...)   |   |
|      |   | boolean sameCircle(...)   |   |
|      |   | boolean sameTriangle(...) |   |
|      |   +---------------------------+   |
+---+  |                                   |
    |  |  +--------------------------------+
    |  |  |
    v  v  v
   +-------+
   | Posn  |
   +-------+
   | int x |
   | int y |
   +-------+
                                                  

*/

// to represent a shape
interface Shape {
/*
  // ** DRAFT TEMPLATE ** Edit as needed.
  // purpose statement 
  ??? mmm();
*/

  // produce a new shape by moving this shape a given distance 
  Shape move(int dx, int dy); 

  // is this shape the same as that shape?
  boolean sameShape(Shape that);

  // is this shape the same as that square?
  boolean sameSquare(Square that);

  // is this shape the same as that Circle?
  boolean sameCircle(Circle that);

  // is this shape the same as that triangle?
  boolean sameTriangle(Triangle that);
}

// to represent a square
class Square implements Shape {
  Posn nw;
  int size;

  Square(Posn nw, int size) {
    this.nw = nw;
    this.size = size;
  }

/*
  // TEMPLATE:
  ??? mmm() {
  ... this.nw ...                 -- Posn
  ... this.size ...               -- int

  ... this.nw.move(int, int) ...  -- Posn
  ... this.nw.samePosn(Posn) ...  -- boolean
  }
*/

  // produce a new square by moving this square a given distance 
  Shape move(int dx, int dy) {
    return new Square(this.nw.move(dx, dy), this.size);
  }

  // is this shape the same as that shape?
  boolean sameShape(Shape that){
    return that.sameSquare(this);
  }

  // is this shape the same as that square?
  boolean sameSquare(Square that){
    return this.nw.samePosn(that.nw) &&
           this.size == that.size;
  }

  // is this shape the same as that Circle?
  boolean sameCircle(Circle that){
    return false;
  }

  // is this shape the same as that triangle?
  boolean sameTriangle(Triangle that){
    return false;
  }
}

// to represent a circle
class Circle implements Shape {
  Posn center;
  int radius;

  Circle(Posn center, int radius) {
    this.center = center;
    this.radius = radius;
  }

/*
  // TEMPLATE:
  ??? mmm() {
  ... this.center ...                 -- Posn
  ... this.radius ...                 -- int

  ... this.center.move(int, int) ...  -- Posn
  ... this.center.samePosn(Posn) ...  -- boolean
  }
*/

  // produce a new circle by moving this circle a given distance 
  Shape move(int dx, int dy) {
    return new Circle(this.center.move(dx, dy), this.radius);
  }

  // is this shape the same as that shape?
  boolean sameShape(Shape that){
    return that.sameCircle(this);
  }

  // is this shape the same as that square?
  boolean sameSquare(Square that){
    return false;
  }

  // is this shape the same as that Circle?
  boolean sameCircle(Circle that){
    return this.center.samePosn(that.center) &&
           this.radius == that.radius;
  }

  // is this shape the same as that triangle?
  boolean sameTriangle(Triangle that){
    return false;
  }
}

// to represent a triangle
class Triangle implements Shape {
  Posn sw;
  int height;
  int width;

  Triangle(Posn sw, int height, int width) {
    this.sw = sw;
    this.height = height;
    this.width = width;
  }

/*
  // TEMPLATE :
  ??? mmm() {
  ... this.sw ...                 -- Posn
  ... this.height ...             -- int
  ... this.width ...              -- int

  ... this.sw.move(int, int) ...  -- Posn
  ... this.nw.samePosn(Posn) ...  -- boolean
  }
*/

  // produce a new triangle by moving this triangle a given distance 
  Shape move(int dx, int dy) {
    return new Triangle(this.sw.move(dx, dy), this.height, this.width);
  }
  
  // is this shape the same as that shape?
  boolean sameShape(Shape that){
    return that.sameTriangle(this);
  }

  // is this shape the same as that square?
  boolean sameSquare(Square that){
    return false;
  }

  // is this shape the same as that Circle?
  boolean sameCircle(Circle that){
    return false;
  }

  // is this shape the same as that triangle?
  boolean sameTriangle(Triangle that){
    return this.sw.samePosn(that.sw) &&
           this.height == that.height &&
           this.width == that.width;
  }
}
/*
+-------+
| Posn  |
+-------+
| int x |
| int y |
+-------+

*/

// to represent a location
class Posn {
  int x;
  int y;

  Posn(int x, int y) {
    this.x = x;
    this.y = y;
  }

/*
  // TEMPLATE:
  ??? mmm() {
    ... this.x ...
    ... this.y ...
  }
*/

  // move this Posn by the given distance
  Posn move(int dx, int dy){
    return new Posn(this.x + dx, this.y + dy);
  }

  // is this Posn the same as the given Posn?
  boolean samePosn(Posn that) {
    return this.x == that.x && this.y == that.y;
  }
}


class Examples {
  Examples() {}                

  Posn p1 = new Posn(100, 100);
  Posn p2 = new Posn(80, 120);
  
  Shape s1 = new Square(this.p1, 20);
  Shape c1 = new Circle(this.p1, 30);
  Shape t1 = new Triangle(this.p2, 40, 50);

  Shape s2 = new Square(this.p2, 20);
  Shape c2 = new Circle(this.p2, 50);
  Shape t2 = new Triangle(this.p2, 30, 50);

   // determine whether two double values are almost the same
  boolean almostSame(double d1, double d2, double eps) {
    return ((d1 - d2) * (d1 -d2) < eps);
  }

  // tests for the method samePosn in the class Posn
  boolean testSamePosn1 = this.p1.samePosn(this.p2) == false;
  boolean testSamePosn2 = this.p1.samePosn(new Posn(100, 100)) == true;

  // tests for the method move in the class Posn
  boolean testMovePosn1 = this.p1.move(10, 20).samePosn(new Posn(110, 120));
  boolean testMovePosn2 = this.p1.move(-20, 20).samePosn(this.p2);
  boolean testMovePosn3 = this.p2.move(20, 10).samePosn(new Posn(100, 130));

  // tests for the method sameSquare in the class Square
  boolean testSameSquareS1 = this.s1.sameSquare(new Square(this.p2, 20)) == false;
  boolean testSameSquareS2 = this.s1.sameSquare(new Square(this.p1, 20)) == true;

  // tests for the method sameCircle in the class Circle
  boolean testSameCircleC1 = this.c1.sameCircle(new Circle(this.p1, 20)) == false;
  boolean testSameCircleC2 = this.c1.sameCircle(new Circle(this.p1, 30)) == true;

  // tests for the method sameTriangle in the class Triangle
  boolean testSameTriangleT1 = this.t1.sameTriangle(new Triangle(this.p2, 30, 50)) == false;
  boolean testSameTriangleT2 = this.t1.sameTriangle(new Triangle(this.p2, 40, 50)) == true;


  // tests for the method sameShape in the Shape classes
  boolean testSameShapeS1 = this.s1.sameShape(this.s2) == false;
  boolean testSameShapeS2 = this.s1.sameShape(new Square(this.p1, 20)) == true;

  boolean testSameShapeC1 = this.c1.sameShape(this.c2) == false;
  boolean testSameShapeC2 = this.c1.sameShape(new Circle(this.p1, 30)) == true;

  boolean testSameShapeT1 = this.t1.sameShape(this.t2) == false;
  boolean testSameShapeT2 = this.t1.sameShape(new Triangle(this.p2, 40, 50)) == true;

  boolean testSameShapeSC = this.s1.sameShape(this.c1) == false;
  boolean testSameShapeST = this.s1.sameShape(this.t1) == false;
  boolean testSameShapeCS = this.c1.sameShape(this.s1) == false;
  boolean testSameShapeCT = this.c1.sameShape(this.t1) == false;
  boolean testSameShapeTS = this.t1.sameShape(this.s1) == false;
  boolean testSameShapeTC = this.t1.sameShape(this.c1) == false;

  // tests for the method move in the Shape classes
  boolean testMoveSquare = 
    this.s1.move(10, 20).sameShape(new Square(new Posn(110, 120), 20));
  boolean testMoveCircle = 
    this.c1.move(-20, 20).sameShape(new Circle(this.p2, 30));
  boolean testMoveTriangle = 
    this.t1.move(20, 10).sameShape(new Triangle(new Posn(100, 130), 40, 50));
  
}
