
package jp.riken.brain.ni.samuraigraph.figure.java2d;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2f;
import jp.riken.brain.ni.samuraigraph.base.SGUtility;
import jp.riken.brain.ni.samuraigraph.figure.SGDrawingElementArrow;
import jp.riken.brain.ni.samuraigraph.figure.SGDrawingElementLine;
import jp.riken.brain.ni.samuraigraph.figure.SGDrawingElementSymbol;
import jp.riken.brain.ni.samuraigraph.figure.SGIArrowConstants;
import jp.riken.brain.ni.samuraigraph.figure.SGISymbolConstants;


/**
 * An arrow class using Java2D.
 *
 */
public class SGDrawingElementArrow2D extends SGDrawingElementArrow
	implements SGIDrawingElementJava2D
{


	/**
	 * The default constructor.
	 */
	public SGDrawingElementArrow2D()
	{
		super();
	}


	protected SGDrawingElementLine createBodyInstance()
	{
		return new ArrowBody();
	}

	protected SGDrawingElementSymbol createHeadInstance()
	{
		return new ArrowHead();
	}

	/**
	 * 
	 */
	public Rectangle2D getElementBounds()
	{
		SGDrawingElementLine2D line = (SGDrawingElementLine2D)this.getLine();
		SGDrawingElementSymbol2D start = (SGDrawingElementSymbol2D)this.getStartHead();
		SGDrawingElementSymbol2D end = (SGDrawingElementSymbol2D)this.getEndHead();
		ArrayList list = new ArrayList();
		list.add( line.getElementBounds() );
		list.add( start.getElementBounds() );
		list.add( end.getElementBounds() );
		Rectangle2D rect = SGUtility.createUnion( list );
		return rect;
	}


	/**
	 * 
	 */
	public void paintElement( Graphics2D g2d )
	{
		if( this.isVisible() == false )
		{
			return;
		}
		final SGDrawingElementLine2D line = (SGDrawingElementLine2D)this.getLine();
		final SGDrawingElementSymbol2D start = (SGDrawingElementSymbol2D)this.getStartHead();
		final SGDrawingElementSymbol2D end = (SGDrawingElementSymbol2D)this.getEndHead();
		line.paintElement(g2d);
		start.paintElement(g2d);
		end.paintElement(g2d);
	}


	/**
	 * 
	 * @param g2d
	 * @param clipRect
	 */
	public void paint( Graphics2D g2d, Rectangle2D clipRect )
	{
		if( this.isVisible() == false )
		{
			return;
		}
		final SGDrawingElementLine2D line = (SGDrawingElementLine2D)this.getLine();
		final SGDrawingElementSymbol2D start = (SGDrawingElementSymbol2D)this.getStartHead();
		final SGDrawingElementSymbol2D end = (SGDrawingElementSymbol2D)this.getEndHead();
		line.paint( g2d, clipRect );
		start.paint( g2d, clipRect );
		end.paint( g2d, clipRect );
	}



	/**
	 * 
	 * @return
	 */
	public float getMagnitude()
	{
		final SGTuple2f start = this.getStart();
		final SGTuple2f end = this.getEnd();
		final float x = start.x - end.x;
		final float y = start.y - end.y;
		return (float)Math.sqrt( x*x + y*y );
	}



	/**
	 * 
	 */
	public boolean setStartX( final float x )
	{
		super.setStartX(x);
		this.updateHeadAngle();
		return true;
	}


	/**
	 * 
	 */
	public boolean setStartY( final float y )
	{
		super.setStartY(y);
		this.updateHeadAngle();
		return true;
	}


	/**
	 * 
	 */
	public boolean setEndX( final float x )
	{
		super.setEndX(x);
		this.updateHeadAngle();
		return true;
	}


	/**
	 * 
	 */
	public boolean setEndY( final float y )
	{
		super.setEndY(y);
		this.updateHeadAngle();
		return true;
	}


	/**
	 *
	 */
	public boolean setTermPoints( final SGTuple2f start, final SGTuple2f end )
	{
		super.setTermPoints( start, end );
		this.updateHeadAngle();
		return true;
	}


	private void updateHeadAngle()
	{
		final SGDrawingElementLine2D line = (SGDrawingElementLine2D)this.getLine();
		final SGDrawingElementSymbol2D start = (SGDrawingElementSymbol2D)this.getStartHead();
		final SGDrawingElementSymbol2D end = (SGDrawingElementSymbol2D)this.getEndHead();
		final float pi = (float)Math.PI;
		final float angle = line.getAngle();
		start.setAngle( angle - 0.50f*pi );
		end.setAngle( angle + 0.50f*pi );
	}


//	/**
//	 * 
//	 * @return
//	 */
//	private float getOpenAngleTangent()
//	{
//		return (float)Math.tan( this.getHeadOpenAngle() );
//	}


//	/**
//	 * 
//	 * @return
//	 */
//	private float getCloseAngleTangent()
//	{
//		return (float)Math.tan( this.getHeadCloseAngle() );
//	}




	/**
	 * An inner class for arrow body.
	 */
	private class ArrowBody extends SGDrawingElementLine2D
	{

		/**
		 * 
		 */
		public Shape getLineShape()
		{
			final float open = SGDrawingElementArrow2D.this.getHeadOpenAngle();
			final float close = SGDrawingElementArrow2D.this.getHeadCloseAngle();
			final float tanOpen = (float)Math.tan(open);
			final float tanClose = (float)Math.tan(close);
			final float diff =
				( open <= close ) ? this.getMagnification() * getHeadSize() * ( 1.0f - tanOpen / tanClose ) : 0.0f;
			final float len = this.getMagnitude();

			final int aType = SGIArrowConstants.SYMBOL_TYPE_ARROW_HEAD;
			final float yStart = ( SGDrawingElementArrow2D.this.getStartHeadType()!=aType ) ? 0.0f : 0.50f*diff;
			final float yEnd = (SGDrawingElementArrow2D.this.getEndHeadType()!=aType ) ? len : ( len - 0.50f*diff );

			Line2D line = new Line2D.Float( 0.0f, yStart, 0.0f, yEnd );
			Shape shape = this.getAffineTransform().createTransformedShape( line );

			return shape;
		}
		
		
		/**
		 *
		 */
		private AffineTransform getAffineTransform()
		{
			AffineTransform af = new AffineTransform();

			// translate
			af.translate( this.getStart().x, this.getStart().y );

			// rotate
			final double angle = this.getAngle() - 0.50*Math.PI;
			af.rotate(angle);

			return af;		
		}

	}




	/**
	 * An inner class for arrow head.
	 */
	private class ArrowHead extends SGDrawingElementSymbol2D
	{
		/**
		 * Overrode not to draw line in the symbol with finite area.
		 */
		protected void paintLine( Graphics2D g2d, Shape sh )
		{
			if( isLineTypeSymbol( this.getType() ) )
			{
				super.paintLine( g2d, sh );
			}
		}


		/**
		 * Overrode not to draw line in the symbol with finite area.
		 */
		protected void paintLine( Graphics2D g2d, Area clipArea, Shape sh )
		{
			if( isLineTypeSymbol( this.getType() ) )
			{
				super.paintLine( g2d, clipArea, sh );
			}
		}


		/**
		 * 
		 */
		public Shape getSymbolShape()
		{
			if( this.getType() == SGISymbolConstants.SYMBOL_TYPE_VOID )
			{
				return null;
			}

			Shape sh = null;
			final int type = this.getType();

			// arrow head
			if( type == SYMBOL_TYPE_ARROW_HEAD )
			{
				final float open = SGDrawingElementArrow2D.this.getHeadOpenAngle();
				final float close = SGDrawingElementArrow2D.this.getHeadCloseAngle();
				if( close <= open )
				{
					return null;
				}
				
				final float tanOpen = (float)Math.tan(open);
				final float tanClose = (float)Math.tan(close);

				final float x = this.getX();
				final float y = this.getY();

				final float mag = this.getMagnification();
				final float headSize = mag*this.getSize();
				final float openSize = headSize*tanOpen;

				Point2D[] pointArray = new Point2D[4];
				pointArray[0] = new Point2D.Float(
					x,
					y
				);
				pointArray[1] = new Point2D.Float(
					x + openSize,
					y + headSize
				);
				pointArray[2] = new Point2D.Float(
					x,
					y + headSize - openSize/tanClose
				);
				pointArray[3] = new Point2D.Float(
					x - openSize,
					y + headSize
				);

				Shape[] pathArray = new Line2D[pointArray.length];
				for( int ii=0; ii<pathArray.length; ii++ )
				{
					pathArray[ii] = new Line2D.Float(
						pointArray[ii], pointArray[(ii+1)%pointArray.length] );
				}

				GeneralPath gp = new GeneralPath();
				for( int ii=0; ii<pathArray.length; ii++ )
				{
					gp.append( pathArray[ii], true );
				}

				sh = this.getAffineTransform().createTransformedShape( gp );
			}
			else
			{
				sh = super.getSymbolShape();
			}

			return sh;
		}

	}


}


