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

import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import jp.riken.brain.ni.samuraigraph.base.SGDrawingElement;
import jp.riken.brain.ni.samuraigraph.base.SGIConstants;
import jp.riken.brain.ni.samuraigraph.base.SGIDrawingElementConstants;
import jp.riken.brain.ni.samuraigraph.base.SGProperties;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2f;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityText;
import org.w3c.dom.Element;


/**
 * Drawing element of arrow.
 */
public abstract class SGDrawingElementArrow extends SGDrawingElement
	implements SGIArrowConstants, SGIDrawingElementConstants
{

	/**
	 * A line object.
	 */
	private SGDrawingElementLine mLine;
	

	/**
	 * A symbol object for the start head.
	 */	
	private SGDrawingElementSymbol mStartHead;


	/**
	 * A symbol object for the end head.
	 */
	private SGDrawingElementSymbol mEndHead;
	
	
	/**
	 * Open angle of the arrow.
	 */
	private float mHeadOpenAngle;


	/**
	 * Close angle of the arrow.
	 */
	private float mHeadCloseAngle;


	/**
	 * Default constructor.
	 */
	public SGDrawingElementArrow()
	{
		super();
		this.mLine = this.createBodyInstance();
		this.mStartHead = this.createHeadInstance();
		this.mEndHead = this.createHeadInstance();
	}


	/**
	 * 
	 * @return
	 */
	protected abstract SGDrawingElementLine createBodyInstance();


	/**
	 * 
	 * @return
	 */
	protected abstract SGDrawingElementSymbol createHeadInstance();


	/**
	 * 
	 */
	protected final SGDrawingElementLine getLine()
	{
		return this.mLine;
	}
	
	/**
	 * 
	 */
	protected final SGDrawingElementSymbol getStartHead()
	{
		return this.mStartHead;
	}
	
	/**
	 * 
	 */
	protected final SGDrawingElementSymbol getEndHead()
	{
		return this.mEndHead;
	}
	
	
	/**
	 * 
	 */
	public void dispose()
	{
		super.dispose();
		this.mLine.dispose();
		this.mStartHead.dispose();
		this.mEndHead.dispose();
		this.mLine = null;
		this.mStartHead = null;
		this.mEndHead = null;
	}


	/**
	 * 
	 */
	public boolean contains(final int x, final int y)
	{
		final boolean lineFlag = this.mLine.contains(x,y);
		final boolean startHeadFlag = this.mStartHead.contains(x,y);
		final boolean endHeadFlag = this.mEndHead.contains(x,y);
		final boolean flag = lineFlag | startHeadFlag | endHeadFlag;
		return flag;
	}


	/**
	 * 
	 */
	public boolean setColor( final Color cl )
	{
		super.setColor( cl );

		this.setLineColor(cl);
		this.setHeadInnerColor( cl );
		this.setHeadLineColor( cl );

		return true;
	}


	/**
	 * 
	 */
	public boolean setColorList( final List cList )
	{
		Color cl = (Color)cList.get(0);
		return this.setColor( cl );
	}


	/**
	 * 
	 */
	public boolean setMagnification( final float ratio )
	{
		super.setMagnification( ratio );
		this.mLine.setMagnification( ratio );
		this.mStartHead.setMagnification( ratio );
		this.mEndHead.setMagnification( ratio );
		return true;
	}


	/**
	 * 
	 * @param x
	 * @return
	 */
	public boolean setStart( SGTuple2f start )
	{
		return this.setTermPoints( start, this.getEnd() );
	}


	/**
	 * 
	 * @param x
	 * @return
	 */
	public boolean setStartX( final float x )
	{
//		this.mStartX = x;
		this.mLine.getStart().setX(x);
		this.mStartHead.setX(x);
		return true;
//		return this.setStart( new SGTuple2f( x, this.getStartY() ) );
	}


	/**
	 * 
	 * @param x
	 * @return
	 */
	public boolean setStartY( final float y )
	{
//		this.mStartY = y;
		this.mLine.getStart().setY(y);
		this.mStartHead.setY(y);
		return true;
//		return this.setStart( new SGTuple2f( this.getStartX(), y ) );
	}


	/**
	 * 
	 * @param x
	 * @return
	 */
	public boolean setEnd( SGTuple2f end )
	{
		return this.setTermPoints( this.getStart(), end );
	}


	/**
	 * 
	 * @param x
	 * @return
	 */
	public boolean setEndX( final float x )
	{
//		this.mEndX = x;
		this.mLine.getEnd().setX(x);
		this.mEndHead.setX(x);
		return true;
//		return this.setEnd( new SGTuple2f( x, this.getEndY() ) );
	}


	/**
	 * 
	 * @param x
	 * @return
	 */
	public boolean setEndY( final float y )
	{
//		this.mEndY = y;
		this.mLine.getEnd().setY(y);
		this.mEndHead.setY(y);
		return true;
//		return this.setStart( new SGTuple2f( this.getEndX(), y ) );
	}


	/**
	 *
	 */
	public boolean setTermPoints( final SGTuple2f start, final SGTuple2f end )
	{
//		this.mStartX = start.x;
//		this.mStartY = start.y;
//		this.mEndX = end.x;
//		this.mEndY = end.y;
		this.mLine.setTermPoints(start,end);
		this.mStartHead.setLocation( start );
		this.mEndHead.setLocation( end );
		return true;
	}


	/**
	 *
	 */
	public boolean setTermPoints(
		final float x1, final float y1,
		final float x2, final float y2 )
	{
		return this.setTermPoints( new SGTuple2f(x1,y1), new SGTuple2f(x2,y2) );
	}



	/**
	 * 
	 * @return
	 */
	public float getStartX()
	{
		return this.mStartHead.getX();
//		return this.mStartX;
	}


	/**
	 * 
	 * @return
	 */
	public float getStartY()
	{
		return this.mStartHead.getY();
//		return this.mStartY;
	}


	/**
	 * 
	 * @return
	 */
	public float getEndX()
	{
		return this.mEndHead.getX();
//		return this.mEndX;
	}


	/**
	 * 
	 * @return
	 */
	public float getEndY()
	{
		return this.mEndHead.getY();
//		return this.mEndY;
	}


	/**
	 * 
	 * @return
	 */
	public SGTuple2f getStart()
	{
		return new SGTuple2f( this.getStartX(), this.getStartY() );
	}
	

	/**
	 * 
	 * @return
	 */
	public SGTuple2f getEnd()
	{
		return new SGTuple2f( this.getEndX(), this.getEndY() );
	}


	/**
	 * 
	 * @param width
	 * @return
	 */
	public boolean setLineWidth( final float width )
	{
		this.mLine.setLineWidth( width );
		this.mStartHead.setLineWidth( width );
		this.mEndHead.setLineWidth( width );
		return true;
	}


	/**
	 * 
	 */
	public boolean setLineWidth( final float width, final String unit )
	{
		return this.setLineWidth( (float)SGUtilityText.convertToPoint( width, unit ) );
	}



	/**
	 * 
	 * @param type
	 * @return
	 */
	public boolean setLineType( final int type )
	{
		this.mLine.setLineType( type );
		return true;
	}


	/**
	 * 
	 * @param cl
	 * @return
	 */
	private boolean setLineColor( final Color cl )
	{
		this.mLine.setColor( cl );
		return true;
	}


	/**
	 * 
	 * @param list
	 * @return
	 */
	private boolean setLineColor( final ArrayList list )
	{
		this.mLine.setColorList( list );
		return true;
	}


	/**
	 * 
	 */
	public boolean setHeadSize( final float size )
	{
		this.mStartHead.setSize( size );
		this.mEndHead.setSize( size );
		return true;
	}


	/**
	 * 
	 */
	public boolean setHeadSize( final float size, final String unit )
	{
		return this.setHeadSize( (float)SGUtilityText.convertToPoint( size, unit ) );
	}



	/**
	 * 
	 */
	public boolean setHeadOpenAngle( final float angle )
	{
		this.mHeadOpenAngle = angle;
		return true;
	}


	/**
	 * 
	 */
	public boolean setHeadCloseAngle( final float angle )
	{
		this.mHeadCloseAngle = angle;
		return true;
	}


//	/**
//	 * 
//	 */
//	public boolean setHeadLineWidth( final float width )
//	{
//		this.mStartHead.setLineWidth( width );
//		this.mEndHead.setLineWidth( width );
//		return true;
//	}


	/**
	 * 
	 * @param cl
	 * @return
	 */
	private boolean setHeadInnerColor( final Color cl )
	{
		this.mStartHead.setColor(cl);
		this.mEndHead.setColor(cl);
		return true;
	}


	/**
	 * 
	 * @param list
	 * @return
	 */
	private boolean setHeadInnerColor( final ArrayList list )
	{
		this.mStartHead.setColorList( list );
		this.mEndHead.setColorList( list );
		return true;
	}


	/**
	 * 
	 */
	private boolean setHeadLineColor( final Color color )
	{
		this.mStartHead.setLineColor( color );
		this.mEndHead.setLineColor( color );
		return true;
	}



	/**
	 * 
	 */
	public boolean setStartHeadType( final int type )
	{
		this.mStartHead.setType( type );
		return true;
	}


	/**
	 * 
	 */
	public boolean setEndHeadType( final int type )
	{
		this.mEndHead.setType( type );
		return true;
	}


	/**
	 * 
	 * @return
	 */
	public float getLineWidth()
	{
		return this.mLine.getLineWidth();
	}


	/**
	 * 
	 * @return
	 */
	public int getLineType()
	{
		return this.mLine.getLineType();
	}


//	/**
//	 * 
//	 * @return
//	 */
//	public Color getLineColor()
//	{
//		return this.mLine.getColor(0);
//	}


	/**
	 * 
	 * @return
	 */
	public float getHeadSize()
	{
		return this.mStartHead.getSize();
	}


	/**
	 * 
	 * @return
	 */
	public float getHeadOpenAngle()
	{
		return this.mHeadOpenAngle;
	}


	/**
	 * 
	 * @return
	 */
	public float getHeadCloseAngle()
	{
		return this.mHeadCloseAngle;
	}


	/**
	 * 
	 * @return
	 */
	public float getHeadLineWidth()
	{
		return this.mStartHead.getLineWidth();
	}


//	/**
//	 * 
//	 * @return
//	 */
//	public Color getHeadLineColor()
//	{
//		return this.mStartHead.getLineColor();
//	}


	/**
	 * 
	 * @return
	 */
	public int getStartHeadType()
	{
		return this.mStartHead.getType();
	}


	/**
	 * 
	 * @return
	 */
	public int getEndHeadType()
	{
		return this.mEndHead.getType();
	}


	/**
	 * 
	 */
	public SGProperties getProperties()
	{
		ArrowProperties p = new ArrowProperties();
		if( this.getProperties(p) == false ) return null;
		return p;
	}


	/**
	 * 
	 */
	public boolean getProperties( SGProperties p )
	{
		if( ( p instanceof ArrowProperties ) == false ) return false;

		if( super.getProperties(p) == false ) return false;

		ArrowProperties ap = (ArrowProperties)p;

		ap.setLineWidth( this.getLineWidth() );
		ap.setLineType( this.getLineType() );
		ap.setHeadSize( this.getHeadSize() );
		ap.setHeadOpenAngle( this.getHeadOpenAngle() );
		ap.setHeadCloseAngle( this.getHeadCloseAngle() );
//		ap.setHeadLineWidth( this.getHeadLineWidth() );
//		ap.setHeadLineColor( this.getHeadLineColor() );
		ap.setStartHeadType( this.getStartHeadType() );
		ap.setEndHeadType( this.getEndHeadType() );

		return true;
	}


	/**
	 * 
	 */
	public boolean setProperties( final SGProperties p )
	{
		if( ( p instanceof ArrowProperties ) == false ) return false;

		if( super.setProperties(p) == false ) return false;

		ArrowProperties ap = (ArrowProperties)p;

		final Float lineWidth = ap.getLineWidth();
		if( lineWidth==null )
		{
			return false;
		}
		this.setLineWidth( lineWidth.floatValue() );

		final Integer lineType = ap.getLineType();
		if( lineType==null )
		{
			return false;
		}
		this.setLineType( lineType.intValue() );

		final Float headSize = ap.getHeadSize();
		if( headSize==null )
		{
			return false;
		}
		this.setHeadSize( headSize.floatValue() );

		final Float openAngle = ap.getHeadOpenAngle();
		if( openAngle==null )
		{
			return false;		
		}
		this.setHeadOpenAngle( openAngle.floatValue() );

		final Float closeAngle = ap.getHeadCloseAngle();
		if( closeAngle==null )
		{
			return false;
		}
		this.setHeadCloseAngle( closeAngle.floatValue() );

//		final Float headLineWidth = ap.getHeadLineWidth();
//		if( headLineWidth==null )
//		{
//			return false;
//		}
//		this.setHeadLineWidth( headLineWidth.floatValue() );

		final Integer startHeadType = ap.getStartHeadType();
		if( startHeadType==null )
		{
			return false;
		}
		this.setStartHeadType( startHeadType.intValue() );
		
		final Integer endHeadType = ap.getEndHeadType();
		if( endHeadType==null )
		{
			return false;
		}
		this.setEndHeadType( endHeadType.intValue() );
		
		return true;
	}


	/**
	 * 
	 */
	public static Integer getSymbolTypeFromName( final String name )
	{
		Integer type = SGDrawingElementSymbol.getSymbolTypeFromName( name );
		if( type!=null )
		{
			return type;
		}

		if( name.equals( SGIArrowConstants.SYMBOL_NAME_ARROW_HEAD ) )
		{
			return new Integer( SGIArrowConstants.SYMBOL_TYPE_ARROW_HEAD );
		}

		return null;
	}


	/**
	 * 
	 */
	public static String getSymbolTypeName( final int type )
	{
		String name = SGDrawingElementSymbol.getSymbolTypeName( type );
		if( name!=null )
		{
			return name;
		}

		if( type == SGIArrowConstants.SYMBOL_TYPE_ARROW_HEAD )
		{
			return SGIArrowConstants.SYMBOL_NAME_ARROW_HEAD;
		}

		return null;
	}



	/**
	 * 
	 */
	public boolean writeProperty( final Element el )
	{
		final float cp = SGIConstants.CM_POINT_RATIO;
		final float rd = SGIConstants.RADIAN_DEGREE_RATIO;

		el.setAttribute( KEY_LINE_WIDTH, Float.toString( this.getLineWidth() ) + SGIConstants.pt );
		el.setAttribute( KEY_LINE_TYPE, SGDrawingElementLine.getLineTypeName( this.getLineType() ) );
		el.setAttribute( KEY_HEAD_SIZE, Float.toString( this.getHeadSize()*cp ) + SGIConstants.cm );
		el.setAttribute( KEY_START_HEAD_TYPE, SGDrawingElementArrow.getSymbolTypeName( this.getStartHeadType() ) );
		el.setAttribute( KEY_END_HEAD_TYPE, SGDrawingElementArrow.getSymbolTypeName( this.getEndHeadType() ) );
		el.setAttribute( KEY_HEAD_OPEN_ANGLE, Double.toString( this.getHeadOpenAngle()/rd ) + SGIConstants.degree );
		el.setAttribute( KEY_HEAD_CLOSE_ANGLE, Double.toString( this.getHeadCloseAngle()/rd ) + SGIConstants.degree );
		el.setAttribute( KEY_COLOR_LIST, SGUtilityText.getColorListString( this.getColorList() ) );

		return true;
	}


	/**
	 * 
	 */
	public boolean readProperty( final Element el )
	{
		final float rd = SGIConstants.RADIAN_DEGREE_RATIO;

		String str = null;
		Number num = null;
		Color cl = null;
		Boolean b = null;
		List list = null;

		// line width
		str = el.getAttribute( KEY_LINE_WIDTH );
		if( str.length()!=0 )
		{
			StringBuffer uLineWidth = new StringBuffer();
			num = SGUtilityText.getNumber( str, uLineWidth );
			if( num==null )
			{
				return false;
			}
			final float lineWidth = num.floatValue();
			if( this.setLineWidth( lineWidth, uLineWidth.toString() ) == false )
			{
				return false;
			}
		}


		// line type
		str = el.getAttribute( KEY_LINE_TYPE );
		if( str.length()!=0 )
		{
			num = SGDrawingElementLine.getLineTypeFromName(str);
			if( num==null )
			{
				return false;
			}
			final int lineType = num.intValue();
			if( this.setLineType( lineType ) == false )
			{
				return false;
			}
		}


		// head size
		str = el.getAttribute( KEY_HEAD_SIZE );
		if( str.length()!=0 )
		{
			StringBuffer uHeadSize = new StringBuffer();
			num = SGUtilityText.getNumber( str, uHeadSize );
			if( num==null )
			{
				return false;
			}
			final float headSize = num.floatValue();
			if( this.setHeadSize( headSize, uHeadSize.toString() ) == false )
			{
				return false;
			}
		}


		// start head type
		str = el.getAttribute( KEY_START_HEAD_TYPE );
		if( str.length()!=0 )
		{
			num = SGDrawingElementArrow.getSymbolTypeFromName(str);
			if( num==null )
			{
				return false;
			}
			final int startHeadType = num.intValue();
			if( this.setStartHeadType( startHeadType ) == false )
			{
				return false;
			}
		}


		// end head type
		str = el.getAttribute( KEY_END_HEAD_TYPE );
		if( str.length()!=0 )
		{
			num = SGDrawingElementArrow.getSymbolTypeFromName(str);
			if( num==null )
			{
				return false;
			}
			final int endHeadType = num.intValue();
			if( this.setEndHeadType( endHeadType ) == false )
			{
				return false;
			}
		}

		// open angle
		str = el.getAttribute( KEY_HEAD_OPEN_ANGLE );
		if( str.length()!=0 )
		{
			num = SGUtilityText.getFloat(str,SGIConstants.degree);
			if( num==null )
			{
				return false;
			}
			final float openAngle = num.floatValue()*rd;
			if( this.setHeadOpenAngle( openAngle ) == false )
			{
				return false;
			}
		}


		// close angle
		str = el.getAttribute( KEY_HEAD_CLOSE_ANGLE );
		if( str.length()!=0 )
		{
			num = SGUtilityText.getFloat(str,SGIConstants.degree);
			if( num==null )
			{
				return false;
			}
			final float closeAngle = num.floatValue()*rd;
			if( this.setHeadCloseAngle( closeAngle ) == false )
			{
				return false;
			}
		}


		// color
		str = el.getAttribute( KEY_COLOR_LIST );
		if( str.length()!=0 )
		{
			list = SGUtilityText.getColorList(str);
			if( list==null )
			{
				return false;
			}
			if( this.setColorList( list ) == false )
			{
				return false;
			}
		}
		

		return true;
	}



	/**
	 * @author  okumura
	 */
	public static class ArrowProperties extends DrawingElementProperties
	{

		private SGDrawingElementLine.LineProperties mLineProperties
			= new SGDrawingElementLine.LineProperties();

		private SGDrawingElementSymbol.SymbolProperties mSymbolProperties
			= new SGDrawingElementSymbol.SymbolProperties();

		private int mEndHeadType;

		private double mHeadOpenAngle;

		private double mHeadCloseAngle;


		/**
		 * 
		 */
		public boolean equals( Object obj )
		{
			if( ( obj instanceof ArrowProperties ) == false ) return false;
			if( super.equals(obj) == false ) return false;

			ArrowProperties p = (ArrowProperties)obj;

			if( this.mLineProperties.equals( p.mLineProperties) == false ) return false;
			if( this.mSymbolProperties.equals( p.mSymbolProperties ) == false) return false;
			if( this.mEndHeadType!=p.mEndHeadType ) return false;
			if( this.mHeadOpenAngle!=p.mHeadOpenAngle ) return false;
			if( this.mHeadCloseAngle!=p.mHeadCloseAngle ) return false;

			return true;
		}


		public Float getLineWidth()
		{
			return this.mLineProperties.getLineWidth();
		}

		public Integer getLineType()
		{
			return this.mLineProperties.getLineType();
		}

		public Float getHeadSize()
		{
			return this.mSymbolProperties.getSize();
		}

		public Integer getStartHeadType()
		{
			return this.mSymbolProperties.getSymbolType();
		}

		public Integer getEndHeadType()
		{
			return new Integer( this.mEndHeadType );
		}

		public Float getHeadOpenAngle()
		{
			return new Float( this.mHeadOpenAngle );
		}

		public Float getHeadCloseAngle()
		{
			return new Float( this.mHeadCloseAngle );
		}


		public void setLineWidth( final float width )
		{
			this.mLineProperties.setLineWidth( width );
			this.mSymbolProperties.setLineWidth( width );
		}
		
		public void setLineType( final int type )
		{
			this.mLineProperties.setLineType( type );
		}

		public void setStartHeadType( final int num )
		{
			this.mSymbolProperties.setSymbolType( num );
		}

		public void setEndHeadType( final int num )
		{
			this.mEndHeadType = num;
		}

		public void setHeadSize( final float size )
		{
			this.mSymbolProperties.setSize( size );
		}

		public void setColor( final Color cl )
		{
			super.setColor( cl );
			this.mLineProperties.setColor( cl );
			this.mSymbolProperties.setColor( cl );
			this.mSymbolProperties.setLineColor( cl );
		}

		public void setColor( final List cList )
		{
			super.setColor( cList );
			this.mLineProperties.setColor( cList );
			this.mSymbolProperties.setColor( cList );
			
			Color cl = (Color)cList.get(0);
			this.mSymbolProperties.setLineColor(cl);
		}

		public void setHeadOpenAngle( final float value )
		{
			this.mHeadOpenAngle = value;
		}

		public void setHeadCloseAngle( final float value )
		{
			this.mHeadCloseAngle = value;
		}
		
	}

}

