import java.util.Vector;
import java.awt.*;

class MMdata {

    protected final static int capacityIncr = 5;
    private Vector points;
    private Vector normals;
    private Rectangle bounds;
    private double perimeter;
    private double maxweight, minweight;
    
    MMdata () {
	bounds = new Rectangle();
	perimeter = 0;
	maxweight = 0;
	minweight = 0;
	points = new Vector (10, MMdata.capacityIncr);
	normals = new Vector (10, MMdata.capacityIncr);
    }
    
    MMdata (int size) {
	bounds = new Rectangle();
	perimeter = 0;
	maxweight = 0;
	minweight = 0;
	points = new Vector (size, MMdata.capacityIncr);
	normals = new Vector (size, MMdata.capacityIncr);
    }
    
    void addPoint (MMpoint p) { 
	if (points.isEmpty()) { 
	    bounds.reshape((int) p.x, (int) p.y, 0, 0);
	    points.addElement(p);
	} else {
	    normals.addElement(new MMnormal(p, (MMpoint) points.lastElement()));
	    double lastweight = ((MMnormal)normals.lastElement()).getWeight();
	    points.addElement(p);
	    updateBounds(p);
	    perimeter += lastweight;
	    if (lastweight > maxweight) maxweight = lastweight;
	    if (lastweight < minweight) minweight = lastweight;
	}
//	System.out.println("Perimeter is "+Float.toString(perimeter));
    }
    
    void updateBounds(MMpoint p) {    
        if (!bounds.inside((int)p.x, (int)p.y)) {
      //      if (p.x < bounds.x || p.x > bounds.x + bounds.width || p.y < bounds.y || p.y > bounds.y + bounds.height) {
	    //System.out.println("Adding point ("+Float.toString(p.x)+", "+Float.toString(p.y)+") to box "+
	    //		    Integer.toString(bounds.x)+", "+Integer.toString(bounds.y)+", "+Integer.toString(bounds.width)+", "+Integer.toString(bounds.height));
	    // form the union of the bounds
	    //bounds.union(new Rectangle((int)p.x, (int)p.y, 0, 0));
	    if ((int) p.x < bounds.x) bounds.reshape((int) p.x, bounds.y, bounds.width + bounds.x - (int)p.x, bounds.height);
	    else if ((int) p.x > bounds.x+bounds.width) bounds.resize((int)p.x - bounds.x, bounds.height);
	    if ((int) p.y < bounds.y) bounds.reshape(bounds.x, (int) p.y, bounds.width, bounds.height + bounds.y - (int)p.y);
	    else if ((int) p.y > bounds.y+bounds.height) bounds.resize(bounds.width, (int)p.y - bounds.y);
	    
	    //System.out.println("Now Bounds are: "+
	    //Integer.toString(bounds.x)+", "+Integer.toString(bounds.y)+", "+Integer.toString(bounds.width)+", "+Integer.toString(bounds.height));
	}
    }
    
    void addFinalNormal () {
	if (!points.isEmpty()) {
	    if (normals.size() != points.size() -1 ) {
	      //		System.out.println("ALERT!! Tried adding final normal when normals.size() != points.size()-1 and vectors are:");
	      //		System.out.println(this.toString());
	    } else {
		normals.addElement(new MMnormal((MMpoint) points.firstElement(), (MMpoint) points.lastElement()));
		double lastweight = ((MMnormal)normals.lastElement()).getWeight();
		perimeter += lastweight; 
//		System.out.println("Perimeter is "+Float.toString(perimeter));
		if (lastweight > maxweight) maxweight = lastweight;
		if (lastweight < minweight) minweight = lastweight;
	    }
	}
    }
    
    public Rectangle getBounds() { return bounds; }
    
    public double getPerimeter() { return perimeter; }
    
    public double getMaxweight () { return maxweight; }
    
    public double getMinweight () { return minweight; }
    
    boolean isEmpty () { return points.isEmpty(); }
    
    int capacity() { return points.capacity(); }
        
    int size () { return points.size(); }
    
    MMpoint pointAt (int i) {
	return (MMpoint) points.elementAt (i);
    }
    
    MMnormal normalAt (int i) {
    	return (MMnormal) normals.elementAt(i);
    }
    
    void readData (MMdata d) {
//	points = new Vector (d.capacity(), MMdata.capacityIncr);
//	normals = new Vector (d.capacity(), MMdata.capacityIncr);
	points.removeAllElements();
	normals.removeAllElements();
	points.ensureCapacity(d.capacity());
	normals.ensureCapacity(d.capacity());
	if (d.size()!=0) {
	    for (int i = 0; i<d.size()-1; i++) {
		points.addElement(new MMpoint(d.pointAt(i)));
		normals.addElement(new MMnormal(d.normalAt(i)));
	    }
	    try {
		points.addElement(new MMpoint(d.pointAt(points.size())));
	    } catch (ArrayIndexOutOfBoundsException e) {
	      //		System.out.println("Array out of Bounds Exception when accessing point at d.size()-1 = "+Integer.toString(d.size()-1));
	    }
	    try { 
		normals.addElement(new MMnormal(d.normalAt(normals.size()))); 
	    } catch (ArrayIndexOutOfBoundsException e) {
	      //		System.out.println("Array out of Bounds Exception when accessing normal at d.size()-1 = "+Integer.toString(d.size()-1));
	    }
	}
	maxweight = d.getMaxweight();
	minweight = d.getMinweight();
	perimeter = d.getPerimeter();
	bounds = d.getBounds();
    }	
    
    void addFirstNormal (MMpoint p, MMnormal v) {
	points.addElement(p);
	normals.addElement(v);
	points.addElement(new MMpoint(p.x-v.y, p.y+v.x));	
    }
    
    void addNormal (MMnormal v) {
	if (size()<1) {
	    System.out.println("Can't add vector since no initial point has been specified. use addFirstNormal(p, v)");
	} else {
	    normals.addElement(v); 
	    MMpoint p = (MMpoint) points.elementAt(points.size()-1); 
	    points.addElement(new MMpoint(p.x-v.y, p.y+v.x)); 
	    if (v.getWeight()>maxweight) maxweight = v.getWeight();
	    if (v.getWeight()<minweight) minweight = v.getWeight();
	    perimeter += v.getWeight();
	    updateBounds((MMpoint)points.lastElement());
	}
    }
    
    // modifies all points (bud doesn't need to modify normals)
    // such that the bounding box and b have the same center. 
    void centerIn (Rectangle b) {
    
	// even if it doesn't fit, center anyway
    
//	if (b.width < bounds.width || b.height < bounds.height) {
//	    System.out.println("Can't center in smaller rectangle");
//	} else {
	    Point translation = new Point(b.x - bounds.x + ((b.width - bounds.width)>>1), 
				      b.y - bounds.y + ((b.height - bounds.height)>>1));
	    bounds.move(bounds.x+translation.x, bounds.y+translation.y);
	    for (int i = 0; i<points.size(); i++) 
		((MMpoint)points.elementAt(i)).add(translation);
//	}	   
    }
    
    void scaleToPerimeter (double p) {
	double scaleFactor = p/perimeter;
//	System.out.println("Scale To Perimeter Scale Factor is "+scaleFactor);
	// store old bounds and perimeter
	Rectangle oldBounds = new Rectangle(bounds.x, bounds.y, bounds.width, bounds.height); 
	double oldPerimeter = perimeter;
	// create new ones
	bounds.reshape((int)((MMpoint)points.elementAt(0)).x, (int)((MMpoint)points.elementAt(0)).y, 0, 0);
	perimeter = 0;
	for (int i = 0; i < points.size()-1; i++) {
	    // scale the side
	    ((MMnormal)normals.elementAt(i)).scaleBy(scaleFactor);
	    // add the scaled side to the point to create the next one
	    ((MMpoint) points.elementAt(i+1)).set(MMpoint.sum((MMpoint)points.elementAt(i), (MMnormal)normals.elementAt(i)));
	    // update perimeter and bounds
	    perimeter += ((MMnormal)normals.elementAt(i)).getWeight();
	    updateBounds((MMpoint)points.elementAt(i+1));
	}
	if (normals.size() == points.size()) {
	    // for the last vector, don't scale, just make sure it joins the last point and the origin
	    ((MMnormal)normals.elementAt(normals.size()-1)).set((MMpoint)points.elementAt(points.size()-1), 
								(MMpoint)points.elementAt(0));
	    perimeter += ((MMnormal)normals.elementAt(normals.size()-1)).getWeight();
	}
	// center the new polygon to its old center
	centerIn(oldBounds);
	
	// check how much perimeter has changed from what we were aiming for
//	System.out.println("Scaling was trying for "+(scaleFactor*oldPerimeter)+" and got "+perimeter);
    }
    
    public String toString() {
	String pointString = new String(points.size()+"Points are (x,y) = ");
	String normalString = new String(normals.size()+"Normals are (angle, weight) = ");
	for (int i = 0; i < points.size(); i++) 
	    pointString += "("+pointAt(i).x+", "+pointAt(i).y+") ";
	for (int i = 0; i < normals.size(); i++) 
	    normalString += "("+normalAt(i).getAngle()+", "+normalAt(i).getWeight()+") ";
	return(pointString+"\n"+normalString);
    }
    
    public void println() {
	printPoints();
	printNormals();
    }
    
    public void printPoints() {
	System.out.println("Points are:\n");
	for (int i = 0; i < points.size(); i++) 
	    System.out.println(i+": "+pointAt(i).x+", "+pointAt(i).y);
    }
    
    public void printNormals() {
	System.out.println("Normals are:\n");
	for (int i = 0; i < normals.size(); i++) 
	    System.out.println(i+": "+normalAt(i).getAngle()+", "+normalAt(i).getWeight());
    }
}
