
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.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

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
{

	public static final int MODE_ALL = 0;
	public static final int MODE_OMIT = 1;


	//
	protected int mMode = MODE_OMIT;


	/**
	 *
	 */
	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");
		}
		this.mLineWidth = width;

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

		return true;
	}


	/**
	 * 
	 * @param width
	 * @param unit
	 * @return
	 */
	public boolean setLineWidth( final float width, final String unit )
	{
		final double lw = SGUtilityText.convertToPoint( width, unit );
		if( this.setLineWidth( (float)lw ) == false )
		{
			return false;
		}

		return true;
	}


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

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

		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;
	}


	/**
	 * 
	 * @param unit
	 * @return
	 */
	public float getLineWidth( final String unit )
	{
		return (float)SGUtilityText.convertFromPoint( this.getLineWidth(), unit );
	}


	/**
	 * 
	 */
	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()
	{
		SGDrawingElementLine line
			= (SGDrawingElementLine)this.getDrawingElementInstance();
		line.setVisible( this.isVisible() );
		line.setLineWidth( this.getLineWidth() );
		line.setType( this.getLineType() );
		line.setColor( this.getColorList() );

		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_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
	 * @return
	 */
	public boolean readProperty( final Element el )
	{
		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;
			}
		}


		// color list
		str = el.getAttribute( KEY_COLOR_LIST );
		if( str.length()!=0 )
		{
			list = SGUtilityText.getColorList(str);
			if( list==null )
			{
				return false;
			}
			if( list.size()<1 )
			{
				return false;
			}
			final Color color = (Color)list.get(0);
			if( this.setColor( color ) == false )
			{
				return false;
			}
		}

		return true;
	}


	/**
	 * 
	 * @return
	 */
	protected SGDrawingElement getDrawingElementInstance()
	{
		return new SGDrawingElementLine2D();
	}
	

	/**
	 * 
	 * @return
	 */
	public boolean initDrawingElement( final int num )
	{
		return super.initDrawingElement( num - 1 );
	}


	/**
	 * 
	 * @return
	 */
	protected boolean initDrawingElement( final SGTuple2f[] array )
	{
		final int num = array.length;
		if( this.initDrawingElement(num) == false )
		{
			return false;
		}

		SGDrawingElement[] lArray = this.mDrawingElementArray;
		for( int ii=0; ii<lArray.length; ii++ )
		{
			((SGDrawingElementLine)lArray[ii]).setTermPoints( array[ii], array[ii+1] );
		}
		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] );
				}
			}

			// reduce the points
			SGTuple2f[] points = pointArray;

//System.out.println(this.mMode);
			if( this.mMode == MODE_DISPLAY )
			{
//				points = this.reducePointsInNoise( points );
				points = this.reduceClosePoints( points );
			}

			final int len = points.length;
			if( len>1 )
			{
				// create a general path
				final GeneralPath gp = new GeneralPath();
				final Line2D line = new Line2D.Float(
					points[0].x, points[0].y, points[1].x, points[1].y );
				gp.append( line, true );

				if( len > 2 )
				{
					for( int ii=0; ii<points.length; ii++ )
					{
						gp.lineTo( points[ii].x, points[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 )
				{
					Point2D pos = line.getP2();
					int tmpX = (int)pos.getX();
					int tmpY = (int)pos.getY();
					for( int jj=1; jj<lineList.size(); jj++ )
					{
						SGDrawingElementLine el2 = (SGDrawingElementLine)lineList.get(jj);
						final SGTuple2f next = el2.getEnd();

						// current location
						final float x = next.x;
						final float y = next.y;
	
						if( ((int)x==tmpX) & ((int)y==tmpY) )
						{
							continue;
						}

						gp.lineTo(x,y);

						// update temporary values
						tmpX = (int)x;
						tmpY = (int)y;
					}
				}

				pathList.add(gp);
			}
		}

		return true;
	}



	private SGTuple2f[] reduceClosePoints( SGTuple2f[] array )
	{
		final int len = array.length;
		if( len<2 )
		{
			return array;
		}

		ArrayList list = new ArrayList();
		list.add( array[0] );

		// temporary variables
		int tmpX = (int)array[0].x;
		int tmpY = (int)array[0].y;

		for( int ii=1; ii<len; ii++ )
		{
			// current location
			final float x = array[ii].x;
			final float y = array[ii].y;
	
			if( ((int)x==tmpX) & ((int)y==tmpY) )
			{
				continue;
			}

			list.add( array[ii] );

			// update temporary values
			tmpX = (int)x;
			tmpY = (int)y;
		}

		SGTuple2f[] arrayNew = (SGTuple2f[])list.toArray( new SGTuple2f[]{} );

		return arrayNew;
	}



	private SGTuple2f[] reducePointsInNoise( final SGTuple2f[] array )
	{
		final int len = array.length;
		if( len<2 )
		{
			return array;
		}

		ArrayList list = new ArrayList();
		list.add( array[0] );

		// temporary variables
		float tmpX = array[0].x;
		float tmpY = array[0].y;
		float minY = array[0].y;
		float maxY = array[0].y;
		int cnt=1;

		// a flag whether the current value is close to the temporay value
		boolean closeFlag = false;

		while( true )
		{
			// current location
			final float x = array[cnt].x;
			final float y = array[cnt].y;

			// if current x is very close to the temporary x value
			if( (int)x == (int)tmpX )
			{
				// update the min and max values
				minY = Math.min(y,minY);
				maxY = Math.max(y,maxY);
						
				// set the close flag true
				closeFlag = true;
			}
			else
			{
				// add a perpendicular line to the general path
				if( closeFlag )
				{
					list.add( new SGTuple2f(tmpX,minY) );
					list.add( new SGTuple2f(tmpX,maxY) );
					list.add( new SGTuple2f(tmpX,tmpY) );
				}

				list.add( new SGTuple2f(x,y) );

				minY = y;
				maxY = y;

				// set the close flag false
				closeFlag = false;
			}

			// update temporary values
			tmpX = x;
			tmpY = y;

			// increment the counter
			cnt++;
			if( cnt>=len )
			{
				break;
			}
		}

		// for the last point
		if( closeFlag )
		{
			list.add( new SGTuple2f(tmpX,minY) );
			list.add( new SGTuple2f(tmpX,maxY) );
		}

		SGTuple2f[] arrayNew = (SGTuple2f[])list.toArray( new SGTuple2f[]{} );

		return arrayNew;
	}



	/**
	 * 
	 * @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 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;

			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();
		}


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


		public void setLineWidth( final float w )
		{
			this.mLineProperties.setLineWidth(w);
		}

		public void setLineType( final int num )
		{
			this.mLineProperties.setLineType(num);
		}


	}


}



