import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.filter.*;
import edu.neu.ccs.jpf.*;
import edu.neu.ccs.parser.*;
import edu.neu.ccs.pedagogy.*;
import edu.neu.ccs.quick.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import java.util.*;
import java.math.*;
import java.beans.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.regex.*;
import java.text.ParseException;

public class ShapeTools {

    public static Area makeArea(Shape s) {
        if (s == null)
            return null;
        
        return new Area(s);
    }
    
    
    public static Shape makeShape(PathIterator path) {
        if (path == null)
            return null;
        
        return new PathList(path).makeShape();
    }
    
    
    // Returns the union of s1 and s2
    public static Shape union(Shape s1, Shape s2) {
        if (s1 == null)
            return s2;
        
        if (s2 == null)
            return s1;
        
        Area a1 = makeArea(s1);
        Area a2 = (s2 instanceof Area) ? (Area) s2 : makeArea(s2);
        
        a1.add(a2);
        
        return a1;
    }
    
    
    // Returns the intersection of s1 and s2
    public static Shape intersect(Shape s1, Shape s2) {
        if (s1 == null)
            return s2;
        
        if (s2 == null)
            return s1;
        
        Area a1 = makeArea(s1);
        Area a2 = (s2 instanceof Area) ? (Area) s2 : makeArea(s2);
        
        a1.intersect(a2);
        
        return a1;
    }
    
    
    // Returns the symmetric set difference of s1 and s2
    public static Shape exclusiveOr(Shape s1, Shape s2) {
        if (s1 == null)
            return s2;
        
        if (s2 == null)
            return s1;
        
        Area a1 = makeArea(s1);
        Area a2 = (s2 instanceof Area) ? (Area) s2 : makeArea(s2);
        
        a1.exclusiveOr(a2);
        
        return a1;
    }
    
    
    // Returns the set difference s1 - s2
    public static Shape subtract(Shape s1, Shape s2) {
        if (s1 == null)
            return null;
        
        if (s2 == null)
            return s1;
        
        Area a1 = makeArea(s1);
        Area a2 = (s2 instanceof Area) ? (Area) s2 : makeArea(s2);
        
        a1.subtract(a2);
        
        return a1;
    }
    
    
    public static boolean intersects(Shape s1, Shape s2) {
        if (s1 == null)
            return false;
        
        if (s2 == null)
            return false;
        
        // easy cases
        if (s1 instanceof Rectangle2D)
            return s2.intersects((Rectangle2D) s1);
        
        if (s2 instanceof Rectangle2D)
            return s1.intersects((Rectangle2D) s2);
        
        // general case
        Area a1 = makeArea(s1);
        Area a2 = makeArea(s2);
        
        a1.intersect(a2);
        
        return ! a1.isEmpty();
    }
    
    
    public static Shape getMutatedShape
        (Shape s, AffineTransform at)
    {
        if ((s == null) || (at == null) || (at.isIdentity()))
            return s;
        
        // special case of rectangle
        if (s instanceof Rectangle2D) {
            double m00 = at.getScaleX();
            double m01 = at.getShearY();
            
            double m10 = at.getShearX();
            double m11 = at.getScaleY();
            
            boolean scales = (m10 == 0) && (m01 == 0);
            boolean rotatescales = (m00 == 0) && (m11 == 0);
            
            boolean simple = scales || rotatescales;
            
            if (simple) {
                Rectangle2D r = (Rectangle2D) s;
                
                double x1 = r.getMinX();
                double y1 = r.getMinY();
                
                double x2 = r.getMaxX();
                double y2 = r.getMaxY();
                
                double m20 = at.getTranslateX();
                double m21 = at.getTranslateY();
                
                double z1 = m00 * x1 + m10 * y1 + m20;
                double w1 = m01 * x1 + m11 * y1 + m21;
                
                double z2 = m00 * x2 + m10 * y2 + m20;
                double w2 = m01 * x2 + m11 * y2 + m21;
                
                XRect result = new XRect();
                
                result.setX1Y1X2Y2(z1, w1, z2, w2);
                
                return result;
            }
        }
        
        // general case
        return makeShape(s.getPathIterator(at));
    }
    
    
    public static Shape getFlattenedShape
        (Shape s, double flatness)
    {
        return getMutatedFlattenedShape(s, null, flatness);
    }


    public static Shape getMutatedFlattenedShape
        (Shape s, AffineTransform at, double flatness)
    {
        if (s == null)
            return null;
        
        return makeShape(s.getPathIterator(at, flatness));
    }
    
    
    public static Shape getMutatedOutline(Paintable paintable) {
        if (paintable == null)
            return null;
        
        AffineTransform mutator = paintable.getMutator();
        
        Shape outline = null;
        
        if (paintable instanceof ShapePaintable) {
            ShapePaintable sp = (ShapePaintable) paintable;
            
            outline = sp.getOutline();
        }
        else if (paintable instanceof TextPaintable) {
            TextPaintable tp = (TextPaintable) paintable;
            
            outline = tp.getOutline();
        }
        else if (paintable instanceof PointPaintable) {
            PointPaintable pp = (PointPaintable) paintable;
            
            PlotMark plotmark = pp.getPlotMark();
            
            PlotMarkAlgorithm algorithm = plotmark.getAlgorithm();
            
            if (algorithm != null) {
                outline = algorithm.makeShape(0, 0, plotmark.getSize());
            }
            else {
                outline = getMutatedOutline(plotmark.getPaintable());
            }
        }
        else if (paintable instanceof ClippingWrapper) {
            ClippingWrapper wrapper = (ClippingWrapper) paintable;
            
            Paintable p = wrapper.getPaintable();
            
            Shape shape = wrapper.getClippingShape();
            
            outline = getMutatedOutline(p);
            
            if (shape != null)
                outline = intersect(shape, outline);
                
        }
        else if (paintable instanceof PaintableSequence) {
            PaintableSequence sequence = (PaintableSequence) paintable;
            
            int N = sequence.length();
            
            for (int i = 0; i < N; i++) {
                Shape shape = getMutatedOutline(sequence.getPaintable(i));
                outline = union(shape, outline);
            }
        }
        else {
            outline = paintable.getOriginalBounds2D();
        }
        
        return getMutatedShape(outline, mutator);
    }
}

