/*
 * Copyright (c) 2005, the JUNG Project and the Regents of the University of
 * California All rights reserved.
 *
 * This software is open-source under the BSD license; see either "license.txt"
 * or http://jung.sourceforge.net/license.txt for a description.
 *
 * Created on Apr 16, 2005
 */

package edu.uci.ics.jung.visualization.transform;

import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;

import edu.uci.ics.jung.visualization.transform.shape.ShapeTransformer;

/**
 *
 * Provides methods to map points from one coordinate system to
 * another, by delegating to a wrapped AffineTransform (uniform)
 * and its inverse.
 * 
 * @author Tom Nelson - RABA Technologies
 */
public class AffineTransformer implements Transformer, ShapeTransformer {

    protected AffineTransform inverse;

    /**
     * the AffineTransform to use. Initialize to identity
     * 
     */
    protected AffineTransform transform = new AffineTransform();
    
    /**
     * create an instance that does not transform points
     *
     */
    public AffineTransformer() {
        // nothing left to do
    }
    /**
     * Create an instance with the supplied transform
     */
    public AffineTransformer(AffineTransform transform) {
        if(transform != null) 
            this.transform = transform;
    }

    /**
     * @return Returns the transform.
     */
    public AffineTransform getTransform() {
        return transform;
    }
    /**
     * @param transform The transform to set.
     */
    public void setTransform(AffineTransform transform) {
        this.transform = transform;
    }
    
    /**
     * applies the inverse transform to the supplied point
     * @param p
     * @return
     */
    public Point2D inverseTransform(Point2D p) {

        return getInverse().transform(p, null);
    }
    
    public AffineTransform getInverse() {
        if(inverse == null) {
            try {
                inverse = transform.createInverse();
            } catch (NoninvertibleTransformException e) {
                e.printStackTrace();
            }
        }
        return inverse;
    }
    
    /**
     * getter for scalex
     */
    public double getScaleX() {
        return transform.getScaleX();   
    }
    
    /**
     * getter for scaley
     */
    public double getScaleY() {
        return transform.getScaleY();
    }
    
    public double getScale() {
    		return Math.sqrt(transform.getDeterminant());
    }
    
    /**
     * getter for shear in x axis
     */
    public double getShearX() {
        return transform.getShearX();
    }
    
    /**
     * getter for shear in y axis
     */
    public double getShearY() {
        return transform.getShearY();
    }
    
    /**
     * get the translate x value
     */
    public double getTranslateX() {
        return transform.getTranslateX();
    }
    
    /**
     * get the translate y value
     */
    public double getTranslateY() {
        return transform.getTranslateY();
    }
    

    
    
    
    /**
     * applies the transform to the supplied point
     */
    public Point2D transform(Point2D p) {
        if(p == null) return null;
        return transform.transform(p, null);
    }
    
    /**
     * transform the supplied shape from graph coordinates to
     * screen coordinates
     * @return the GeneralPath of the transformed shape
     */
    public Shape transform(Shape shape) {
        GeneralPath newPath = new GeneralPath();
        float[] coords = new float[6];
        for(PathIterator iterator=shape.getPathIterator(null);
            iterator.isDone() == false;
            iterator.next()) {
            int type = iterator.currentSegment(coords);
            switch(type) {
            case PathIterator.SEG_MOVETO:
                Point2D p = transform(new Point2D.Float(coords[0], coords[1]));
                newPath.moveTo((float)p.getX(), (float)p.getY());
                break;
                
            case PathIterator.SEG_LINETO:
                p = transform(new Point2D.Float(coords[0], coords[1]));
                newPath.lineTo((float)p.getX(), (float) p.getY());
                break;
                
            case PathIterator.SEG_QUADTO:
                p = transform(new Point2D.Float(coords[0], coords[1]));
                Point2D q = transform(new Point2D.Float(coords[2], coords[3]));
                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
                break;
                
            case PathIterator.SEG_CUBICTO:
                p = transform(new Point2D.Float(coords[0], coords[1]));
                q = transform(new Point2D.Float(coords[2], coords[3]));
                Point2D r = transform(new Point2D.Float(coords[4], coords[5]));
                newPath.curveTo((float)p.getX(), (float)p.getY(), 
                        (float)q.getX(), (float)q.getY(),
                        (float)r.getX(), (float)r.getY());
                break;
                
            case PathIterator.SEG_CLOSE:
                newPath.closePath();
                break;
                    
            }
        }
        return newPath;
    }

    /**
     * transform the supplied shape from graph coordinates to
     * screen coordinates
     * @return the GeneralPath of the transformed shape
     */
    public Shape inverseTransform(Shape shape) {
        GeneralPath newPath = new GeneralPath();
        float[] coords = new float[6];
        for(PathIterator iterator=shape.getPathIterator(null);
            iterator.isDone() == false;
            iterator.next()) {
            int type = iterator.currentSegment(coords);
            switch(type) {
            case PathIterator.SEG_MOVETO:
                Point2D p = inverseTransform(new Point2D.Float(coords[0], coords[1]));
                newPath.moveTo((float)p.getX(), (float)p.getY());
                break;
                
            case PathIterator.SEG_LINETO:
                p = inverseTransform(new Point2D.Float(coords[0], coords[1]));
                newPath.lineTo((float)p.getX(), (float) p.getY());
                break;
                
            case PathIterator.SEG_QUADTO:
                p = inverseTransform(new Point2D.Float(coords[0], coords[1]));
                Point2D q = inverseTransform(new Point2D.Float(coords[2], coords[3]));
                newPath.quadTo((float)p.getX(), (float)p.getY(), (float)q.getX(), (float)q.getY());
                break;
                
            case PathIterator.SEG_CUBICTO:
                p = inverseTransform(new Point2D.Float(coords[0], coords[1]));
                q = inverseTransform(new Point2D.Float(coords[2], coords[3]));
                Point2D r = inverseTransform(new Point2D.Float(coords[4], coords[5]));
                newPath.curveTo((float)p.getX(), (float)p.getY(), 
                        (float)q.getX(), (float)q.getY(),
                        (float)r.getX(), (float)r.getY());
                break;
                
            case PathIterator.SEG_CLOSE:
                newPath.closePath();
                break;
                    
            }
        }
        return newPath;
    }
    
    public double getRotation() {    
        double[] unitVector = new double[]{0,0,1,0};
        double[] result = new double[4];

        transform.transform(unitVector, 0, result, 0, 2);

        double dy = Math.abs(result[3] - result[1]);
        double length = Point2D.distance(result[0], result[1], result[2], result[3]);
        double rotation = Math.asin(dy / length);        
        
        if (result[3] - result[1] > 0) {
            if (result[2] - result[0] < 0) {
                rotation = Math.PI - rotation;
            }
        } else {
            if (result[2] - result[0] > 0) {
                rotation = 2 * Math.PI - rotation;
            } else {
                rotation = rotation + Math.PI;
            }
        }

        return rotation;
    }

    public String toString() {
        return "Transformer using "+transform;
    }
 
}
