import tester.*;

/**
 * HtDC Lectures
 * Lecture 7: Abstract classes
 * 
 * Copyright 2013 Viera K. Proulx
 * This program is distributed under the terms of the 
 * GNU Lesser General Public License (LGPL)
 * 
 * @since 14 September 2013
 */

/*
                                +-----------------------------------------+
                                |  +------------------------------------+ |
                                |  |                                    | |
                                v  v                                    | |
                    +----------------------------+                      | |          
                    | IShape                     |                      | |           
                    +----------------------------+                      | |         
                    | double area()              |                      | |         
                    | boolean distTo0()          |                      | |         
                    | IShape grow(int)           |                      | |         
                    | boolean biggerThan(IShape) |                      | |            
                    | boolean contains(CartPt)   |                      | |         
                    +----------------------------+                      | |               
                                   |                                    | |               
                                  / \                                   | |               
                                  ---                                   | |               
                                   |                                    | |
               ---------------------------------------------            | |
               |                                           |            | |
   +-----------------------------------+ +----------------------------+ | |
   | abstract AShape                   | | Combo                      | | |
   +-----------------------------------+ +----------------------------+ | |
+--| CartPt loc                        | | IShape top                 |-+ |
|  | String color                      | | IShape bot                 |---+
|  +-----------------------------------+ +----------------------------+ 
|  | abstract double area()            | | double area()              | 
|  | boolean distTo0()                 | | boolean distTo0()          | 
|  | abstract IShape grow(int)         | | IShape grow(int)           | 
|  | boolean biggerThan(IShape)        | | boolean biggerThan(IShape) | 
|  | abstract boolean contains(CartPt) | | boolean contains(CartPt)   |       
|  +-----------------------------------+ +----------------------------+ 
|                                  |                                    
|                                 / \                                   
|                                 ---                                   
|                                  |                                    
|                --------------------------------
|                |                              |            
| +--------------------------+  +--------------------------+  
| | Circle                   |  | Rect                     |  
| +--------------------------+  +--------------------------+  
| | int radius               |  | int width                |  
| +--------------------------+  | int height               |  
| | double area()            |  +--------------------------+  
| | boolean distTo0()        |  | double area()            |   
| | IShape grow(int)         |  | IShape grow(int)         |  
| | boolean contains(CartPt) |  | boolean contains(CartPt) |  
| +--------------------------+  +--------------------------+  
|                                            / \
|                                            ---             
|                                             |
|                               +-----------------------------+ 
|                               | Square                      | 
|                               +-----------------------------+
|                               +-----------------------------+
|                               | IShape grow(int)            |  
|                               +-----------------------------+       
| 
+-------+
        |
        v                               
  +-----------------------+
  | CartPt                |
  +-----------------------+
  | int x                 |
  | int y                 |
  +-----------------------+
  | double distTo0()      |
  | double distTo(CartPt) |
  +-----------------------+
*/


interface IShape {
    
    // to compute the area of this shape
    public double area();
    
    // to compute the distance form this shape to the origin
    public double distTo0();
    
    // to increase the size of this shape by the given increment
    public IShape grow(int inc);
    
    // is the area of this shape is bigger than the area of the given shape?
    public boolean biggerThan(IShape that);
    
    // does this shape (including the boundary) contain the given point?
    public boolean contains(CartPt pt);
}

// to represent a geometric shape
abstract class AShape implements IShape {
    CartPt loc;
    String color;
    
    AShape(CartPt loc, String color) {
        this.loc = loc;
        this.color = color;
    }
    
    // to compute the area of this shape
    public abstract double area();
    
    // to compute the distance form this shape to the origin
    public double distTo0(){
        return this.loc.distTo0();
    }
    
    // to increase the size of this shape by the given increment
    public abstract IShape grow(int inc);
    
    // is the area of this shape is bigger than the area of the given shape?
    public boolean biggerThan(IShape that){
        return this.area() >= that.area();
    }
    
    // does this shape (including the boundary) contain the given point?
    public abstract boolean contains(CartPt pt);
}

// to represent a circle
class Circle extends AShape {
    int radius;
    
    Circle(CartPt center, int radius, String color) {
        super(center, color);
        this.radius = radius;
    }
    
    /*  TEMPLATE 
     Fields:
     ... this.loc ...             -- CartPt
     ... this.rad ...             -- int
     ... this.color ...           -- String
     Methods:
     ... this.area() ...                  -- double 
     ... this.distTo0() ...               -- double
     ... this.grow(int) ...               -- IShape
     ... this.biggerThan(IShape) ...      -- boolean
     ... this.contains(CartPt) ...        -- boolean 
     Methods for fields:
     ... this.loc.distTo0() ...           -- double 
     ... this.loc.distTo(CartPt) ...      -- double 
     */
    
    // to compute the area of this shape
    public double area(){
        return Math.PI * this.radius * this.radius;
    }
    
    // to compute the distance form this shape to the origin
    public double distTo0(){
        return this.loc.distTo0() - this.radius;
    }
    
    // to increase the size of this shape by the given increment
    public IShape grow(int inc){
        return new Circle(this.loc, this.radius + inc, this.color);
    }
    
    // does this shape (including the boundary) contain the given point?
    public boolean contains(CartPt pt){
        return this.loc.distTo(pt) <= this.radius;
    }
    
}


// to represent a rectangle
class Rect extends AShape {
    int width;
    int height;
    
    Rect(CartPt nw, int width, int height, String color) {
        super(nw, color);
        this.width = width;
        this.height = height;
    }
    
    /* TEMPLATE
     FIELDS
     ... this.loc ...             -- CartPt
     ... this.width ...           -- int
     ... this.height ...          -- int
     ... this.color ...           -- String
     METHODS
     ... this.area() ...                  -- double 
     ... this.distTo0() ...               -- double 
     ... this.grow(int) ...               -- IShape
     ... this.biggerThan(IShape) ...      -- boolean
     ... this.contains(CartPt) ...        -- boolean 
     METHODS FOR FIELDS:
     ... this.loc.distTo0() ...           -- double 
     ... this.loc.distTo(CartPt) ...      -- double 
     */
    
    // to compute the area of this shape
    public double area(){
        return this.width * this.height;
    }
    
    // to increase the size of this shape by the given increment
    public IShape grow(int inc){
        return new Rect(this.loc, this.width + inc, this.height + inc, 
                        this.color);
    }
    
    // does this shape (including the boundary) contain the given point?
    public boolean contains(CartPt pt){
        return (this.loc.x <= pt.x) && (pt.x <= this.loc.x + this.width) &&
        (this.loc.y <= pt.y) && (pt.y <= this.loc.y + this.height);            
    }
}


//to represent a square
class Square extends Rect {
    
    Square(CartPt nw, int size, String color) {
        super(nw, size, size, color);
    }
    
    /* TEMPLATE
     FIELDS
     ... this.loc ...             -- CartPt
     ... this.width ...           -- int
     ... this.height ...          -- int
     ... this.color ...           -- String
     METHODS
     ... this.area() ...                  -- double 
     ... this.distTo0() ...               -- double 
     ... this.grow(int) ...               -- IShape
     ... this.biggerThan(IShape) ...      -- boolean
     ... this.contains(CartPt) ...        -- boolean
     METHODS FOR FIELDS:
     ... this.loc.distTo0() ...           -- double 
     ... this.loc.distTo(CartPt) ...      -- double 
     */
    
    // to increase the size of this shape by the given increment
    public IShape grow(int inc){
        return new Square(this.loc, this.width + inc, this.color);
    }
}

// to represent a shape that combines two existing shapes
class Combo implements IShape {
    IShape top;
    IShape bot;
    
    Combo(IShape top, IShape bot) {
        this.top = top;
        this.bot = bot;
    }
    
    /* TEMPLATE
     FIELDS
     ... this.top ...           -- IShape
     ... this.bot ...           -- IShape
     METHODS
     ... this.area() ...                  -- double 
     ... this.distTo0() ...               -- double 
     ... this.grow(int) ...               -- IShape
     ... this.biggerThan(IShape) ...      -- boolean
     ... this.contains(CartPt) ...        -- boolean
     METHODS FOR FIELDS:
     ... this.top.area() ...                  -- double 
     ... this.top.distTo0() ...               -- double 
     ... this.top.grow(int) ...               -- IShape
     ... this.top.biggerThan(IShape) ...      -- boolean
     ... this.top.contains(CartPt) ...        -- boolean
     
     ... this.bot.area() ...                  -- double 
     ... this.bot.distTo0() ...               -- double 
     ... this.bot.grow(int) ...               -- IShape
     ... this.bot.biggerThan(IShape) ...      -- boolean
     ... this.bot.contains(CartPt) ...        -- boolean
     */  
    // to compute the area of this shape
    public double area() {
        return this.top.area() + this.bot.area();
    }
    
    // to compute the distance form this shape to the origin
    public double distTo0(){
        return Math.min(this.top.distTo0(), this.bot.distTo0());
    }
    
    // to increase the size of this shape by the given increment
    public IShape grow(int inc) {
        return new Combo(this.top.grow(inc), this.bot.grow(inc));
    }
    
    // is the area of this shape is bigger than the area of the given shape?
    public boolean biggerThan(IShape that){
        return this.area() >= that.area();
    }
    
    // does this shape (including the boundary) contain the given point?
    public boolean contains(CartPt pt) {
        return this.top.contains(pt) || this.bot.contains(pt);
    }
}


// to represent a Cartesian point
class CartPt {
    int x;
    int y;
    
    CartPt(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    /* TEMPLATE
     FIELDS
     ... this.x ...          -- int
     ... this.y ...          -- int
     METHODS
     ... this.distTo0() ...        -- double
     ... this.distTo(CartPt) ...   -- double
     */
    
    // to compute the distance form this point to the origin
    public double distTo0(){
        return Math.sqrt(this.x * this.x + this.y * this.y);
    }
    
    // to compute the distance form this point to the given point
    public double distTo(CartPt pt){
        return Math.sqrt((this.x - pt.x) * (this.x - pt.x) + 
                         (this.y - pt.y) * (this.y - pt.y));
    }
}

@SuppressWarnings("unused")
class ExamplesShapes {
    ExamplesShapes() {}
    
    CartPt pt1 = new CartPt(0, 0);
    CartPt pt2 = new CartPt(3, 4);
    CartPt pt3 = new CartPt(7, 1);
    
    IShape c1 = new Circle(new CartPt(50, 50), 10, "red");
    IShape c2 = new Circle(new CartPt(50, 50), 30, "red");
    IShape c3 = new Circle(new CartPt(30, 100), 30, "blue");
    
    IShape s1 = new Square(new CartPt(50, 50), 30, "red");
    IShape s2 = new Square(new CartPt(50, 50), 50, "red");
    IShape s3 = new Square(new CartPt(20, 40), 10, "green");
    
    IShape r1 = new Rect(new CartPt(50, 50), 30, 20, "red");
    IShape r2 = new Rect(new CartPt(50, 50), 50, 40, "red");
    IShape r3 = new Rect(new CartPt(20, 40), 10, 20, "green");
    
    IShape cb1 = new Combo(this.r1, this.c1);
    IShape cb2 = new Combo(this.r2, this.r3);
    IShape cb3 = new Combo(this.cb1, this.cb2);
    
    // test the method distTo0 in the class CartPt
    boolean testDistTo0(Tester t) { 
        return
        t.checkInexact(this.pt1.distTo0(), 0.0, 0.001) &&
        t.checkInexact(this.pt2.distTo0(), 5.0, 0.001);
    }
    
    // test the method distTo in the class CartPt
    boolean testDistTo(Tester t) { 
        return
        t.checkInexact(this.pt1.distTo(this.pt2), 5.0, 0.001) &&
        t.checkInexact(this.pt2.distTo(this.pt3), 5.0, 0.001);
    }
    
    // test the method area in the shape classes
    boolean testShapeArea(Tester t) { 
        return
        t.checkInexact(this.c1.area(), 314.15, 0.01) &&
        t.checkInexact(this.s1.area(), 900.0, 0.01) &&
        t.checkInexact(this.r1.area(), 600.0, 0.01) &&
        t.checkInexact(this.cb1.area(), 914.15926, 0.01) &&
        t.checkInexact(this.cb2.area(), 2200.0, 0.01) &&
        t.checkInexact(this.cb3.area(), 3114.15926, 0.01);
    }
    
    // test the method distTo0 in the shape classes
    boolean testShapeDistTo0(Tester t) { 
        return
        t.checkInexact(this.c1.distTo0(), 60.71, 0.01) &&
        t.checkInexact(this.c3.distTo0(), 74.40, 0.01) &&
        t.checkInexact(this.s1.distTo0(), 70.71, 0.01) &&
        t.checkInexact(this.s3.distTo0(), 44.72, 0.01) &&
        t.checkInexact(this.r1.distTo0(), 70.71, 0.01) &&
        t.checkInexact(this.r3.distTo0(), 44.72, 0.01) &&
        t.checkInexact(this.cb1.distTo0(), 60.71, 0.01) &&
        t.checkInexact(this.cb2.distTo0(), 44.72, 0.01) &&
        t.checkInexact(this.cb3.distTo0(), 44.72, 0.01);
    }
    
    // test the method grow in the shape classes
    boolean testShapeGrow(Tester t) { 
        return
        t.checkExpect(this.c1.grow(20), this.c2) &&
        t.checkExpect(this.s1.grow(20), this.s2) &&
        t.checkExpect(this.r1.grow(20), this.r2) &&
        t.checkExpect(this.cb1.grow(20), new Combo(this.r2, this.c2));
    }
    
    // test the method biggerThan in the shape classes
    boolean testShapeBiggerThan(Tester t) { 
        return
        t.checkExpect(this.c1.biggerThan(this.c2), false) && 
        t.checkExpect(this.c2.biggerThan(this.c1), true) && 
        t.checkExpect(this.c1.biggerThan(this.s1), false) && 
        t.checkExpect(this.c1.biggerThan(this.s3), true) && 
        t.checkExpect(this.c1.biggerThan(this.cb1), false) &&
        
        t.checkExpect(this.s1.biggerThan(this.s2), false) && 
        t.checkExpect(this.s2.biggerThan(this.s1), true) && 
        t.checkExpect(this.s1.biggerThan(this.c1), true) && 
        t.checkExpect(this.s3.biggerThan(this.c1), false) && 
        t.checkExpect(this.s2.biggerThan(this.cb1), true) &&
        
        t.checkExpect(this.r1.biggerThan(this.r2), false) && 
        t.checkExpect(this.r2.biggerThan(this.r1), true) && 
        t.checkExpect(this.r1.biggerThan(this.c1), true) && 
        t.checkExpect(this.r3.biggerThan(this.s1), false) && 
        t.checkExpect(this.r2.biggerThan(this.cb1), true) && 
        t.checkExpect(this.r3.biggerThan(this.cb1), false) && 
        
        t.checkExpect(this.cb2.biggerThan(this.r1), true) &&
        t.checkExpect(this.cb1.biggerThan(this.r2), false) &&  
        t.checkExpect(this.cb1.biggerThan(this.c1), true) && 
        t.checkExpect(this.cb1.biggerThan(this.c3), false) && 
        t.checkExpect(this.cb1.biggerThan(this.s2), false) && 
        t.checkExpect(this.cb2.biggerThan(this.s1), true) && 
        t.checkExpect(this.cb1.biggerThan(this.cb3), false) && 
        t.checkExpect(this.cb2.biggerThan(this.cb1), true);
    }
    
    // test the method contains in the shape classes
    boolean testShapeContains(Tester t) { 
        return
        t.checkExpect(this.c1.contains(new CartPt(100, 100)), false) && 
        t.checkExpect(this.c2.contains(new CartPt(40, 60)), true) &&
        t.checkExpect(this.s1.contains(new CartPt(100, 100)), false) && 
        t.checkExpect(this.s2.contains(new CartPt(55, 60)), true) &&
        t.checkExpect(this.r1.contains(new CartPt(100, 100)), false) && 
        t.checkExpect(this.r2.contains(new CartPt(55, 60)), true) &&
        t.checkExpect(this.cb1.contains(new CartPt(100, 100)), false) && 
        t.checkExpect(this.cb2.contains(new CartPt(55, 60)), true);
    }
}
