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

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import jp.riken.brain.ni.samuraigraph.base.SGDrawingElement;
import jp.riken.brain.ni.samuraigraph.base.SGProperties;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2f;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityNumber;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityText;
import jp.riken.brain.ni.samuraigraph.figure.SGDrawingElementLine;
import jp.riken.brain.ni.samuraigraph.figure.SGILineConstants;

import org.w3c.dom.Element;


/**
 * 
 */
public abstract class SGElementGroupLine extends SGElementGroupSXY
	implements SGILineConstants, SGIElementGroupConstants
{

	/**
	 *
	 */
	protected float mLineWidth;


	/**
	 *
	 */
	protected int mLineType;


	/**
	 * 
	 */
	protected int mCap = BasicStroke.CAP_BUTT;


	/**
	 * 
	 */
	protected int mJoin = BasicStroke.JOIN_ROUND;


	/**
	 * 
	 */
	protected float mMiterLimit = 1.0f;


	/**
	 * 
	 */
	protected float mDashPhase = 0.0f;



	/**
	 * 
	 */
	protected ArrayList mConnectedPathList = new ArrayList();



	/**
	 *
	 */
	public SGElementGroupLine()
	{
		super();
	}


	/**
	 * 
	 */
	public void dispose()
	{
		super.dispose();

		this.mConnectedPathList.clear();
		this.mConnectedPathList = null;
	}


	/**
	 *
	 */
	public boolean setLineWidth( final float width )
	{
		if( width<0.0f )
		{
			throw new IllegalArgumentException("width<0.0f");
		}
		mLineWidth = width;

		if( this.mDrawingElementArray!=null )
		{
			for( int ii=0; ii<this.mDrawingElementArray.length; ii++ )
			{
				SGDrawingElementLine2D el = (SGDrawingElementLine2D)mDrawingElementArray[ii];
				el.setLineWidth( mLineWidth );
			}
		}

		return true;
	}


	/**
	 *
	 */
	public boolean setLineType( final int type )
	{
		mLineType = type;

		if( this.mDrawingElementArray!=null )
		{
			for( int ii=0; ii<this.mDrawingElementArray.length; ii++ )
			{
				SGDrawingElementLine2D el = (SGDrawingElementLine2D)mDrawingElementArray[ii];
				el.setType( mLineType );
			}
		}

		return true;
	}



	/**
	 * 
	 * @param cap
	 * @return
	 */
	public boolean setBasicStrokeProperty(
		final int cap, final int join, final float miterlimit, final float dash_phase )
	{

		if( cap!=BasicStroke.CAP_BUTT && cap!=BasicStroke.CAP_ROUND
			&& cap!=BasicStroke.CAP_SQUARE )
		{
			throw new IllegalArgumentException("The argument is invalid.");
		}

		if( join!=BasicStroke.JOIN_BEVEL && join!=BasicStroke.JOIN_MITER
			&& join!=BasicStroke.JOIN_ROUND )
		{
			throw new IllegalArgumentException("The argument is invalid.");
		}

		if( miterlimit<1.0f && join==BasicStroke.JOIN_MITER )
		{
			throw new IllegalArgumentException("The argument is invalid.");
		}

		if( dash_phase<0.0f )
		{
			throw new IllegalArgumentException("The argument is invalid.");
		}

		this.mCap = cap;
		this.mJoin = join;
		this.mMiterLimit = miterlimit;
		this.mDashPhase = dash_phase;

		return true;
	}



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


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


	/**
	 * 
	 * @return
	 */
	public int getCap()
	{
		return this.mCap;
	}



	/**
	 * 
	 */
	public int getJoin()
	{
		return this.mJoin;
	}


	/**
	 * 
	 * @return
	 */
	public float getMiterLimit()
	{
		return this.mMiterLimit;
	}


	/**
	 * 
	 */
	public SGDrawingElement getDrawingElement()
	{
		// line
		SGDrawingElementLine line = new SGDrawingElementLine2D();
		line.setVisible( this.mVisibleFlag );
		line.setLineWidth( mLineWidth );
		line.setType( mLineType );
		line.setColor( mColorList );

		return line;		
	}


	/**
	 * 
	 */
	public boolean setProperty( final SGDrawingElement element )
	{
		if( !(element instanceof SGDrawingElementLine) )
		{
			return false;
		}

		if( super.setProperty( element ) == false )
		{
			return false;
		}

		SGDrawingElementLine line = (SGDrawingElementLine)element;

		this.setLineType( line.getLineType() );
		this.setLineWidth( line.getLineWidth() );
	
		return true;
	}



	/**
	 * 
	 */
	public boolean paintElement(
		final Graphics2D g2d, final Rectangle2D clipRect )
	{
		if( g2d==null )
		{
			return false;
		}

		g2d.setPaint(this.getColor(0));

		final float width = this.mMagnification*this.mLineWidth;

		// set stroke
		Stroke stroke = SGUtilityJava2D.getBasicStroke(
			this.mLineType, width, this.mCap,
			this.mJoin, this.mMiterLimit, this.mDashPhase );
		g2d.setStroke( stroke );


		// •`‰æ
		ArrayList list = new ArrayList( this.mConnectedPathList );
		if( clipRect==null )
		{
			for( int ii=0; ii<list.size(); ii++ )
			{
				GeneralPath gp = (GeneralPath)list.get(ii);
				g2d.draw( gp );
			}
		}
		else
		{
			Area rectArea = new Area( clipRect );
			for( int ii=0; ii<list.size(); ii++ )
			{
				GeneralPath gp = (GeneralPath)list.get(ii);
				Shape sh = stroke.createStrokedShape( gp );
				Area shArea = new Area( sh );
				shArea.intersect( rectArea );
				g2d.fill( shArea );
			}
		}

		return true;
	}

	
	/**
	 * 
	 * @return
	 */
	public String getTagName()
	{
		return TAG_NAME_LINE;
	}


	/**
	 * 
	 */
	public boolean writeProperty( final Element el )
	{
		el.setAttribute( KEY_VISIBLE, Boolean.toString( this.mVisibleFlag ) );
		el.setAttribute( KEY_LINE_WIDTH, Float.toString( this.mLineWidth ) + SGUtilityNumber.pt );
		el.setAttribute( KEY_LINE_TYPE, SGDrawingElementLine.getLineTypeName( this.mLineType ) );
		el.setAttribute( KEY_COLOR_LIST, SGUtilityText.getColorListString( this.mColorList ) );
		return true;
	}


	
	/**
	 * 
	 * @param el
	 * @param p
	 * @return
	 */
	public boolean readProperties( final Element el, SGProperties p )
	{
		
		if( ( p instanceof LineProperties ) == false ) return false;
		
		LineProperties lp = (LineProperties)p;

		String str = null;
		Number num = null;
		Color cl = null;
		Boolean b = null;
		ArrayList list = null;
		
		
		// visible
		str = el.getAttribute( KEY_VISIBLE );
		if( str.length()==0 )
		{
			return false;
		}
		b = SGUtilityText.getBoolean(str);
		if( b==null )
		{
			return false;
		}
		lp.setVisible( b.booleanValue() );


		// line width
		str = el.getAttribute( KEY_LINE_WIDTH );
		if( str.length()==0 )
		{
			return false;
		}
		num = SGUtilityText.getLengthInPoint( str );
		if( num==null )
		{
			return false;
		}
		lp.setLineWidth( num.floatValue() );


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


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

		
		return true;
	}
	
	
	
	/**
	 * 
	 * @param el
	 * @return
	 */
	public SGProperties readProperties( final Element el )
	{
		LineProperties p = new LineProperties();
		if( this.readProperties( el, p ) == false ) return null;
		return p;
	}
	

	/**
	 * 
	 * @return
	 */
	protected boolean initDrawingElement( final int num )
	{
		final int lNum = num-1;
		SGDrawingElementLine[] array = new SGDrawingElementLine[lNum];
		for( int ii=0; ii<lNum; ii++ )
		{
			array[ii] = new SGDrawingElementLine2D();
		}
		this.mDrawingElementArray = array;
		return true;
	}


	/**
	 * 
	 * @return
	 */
	protected boolean initDrawingElement( final SGTuple2f[] array )
	{
		final int lNum = array.length - 1;
		SGDrawingElementLine[] lArray = new SGDrawingElementLine[lNum];
		for( int ii=0; ii<lNum; ii++ )
		{
			lArray[ii] = new SGDrawingElementLine2D();
			lArray[ii].setTermPoints( array[ii], array[ii+1] );
		}
		this.mDrawingElementArray = lArray;
		return true;
	}



	/**
	 * Set the term points of each line element.
	 * @param pointArray - location of term points
	 */
	public boolean setLocation(
		final SGTuple2f[] pointArray )
	{
		return this.setLocation( pointArray, true );
	}


	/**
	 * Set the term points of each line element.
	 * @param pointArray - location of term points
	 * @param setFlag - whether to set the location to each line element
	 */
	protected boolean setLocation(
		final SGTuple2f[] pointArray, final boolean setFlag )
	{
		final SGDrawingElement[] array = this.mDrawingElementArray;
		if( array==null )
		{
			return true;
		}

		if( pointArray.length - 1 != array.length )
		{
			throw new Error("pointArray.length != this.mDrawingElementArray.length + 1");
		}


		// clear the list
		final ArrayList pathList = this.mConnectedPathList;
		pathList.clear();


		// check the effectivity of points
		boolean allEffectiveFlag = true;
		final boolean[] effArray = new boolean[pointArray.length];
		for( int ii=0; ii<effArray.length; ii++ )
		{
//System.out.println(ii+"  "+pointArray[ii]);
			effArray[ii] = !( pointArray[ii].isInfinite() | pointArray[ii].isNaN()	);
			if( effArray[ii] == false )
			{
				allEffectiveFlag = false;
			}
		}
//System.out.println();

		// if all points are effective
		if( allEffectiveFlag )
		{
			if( setFlag )
			{
				// set location to the drawing elements
				for( int ii=0; ii<pointArray.length-1; ii++ )
				{
					final SGDrawingElementLine line
						= (SGDrawingElementLine)array[ii];
					line.setTermPoints( pointArray[ii], pointArray[ii+1] );
				}
			}

			// create a general path
			final GeneralPath gp = new GeneralPath();
			final Line2D line = new Line2D.Float(
				pointArray[0].x, pointArray[0].y, pointArray[1].x, pointArray[1].y );
			gp.append( line, true );
			if( pointArray.length > 2 )
			{
				for( int ii=2; ii<pointArray.length; ii++ )
				{
					gp.lineTo( pointArray[ii].x, pointArray[ii].y );
				}
			}
			pathList.add( gp );			
		}
		else
		{
			//
			ArrayList lineListList = new ArrayList();
			ArrayList list = new ArrayList();
			for( int ii=0; ii<pointArray.length-1; ii++ )
			{
				final SGDrawingElementLine line = (SGDrawingElementLine)array[ii];
				final boolean eff = effArray[ii] & effArray[ii+1];
				line.setVisible( eff );
				if( eff )
				{
					if( true )
					{
						line.setTermPoints( pointArray[ii], pointArray[ii+1] );
					}
					list.add( line );
				}
				else
				{
					if( list.size()!=0 )
					{
						lineListList.add( list );
						list = new ArrayList();
					}
				}
			}
			if( list.size()!=0 )
			{
				lineListList.add( list );
			}

			// create a general path
			for( int ii=0; ii<lineListList.size(); ii++ )
			{
				final ArrayList lineList = (ArrayList)lineListList.get(ii);
				final GeneralPath gp = new GeneralPath();
				final SGDrawingElementLine el
					= (SGDrawingElementLine)lineList.get(0);
				final Line2D line = SGDrawingElementLine2D.getLine( el );
				gp.append( line, true );
				if( lineList.size() > 2 )
				{
					for( int jj=1; jj<lineList.size(); jj++ )
					{
						SGDrawingElementLine el2 = (SGDrawingElementLine)lineList.get(jj);
						final SGTuple2f next = el2.getEnd();
						gp.lineTo( next.x, next.y );
					}
				}

				pathList.add(gp);
			}
		}

		return true;
	}



	/**
	 * 
	 * @return
	 */
	public boolean setPropertiesOfDrawingElements()
	{
		for( int ii=0; ii<this.mDrawingElementArray.length; ii++ )
		{
			final SGDrawingElementLine line
				= (SGDrawingElementLine)this.mDrawingElementArray[ii];
			line.setType( this.mLineType );
			line.setColor( this.mColorList );
			line.setLineWidth( this.mLineWidth );
			line.setMagnification( this.mMagnification );
		}
		return true;
	}



	/**
	 * 
	 */
	public SGProperties getProperties()
	{
		LineProperties p = new LineProperties();
		this.getProperties(p);
		return p;
	}


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

		super.getProperties(p);

		LineProperties lp = (LineProperties)p;
		lp.setLineWidth( this.getLineWidth() );
		lp.setLineType( this.getLineType() );

		return true;
	}




	/**
	 * 
	 */
	public boolean setProperties( SGProperties p )
	{

		if( ( p instanceof LineProperties ) == false ) return false;

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

		LineProperties lp = (LineProperties)p;
		Float width = lp.getLineWidth();
		if( width==null )
		{
			return false;
		}
		Integer type = lp.getLineType();
		if( type==null )
		{
			return false;
		}

		this.setLineWidth( width.floatValue() );
		this.setLineType( type.intValue() );

		return true;
	}





	/**
	 * 
	 */
	public static class LineProperties extends ElementGroupProperties
	{

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


//		public static final String[] keys = {
//			KEY_LINE_WIDTH, KEY_LINE_TYPE
//		};


		/**
		 *
		 */
		public LineProperties()
		{
			super();
		}


		/**
		 * 
		 */
		public boolean equals( final Object obj )
		{
			if( ( obj instanceof LineProperties ) == false )
			{
				return false;
			}

			if( super.equals(obj) == false ) return false;

			LineProperties p = (LineProperties)obj;
			if( this.mLineProperties.equals(p.mLineProperties) == false ) return false;

//			for( int ii=0; ii<keys.length; ii++ )
//			{
//				if( this.getProperty(keys[ii]).equals(p.getProperty(keys[ii])) == false )
//				{
//					return false;
//				}
//			}

			return true;
		}


		/**
		 * 
		 */
/*		public String toString()
		{
			String str = new String("[");
			str += new String("visible="+visible+", ");
			str += new String("colorList="+colorList+", ");
			str += new String("type="+type+", ");
			str += new String("width="+width+", ");
			str += new String("]");

			return str;
		}
*/

		public Float getLineWidth()
		{
			return this.mLineProperties.getLineWidth();
//			final String value = this.getProperty(KEY_LINE_WIDTH);
//			return SGUtilityText.getFloat(value);
		}


		public Integer getLineType()
		{
			return this.mLineProperties.getLineType();
//			final String value = this.getProperty(KEY_LINE_TYPE);
//			return SGUtilityText.getInteger(value);
		}


		public void setLineWidth( final float w )
		{
			this.mLineProperties.setLineWidth(w);
//			if( w<0.0f )
//			{
//				return false;
//			}
//			this.setProperty( KEY_LINE_WIDTH, new Float(w).toString() );
//			return true;
		}

		public void setLineType( final int num )
		{
			this.mLineProperties.setLineType(num);
//			this.setProperty( KEY_LINE_TYPE, new Integer(num).toString() );
//			return true;
		}


	}


}



