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

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

import jp.riken.brain.ni.samuraigraph.base.SGAxis;
import jp.riken.brain.ni.samuraigraph.base.SGICopiable;
import jp.riken.brain.ni.samuraigraph.base.SGIDisposable;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElement;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementAxis;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementAxisBreak;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementGraph;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementGrid;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementLegend;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementShape;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementSignificantDifference;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementString;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElementTimingLine;
import jp.riken.brain.ni.samuraigraph.base.SGIMovable;
import jp.riken.brain.ni.samuraigraph.base.SGINode;
import jp.riken.brain.ni.samuraigraph.base.SGISelectable;
import jp.riken.brain.ni.samuraigraph.base.SGIUndoable;
import jp.riken.brain.ni.samuraigraph.base.SGProperties;
import jp.riken.brain.ni.samuraigraph.base.SGPropertyDialog;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2f;
import jp.riken.brain.ni.samuraigraph.base.SGUndoManager;
import jp.riken.brain.ni.samuraigraph.base.SGUtility;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityText;
import jp.riken.brain.ni.samuraigraph.figure.SGFigureElement;
import jp.riken.brain.ni.samuraigraph.figure.SGIArrowConstants;
import jp.riken.brain.ni.samuraigraph.figure.SGILineConstants;
import jp.riken.brain.ni.samuraigraph.figure.SGISymbolConstants;
import jp.riken.brain.ni.samuraigraph.figure.SGITimingLineConstants;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * A class managing timing lines.
 */
public class SGFigureElementTimingLine extends SGFigureElement
	implements SGIFigureElementTimingLine
{

	/**
	 * 
	 */	
	private SGIFigureElementAxis mAxisElement = null;


	/**
	 * 
	 */
	private SGTimingLineDialog mDialog = null;



	/**
	 * 
	 *
	 */
	public SGFigureElementTimingLine()
	{
		super();
		this.init();
	}


	/**
	 * 
	 *
	 */
	private void init()
	{
		SGDrawingElementArrow2D a = new SGDrawingElementArrow2D();

		a.setStartHeadType( SGISymbolConstants.SYMBOL_TYPE_VOID );
		a.setEndHeadType( SGIArrowConstants.SYMBOL_TYPE_ARROW_HEAD );
		a.setLineWidth( 3.0f );
		a.setLineType( SGILineConstants.LINE_TYPE_SOLID );
		a.setColor( Color.RED );
//		a.setHeadLineWidth( 2.0f );
		a.setHeadSize( 12.0f );
		a.setHeadOpenAngle( (float)Math.PI/5.0f );
		a.setHeadCloseAngle( (float)Math.PI/3.0f );

		a.setVisible(false);

		this.mGuideArrow = a;
	}


	public void dispose()
	{
		super.dispose();
		this.mDialog.dispose();
		this.mDialog = null;
	}


	/**
	 * 
	 */
	public String toString()
	{
		return "SGTimingLineEement";
	}


	/**
	 * 
	 * @return
	 */
	public String getClassDescription()
	{
		return "Timing Lines";
	}


	/**
	 * 
	 * @param element
	 */
	public void setAxisElement( final SGIFigureElementAxis element )
	{
		this.mAxisElement = element;
	}



	/**
	 * 
	 * @param x
	 * @param y
	 */
	public void guideToAdd( final int x, final int y )
	{

		if( this.getGraphRect().contains(x,y) == false )
		{
			this.mGuideArrow.setVisible(false);
			this.repaint();
			return;
		}

		ArrayList configList = new ArrayList();
		ArrayList distList = new ArrayList();
		this.getNearestAxisInfo( x, y, configList, distList );
		final int config = ((Integer)configList.get(0)).intValue();
		final float distance = ((Number)distList.get(0)).floatValue();

		final float length = distance;

		SGTuple2f start = new SGTuple2f(x,y);
		SGTuple2f end = new SGTuple2f();
		if( config == SGIFigureElementAxis.AXIS_HORIZONTAL_1 )
		{
			end.x = x;
			end.y = y + length;
		}
		else if( config == SGIFigureElementAxis.AXIS_HORIZONTAL_2 )
		{
			end.x = x;
			end.y = y - length;
		}
		else if( config == SGIFigureElementAxis.AXIS_PERPENDICULAR_1 )
		{
			end.x = x - length;
			end.y = y;
		}
		else if( config == SGIFigureElementAxis.AXIS_PERPENDICULAR_2 )
		{
			end.x = x + length;
			end.y = y;
		}

		this.mGuideArrow.setTermPoints( start, end );

		this.mGuideArrow.setVisible(true);
		this.repaint();
	}



	private void getNearestAxisInfo(
		final int x, final int y, final ArrayList configList, final ArrayList distList )
	{
		
		final float left = this.getGraphRectX();
		final float right = left + this.getGraphRectWidth();
		final float top = this.getGraphRectY();
		final float bottom = top + this.getGraphRectHeight();

		final float dl = x - left;
		final float dr = right - x;
		final float dt = y - top;
		final float db = bottom - y;

		float[] array = {dl,dr,dt,db};
		float min = Float.MAX_VALUE;
		for( int ii=0; ii<array.length; ii++ )
		{
			if( array[ii] < min )
			{
				min = array[ii];
			}
		}

		int config = -1;
		if( min==db )
		{
			config = SGIFigureElementAxis.AXIS_HORIZONTAL_1;
		}
		else if( min==dt )
		{
			config = SGIFigureElementAxis.AXIS_HORIZONTAL_2;
		}
		else if( min==dl )
		{
			config = SGIFigureElementAxis.AXIS_PERPENDICULAR_1;
		}
		else if( min==dr )
		{
			config = SGIFigureElementAxis.AXIS_PERPENDICULAR_2;
		}

		configList.add( new Integer(config) );
		distList.add( new Float(min) );
	}



	/**
	 * 
	 */
	public boolean addTimingLine( final int x, final int y )
	{
		if( this.getGraphRect().contains(x,y) == false )
		{
			return false;
		}

		final ArrayList configList = new ArrayList();
		final ArrayList distList = new ArrayList();
		this.getNearestAxisInfo( x, y, configList, distList );
		final int config = ((Integer)configList.get(0)).intValue();

		final SGAxis axis = this.mAxisElement.getAxisInPlane( config );
		final double value = this.mAxisElement.getValue( config, x, y );

		// create an instance
		final TimingLine line = new TimingLine();

		line.setAxis( axis );
		line.setValue( value );
		line.setMagnification( this.getMagnification() );

		// add to the list
		this.addToList( line );

		// hide the guide
		this.mGuideArrow.setVisible(false);

		// set focus
		this.updateFocusedObjectsList( line, 0 );

		// update the history
		this.setChanged(true);
		this.notifyToRoot();

		return true;
	}



	protected void addToList( final ChildObject obj )
	{
		super.addToList(obj);

		TimingLine line = (TimingLine)obj;

		// location
		line.initDrawingElement(2);
		line.setDrawingElementsLocation();

		// properties
		line.setPropertiesOfDrawingElements();
		line.initPropertiesHistory();
	}
	
	
	

	/**
	 * 
	 * @return
	 */
	public boolean setDialogOwner( final Frame frame )
	{
		super.setDialogOwner(frame);
		this.createTimingLineDialog();
		return true;
	}


	/**
	 * 同期を取る
	 */
	public boolean synchronize( final SGIFigureElement element, final String msg )
	{

		boolean flag = true;
		if( element instanceof SGIFigureElementLegend )
		{

		}
		else if( element instanceof SGIFigureElementAxis )
		{
			final SGIFigureElementAxis aElement = (SGIFigureElementAxis)element;
			flag = this.synchronizeToAxisElement( aElement, msg );
		}
		else if( element instanceof SGIFigureElementString )
		{
			
		}
		else if( element instanceof SGIFigureElementGraph )
		{
			
		}
		else if( element instanceof SGIFigureElementAxisBreak )
		{
			
		}
		else if( element instanceof SGIFigureElementSignificantDifference )
		{
			
		}
		else if( element instanceof SGIFigureElementTimingLine )
		{
			
		}
		else if( element instanceof SGIFigureElementGrid )
		{

		}
		else if( element instanceof SGIFigureElementShape )
		{

		}
		else
		{
			flag = this.synchronizeArgument( element, msg );
		}

		return flag;
	}


	/**
	 * Synchronize the element given by the argument.
	 * @param element An object to be synchronized.
	 */
	public boolean synchronizeArgument( final SGIFigureElement element, final String msg )
	{
	    // this shouldn't happen
	    throw new Error();
	}


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



	/**
	 * 
	 */
	public String getTagName()
	{
		return TAG_NAME_TIMING_LINES;
	}
	
	

	/**
	 * 
	 */
	public boolean writeProperty( final Element el )
	{
		return true;
	}


	
	/**
	 * 
	 */
	public boolean readProperty( final Element element )
	{
		NodeList nList = element.getElementsByTagName( TimingLine.TAG_NAME_TIMING_LINE );
		for( int ii=0; ii<nList.getLength(); ii++ )
		{
			Node node = nList.item(ii);
			if( node instanceof Element )
			{
				Element el = (Element)node;
				TimingLine line = new TimingLine();
				if( line.readProperty(el) == false )
				{
					return false;
				}
				this.addToList( line );
			}
		}
		
		return true;
	}

	
	/**
	 * 
	 */
	public Element createElement( Document document )
	{
		Element el = this.createThisElement( document );
		if( el==null )
		{
			return null;
		}

		ArrayList list = this.getVisibleChildList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			TimingLine line = (TimingLine)list.get(ii);
			if( line.isValid() == false )
			{
				continue;
			}
			Element elTiming = line.createElement( document );
			if( elTiming==null )
			{
				return null;
			}
			el.appendChild( elTiming );
		}
		return el;
	}



	/**
	 * ズーム
	 */
	public boolean zoom( final float ratio )
	{
		super.zoom(ratio);

		List list = this.mChildList;
		for( int ii=0; ii<list.size(); ii++ )
		{
			final SGElementGroupTimingLine line = (SGElementGroupTimingLine)list.get(ii);
			line.zoom(ratio);
		}

		return true;
	}


	/**
	 * 
	 */
	public boolean setGraphRect(
		final float x, final float y, final float width, final float height )
	{
		super.setGraphRect(x,y,width,height);
		if( this.setAllDrawingElementsLocation() == false )
		{
			return false;
		}
		return true;
	}


	/**
	 * タイミング線の位置を決定
	 */

	private boolean calcLocationOfTimingLine(
		final double value,
		final SGAxis axis,
		final boolean horizontal,
		final SGTuple2f startPoint,
		final SGTuple2f endPoint )
	{

		final float location = this.calcLocation( value, axis, horizontal );
		if( Float.isNaN( location ) )
		{
			return false;
		}

		Rectangle2D rect = this.getGraphRect();
		if( horizontal )
		{
			startPoint.x = location;
			startPoint.y = (float)rect.getY();
			endPoint.x = location;
			endPoint.y = startPoint.y + (float)( rect.getHeight() );
		}
		else
		{
			startPoint.x = (float)rect.getX();
			startPoint.y = location;
			endPoint.x = startPoint.x + (float)( rect.getWidth() );
			endPoint.y = location;
		}

		return true;
	}



	/**
	 * 
	 */
	private boolean createTimingLineDialog()
	{
		final SGTimingLineDialog dg = new SGTimingLineDialog( this.mDialogOwner, true );

		this.mDialog = dg;

		return true;
	}



	/**
	 * 
	 * @return
	 */
	private ArrayList getVisibleTimingElementListInside()
	{
		ArrayList list = new ArrayList();
		ArrayList lList = this.getVisibleChildList();
		for( int ii=0; ii<lList.size(); ii++ )
		{
			TimingLine line = (TimingLine)lList.get(ii);
			if( this.isInsideRange( line ) )
			{
				list.add( line );
			}
		}

		return list;
	}


	/**
	 * 
	 * @param groupSet
	 * @return
	 */
	private boolean isInsideRange( SGElementGroupTimingLine line )
	{
		final SGAxis axis = line.mAxis;
		final double value = line.mValue;
		return axis.insideRange(value);
	}
	
	public boolean onKeyPressed( final KeyEvent e )
	{
		boolean effective = false;
		final int keycode = e.getKeyCode();
		final int mod = e.getModifiersEx();
		final boolean isShiftPressed = ( ( mod & InputEvent.SHIFT_DOWN_MASK ) != 0 );
		int dx = 0;
		int dy = 0;
		switch (keycode) {
		case KeyEvent.VK_UP:
		    if ( isShiftPressed ){
		    	dy = -1;
		    } else {
		    	dy = -10;
		    }
			break;
		case KeyEvent.VK_DOWN:
		    if ( isShiftPressed ){
		    	dy = 1;
		    } else {
		    	dy = 10;
		    }
			break;
		case KeyEvent.VK_LEFT:
		    if ( isShiftPressed ){
		    	dx = -1;
		    } else {
		    	dx = -10;
		    }
			break;
		case KeyEvent.VK_RIGHT:
		    if ( isShiftPressed ){
		    	dx = 1;
		    } else {
		    	dx = 10;
		    }
			break;
		}
		if (dx != 0 || dy != 0) {
			final ArrayList list = this.getVisibleChildList();
			for( int ii=list.size()-1; ii>=0; ii-- ) {
				final TimingLine el = (TimingLine)list.get(ii);
				if ( el.isSelected() ) {
					if( this.setDirectlyBefore( el ) == false ) return false;
					el.translate( (float)dx, (float) dy );
					if( this.setDirectlyAfter( el ) == false ) return false;
					effective = true;
				}
			}
		}
		return effective;
	}


	/**
	 * 
	 */
	public boolean onMouseClicked( final MouseEvent e )
	{

		ArrayList list = this.getVisibleTimingElementListInside();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			final TimingLine line = (TimingLine)list.get(ii);
			if( line.isValid() == false )
			{
				continue;
			}
			if( this.clickDrawingElements(line,e) )
			{
				return true;
			}
		}

		return false;
	}

	
	/**
	 * 
	 */
	private boolean clickDrawingElements(
		final TimingLine el, final MouseEvent e )
	{
		final int x = e.getX();
		final int y = e.getY();
		final int cnt = e.getClickCount();

		// clicked on the line elements
		if( el.contains(x,y) )
		{
			this.updateFocusedObjectsList( el, e );

			if( cnt==1 )
			{
				if( SwingUtilities.isLeftMouseButton(e) )
				{

				}
				else if( SwingUtilities.isRightMouseButton(e) )
				{
					el.getPopupMenu().show( this.getComponent(), x, y );
				}
			}
			else if( cnt==2 )
			{
				if( SwingUtilities.isLeftMouseButton(e) )
				{
					this.setPropertiesOfSelectedObjects();
				}
			}

			return true;
		}

		return false;
	}




	/**
	 * 
	 */
	public boolean onMousePressed( final MouseEvent e )
	{

		final int x = e.getX();
		final int y = e.getY();

		ArrayList list = this.getVisibleTimingElementListInside();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			final TimingLine el = (TimingLine)list.get(ii);
			if( el.isValid() == false )
			{
				continue;
			}
			if( el.contains(x,y) )
			{
				this.mPressedPoint = e.getPoint();
				this.setMouseCursor( Cursor.MOVE_CURSOR );
				if( el.isSelected() )
				{
					this.mDraggableFlag = true;
				}
				return true;
			}
		}


		return false;
	}



	/**
	 * 
	 */
	public boolean onMouseDragged( final MouseEvent e )
	{
		if( this.mPressedPoint==null )
		{
			return false;
		}
		if( this.mDraggableFlag == false )
		{
			return false;
		}

		final int dx = e.getX() - this.mPressedPoint.x;
		final int dy = e.getY() - this.mPressedPoint.y;
		
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			TimingLine el = (TimingLine)list.get(ii);
			if( el.isValid() == false )
			{
				continue;
			}
			el.translate(dx,dy);
		}
		
		this.mPressedPoint = e.getPoint();

		return true;
	}


	/**
	 * 
	 */
	public boolean onMouseReleased( final MouseEvent e )
	{
		final int x = e.getX();
		final int y = e.getY();

		ArrayList list = this.getFocusedObjectsList();
		boolean onLine = false;
		for( int ii=0; ii<list.size(); ii++ )
		{
			TimingLine el = (TimingLine)list.get(ii);
			if( el.isValid() == false )
			{
				continue;
			}

			if( el.contains(x,y) )
			{
				onLine = true;
			}
		}

		if( onLine )
		{
			this.setMouseCursor( Cursor.HAND_CURSOR );
		}
		else
		{
			this.setMouseCursor( Cursor.DEFAULT_CURSOR );
		}

		this.unselectOuterLines();

		this.mDraggableFlag = false;
		this.notifyToRoot();
		
		return true;
	}
	
	
	/**
	 * 
	 */
	public boolean onDrawingElement( final int x, final int y )
	{
		ArrayList list = this.getVisibleTimingElementListInside();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			final TimingLine el = (TimingLine)list.get(ii);
			if( el.isValid() == false )
			{
				continue;
			}
			final boolean flag = el.contains(x,y);
			if( flag )
			{
				this.setMouseCursor( Cursor.HAND_CURSOR );
				return true;
			}
		}

		return false;
	}



	/**
	 * 
	 * @return
	 */
	public ArrayList getPropertyDialogObserverList()
	{
		return this.getFocusedObjectsList();
	}


	
	/**
	 * 
	 * @return
	 */
	public boolean setTemporaryPropertiesOfFocusedObjects()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			TimingLine el = (TimingLine)list.get(ii);
			el.mTemporaryProperties = el.getProperties();
		}
		return true;
	}



	/**
	 * 
	 * @return
	 */
	public boolean setChangedFocusedObjects()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			TimingLine el = (TimingLine)list.get(ii);
			SGProperties temp = el.mTemporaryProperties;
			if( temp!=null )
			{
				SGProperties p = el.getProperties();
				if( p.equals(temp)==false )
				{
					el.setChanged(true);
				}
			}
		}
		return true;
	}



	/**
	 * An arrow for the guide to add a timing line.
	 */
	private SGDrawingElementArrow2D mGuideArrow = null;


	
	/**
	 * 
	 */
	public void paintGraphics( Graphics g, boolean clip )
	{
		Graphics2D g2d = (Graphics2D)g;
		
		ArrayList list = this.getVisibleTimingElementListInside();
		Rectangle2D gRect = this.getGraphRect();


		// draw guide arrow
		if( this.mGuideArrow.isVisible() )
		{
			this.mGuideArrow.paint( g2d, gRect );
		}


		// グラフ描画領域のクリッピング
		if( clip )
		{
			SGUtilityForFigureElementJava2D.clipGraphRect(this,g2d);
		}


		// draw timing line
		for( int ii=0; ii<list.size(); ii++ )
		{
			TimingLine tl = (TimingLine)list.get(ii);
			if( tl.isValid() == false )
			{
				continue;
			}
			if( clip )
			{
				tl.paintElement(g2d);
			}
			else
			{
				tl.paintElement(g2d,gRect);
			}
		}


		if( clip )
		{
			g2d.setClip( this.getViewBounds() );
		}


		// draw symbols around all objects
		if( this.mSymbolsVisibleFlagAroundAllObjects )
		{
			for( int ii=0; ii<list.size(); ii++ )
			{
				TimingLine el = (TimingLine)list.get(ii);
				if( el.isValid() == false )
				{
					continue;
				}
				ArrayList pList = el.getAnchorPointList();
				SGUtilityForFigureElementJava2D.drawAnchorAsChildObject(
					pList, g2d );
			}
		}

		// draw symbols around focused objects
		if( this.mSymbolsVisibleFlagAroundFocusedObjects )
		{
			ArrayList fList = new ArrayList();
			this.getFocusedObjectsList( fList );
			for( int ii=0; ii<fList.size(); ii++ )
			{
				TimingLine el = (TimingLine)fList.get(ii);
				if( el.isValid() == false )
				{
					continue;
				}
				ArrayList pList = el.getAnchorPointList();
				SGUtilityForFigureElementJava2D.drawAnchorAsFocusedObject(
					pList, g2d );
			}
		}

	}


	/**
	 * 
	 */
	protected boolean removeTimingElementGroupSet( final SGElementGroupTimingLine line )
	{
		this.mChildList.remove(line);
		return false;
	}



	/**
	 * 
	 */
	protected boolean synchronizeToAxisElement( final SGIFigureElementAxis aElement, final String msg )
	{
		this.setAllDrawingElementsLocation();
		this.unselectOuterLines();
		return true;
	}


	/**
	 * 全描画要素の位置を決定
	 */
	protected boolean setAllDrawingElementsLocation()
	{
		ArrayList list = this.getVisibleChildList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			final TimingLine line
				= (TimingLine)list.get(ii);
			if( line.setDrawingElementsLocation() == false )
			{
				return false;
			}
		}

		return true;
	}


	// set unselected the lines out of range
	private void unselectOuterLines()
	{
		ArrayList list = this.getVisibleChildList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			final TimingLine line = (TimingLine)list.get(ii);
			final boolean inside = this.isInsideRange( line );
			if( line.isSelected() & !inside )
			{
				line.setSelected( false );
			}
		}
	}


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

		TimingElementProperties gp = (TimingElementProperties)p;
		gp.visibleTimingElementList = this.getVisibleChildList();

		return true;
	}


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

		TimingElementProperties gp = (TimingElementProperties)p;


		boolean flag;
		flag = this.setVisibleChildList( gp.visibleTimingElementList );
		if( !flag )
		{
			return false;
		}

		return true;

	}



	/**
	 * Create copies of the focused objects.
	 * @return
	 */
	public boolean duplicateFocusedObjects()
	{
		final int ox = (int)( this.mMagnification*OFFSET_DUPLICATED_OBJECT_X );
		final int oy = (int)( this.mMagnification*OFFSET_DUPLICATED_OBJECT_Y );

		ArrayList cList = this.duplicateObjects();
		for( int ii=0; ii<cList.size(); ii++ )
		{
			// duplicate
			TimingLine el = (TimingLine)cList.get(ii);

			// translate the duplicate
			el.translate( ox, oy );

//			el.setDrawingElementsLocation();

			// set selected
			el.setSelected(true);
			
			// add to the list
			this.addToList( el );
		}

		if( cList.size()!=0 )
		{
			this.setChanged(true);
		}
		
//		// set the location of drawing elements
//		this.setAllDrawingElementsLocation();

//		this.repaint();

		return true;
	}



	/**
	 * Paste the objects.
	 * @param list of the objects to be pasted
	 * @return true:succeeded, false:failed
	 */
	public boolean paste( ArrayList list )
	{
		final float mag = this.getMagnification();
		final int ox = (int)( mag*OFFSET_DUPLICATED_OBJECT_X );
		final int oy = (int)( mag*OFFSET_DUPLICATED_OBJECT_Y );

		int cnt = 0;
		for( int ii=0; ii<list.size(); ii++ )
		{
			Object obj = list.get(ii);
			if( obj instanceof TimingLine )
			{
				TimingLine line = (TimingLine)obj;

				// translate the instance to be pasted
				line.translate( ox, oy );

				SGProperties p = line.getProperties();
				
				TimingLine el = new TimingLine();
				el.setMagnification(mag);
				el.setProperties(p);

				el.mAxis = this.mAxisElement.getAxisInCube( line.mTempAxis );

//				el.setDrawingElementsLocation();

				// add to the list
				this.addToList(el);

				// initialize history
				el.initPropertiesHistory();

				cnt++;
			}
		}

//		this.setAllDrawingElementsLocation();
		
		if( cnt!=0 )
		{
			this.setChanged(true);
		}

//		this.repaint();

		return true;
	}



	/**
	 * 
	 */
	protected Set getAvailableChildSet()
	{
		Set set = new HashSet();
		List mList = this.getMementoList();
		for( int ii=0; ii<mList.size(); ii++ )
		{
			TimingElementProperties p = (TimingElementProperties)mList.get(ii);
			set.addAll( p.visibleTimingElementList );
		}

		return set;		
	}


	//
	private TimingLine getTimingLine( final int id )
	{
		TimingLine el = (TimingLine)this.getChildObject(id);
		if( el == null ) return null;
		if( el.isVisible() == false ) return null;
		return el;
	}

	public boolean setAxisDirectly( final int id, final int value )
	{
		TimingLine el = this.getTimingLine(id);
		if( el==null ) return false;
		if( this.setDirectlyBefore(el) == false ) return false;
		if( el.setAxisLocation( value ) == false ) return false;
		if( this.setDirectlyAfter(el) == false ) return false;
		return true;
	}

	public boolean setLineWidthDirectly( final int id, final float value, final String unit )
	{
		TimingLine el = this.getTimingLine(id);
		if( el==null ) return false;
		if( this.setDirectlyBefore(el) == false ) return false;
		if( el.setLineWidth( value, unit ) == false ) return false;
		if( this.setDirectlyAfter(el) == false ) return false;
		return true;
	}

	public boolean setLineTypeDirectly( final int id, final int value )
	{
		TimingLine el = this.getTimingLine(id);
		if( el==null ) return false;
		if( this.setDirectlyBefore(el) == false ) return false;
		if( el.setLineType( value ) == false ) return false;
		if( this.setDirectlyAfter(el) == false ) return false;
		return true;
	}

	public boolean setColorDirectly( final int id, final Color value )
	{
		TimingLine el = this.getTimingLine(id);
		if( el==null ) return false;
		if( this.setDirectlyBefore(el) == false ) return false;
		if( el.setColor( value ) == false ) return false;
		if( this.setDirectlyAfter(el) == false ) return false;
		return true;
	}


	private boolean setDirectlyBefore( TimingLine el )
	{
		return el.prepare();
	}


	private boolean setDirectlyAfter( TimingLine el )
	{
		if( el.commit() == false )
		{
			return false;
		}
		this.notifyChange();
		this.notifyToRoot();
		this.repaint();
		return true;
	}



	/**
	 * @author  okumura
	 */
	private class TimingLine extends SGElementGroupTimingLine
		implements ActionListener, SGIUndoable, SGISelectable, SGICopiable,
			SGIMovable, SGITimingLineDialogObserver, SGINode, SGIDisposable,
			ChildObject, SGITimingLineConstants
	{

		public void finalize()
		{
//			System.out.println("finalize:"+this);
		}


		/**
		 * 
		 */
		public void dispose()
		{
			super.dispose();
			this.mAxis = null;
			this.mParallelAxis = null;
			this.mPopupMenu = null;
			this.mUndoManager.dispose();
			this.mUndoManager = null;
			this.mTemporaryProperties = null;
		}


		/**
		 * 
		 */
		private int mID;


		public int getID()
		{
			return this.mID;
		}

		public boolean setID( final int id )
		{
			this.mID = id;
			return true;
		}

		
		
		/**
		 * Flag whether this object is focused.
		 */
		private boolean mSelectedFlag = false;


		/**
		 * Get the flag as a focused object.
		 * @return whether this object is focused.
		 */
		public boolean isSelected()
		{
			return this.mSelectedFlag;
		}


		/**
		 * Set the flag as a focused object.
		 * @param b focused
		 */
		public void setSelected( final boolean b )
		{
			this.mSelectedFlag = b;
		}


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


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


		/**
		 * 
		 */
		public boolean setLineWidth( final float width, final String unit )
		{
			final double conv = SGUtilityText.convert( width, unit, LINE_WIDTH_UNIT );
			if( conv < LINE_WIDTH_MIN_VALUE ) return false;
			if( conv > LINE_WIDTH_MAX_VALUE ) return false;

			return this.setLineWidth( (float)SGUtilityText.convertToPoint( width, unit ) );
		}

		
		/**
		 * 
		 */
		private SGProperties mTemporaryProperties = null;


		/**
		 * 
		 */
		public boolean prepare()
		{
			this.mTemporaryProperties = this.getProperties();
			return true;
		}


		/**
		 * 
		 */
		private JPopupMenu mPopupMenu = new JPopupMenu();

		
		/**
		 * 
		 *
		 */
		protected TimingLine()
		{
			super();
			this.init();
		}
		
		
		/**
		 * 
		 */
		private boolean init()
		{
			this.setLineWidth( DEFAULT_LINE_WIDTH, LINE_WIDTH_UNIT );
			this.setLineType( DEFAULT_LINE_TYPE );
			this.setColor( DEFAULT_LINE_COLOR );

			this.createPopupMenu();

			return true;
		}

		
		/**
		 * 
		 * @return
		 */
		public String getClassDescription()
		{
			return "";
		}


		/**
		 * 
		 * @return
		 */
		public String getInstanceDescription()
		{
			String str = "";
			str += this.mID + ": ";

			String config = SGFigureElementTimingLine.this.mAxisElement.getLocationName( this.mAxis );
			str += config + ", ";

			String s;
			if( this.isHorizontal() )
			{
				s = "X";
			}
			else
			{
				s = "Y";
			}
			str += s + "=" + this.getValue();

			return str;
		}


		/**
		 * 
		 * @return
		 */
		private ArrayList getAnchorPointList()
		{
			ArrayList list = new ArrayList();

			Point2D pos0 = new Point2D.Float();
			Point2D pos1 = new Point2D.Float();
			
			final boolean flag = this.isHorizontal();
			float location = calcLocation( this.mValue, this.mAxis, flag );
			Rectangle2D rect = getGraphRect();
			if( flag )
			{
				final float y0 = (float)rect.getY();
				final float y1 = y0 + (float)rect.getHeight();
				pos0.setLocation( location, y0 );
				pos1.setLocation( location, y1 );
			}
			else
			{
				final float x0 = (float)rect.getX();
				final float x1 = x0 + (float)rect.getWidth();
				pos0.setLocation( x0, location );
				pos1.setLocation( x1, location );
			}
			
			list.add( pos0 );
			list.add( pos1 );

			return list;
		}

		
		
		/**
		 * 
		 */
		private boolean createPopupMenu()
		{
			JPopupMenu p = this.mPopupMenu;
			
			p.setBounds( 0, 0, 100, 100 );

			p.add( new JLabel( "  -- Timing Line --" ) );
			p.addSeparator();

			SGUtility.addItem( p, this, MENUCMD_MOVE_TO_FRONT );
			SGUtility.addItem( p, this, MENUCMD_MOVE_TO_BACK );

			p.addSeparator();

			SGUtility.addItem( p, this, MENUCMD_CUT );
			SGUtility.addItem( p, this, MENUCMD_COPY );
			SGUtility.addItem( p, this, MENUCMD_PASTE );

			p.addSeparator();

			SGUtility.addItem( p, this, MENUCMD_DELETE );
			SGUtility.addItem( p, this, MENUCMD_DUPLICATE );

			p.addSeparator();

			SGUtility.addItem( p, this, MENUCMD_PROPERTY );

			return true;
		}


		/**
		 * 
		 */
		public JPopupMenu getPopupMenu()
		{
			return this.mPopupMenu;
		}


		/**
		 * 
		 */
		public int getAxisLocation()
		{
			return SGFigureElementTimingLine.this.mAxisElement.getLocationInPlane( this.mAxis );
		}


		/**
		 * 
		 */
		public boolean setAxisLocation( final int config )
		{
			SGIFigureElementAxis aElement = SGFigureElementTimingLine.this.mAxisElement;
			SGAxis axis = aElement.getAxisInPlane( config );
			if( axis==null )
			{
				return false;
			}
			this.mAxis = axis;
			return true;
		}



		/**
		 * Called when the location of data points have changed.
		 */
		private boolean setDrawingElementsLocation()
		{
			final SGAxis axis = this.mAxis;
			final double value = this.mValue;
			final boolean horizontal = this.isHorizontal();

			SGTuple2f[] array = new SGTuple2f[2];
			array[0] = new SGTuple2f();
			array[1] = new SGTuple2f();
			
			if( calcLocationOfTimingLine(
					value,
					axis,
					horizontal,
					array[0],
					array[1] ) == false )
			{
				this.setValid(false);
				return false;
			}
			this.setValid(true);

			if( this.setLocation( array ) == false )
			{
				return false;
			}

			return true;
		}



		private boolean mValidFlag = true;

		public boolean isValid()
		{
			return this.mValidFlag;
		}

		public void setValid( final boolean b )
		{
			this.mValidFlag = b;
		}


		/**
		 * Translate the object.
		 * @param dx
		 * @param dy
		 */
		public void translate( float dx, float dy )
		{
			SGAxis axis = this.mAxis;
			final boolean horizontal = this.isHorizontal();
			float location = SGFigureElementTimingLine.this.calcLocation( this.getValue(), axis, horizontal );
			float locationNew;
			if( this.isHorizontal() )
			{
				locationNew = location + dx;
			}
			else
			{
				locationNew = location + dy;
			}
			double value = SGFigureElementTimingLine.this.calcValue( locationNew, axis, horizontal );
			value = SGFigureElementTimingLine.this.getNumberInRangeOrder( value, axis );
			this.setValue( value );

			this.setDrawingElementsLocation();
		}
		
		
		/**
		 * 
		 * @return
		 */
		private boolean isHorizontal()
		{
			final ArrayList hAxisList = SGFigureElementTimingLine.this.mAxisElement.getHorizontalAxisList();
			final ArrayList pAxisList = SGFigureElementTimingLine.this.mAxisElement.getPerpendicularAxisList();

			Boolean hFlag = null;
			if( hAxisList.contains( this.mAxis ) )
			{
				hFlag = Boolean.TRUE;
			}
			if( pAxisList.contains( this.mAxis ) )
			{
				hFlag = Boolean.FALSE;
			}
			if( hFlag == null )
			{
				throw new Error("");
			}

			return hFlag.booleanValue();
		}
		
		
		
		/**
		 * 
		 * @param config
		 * @param value
		 * @return
		 */
		public boolean hasValidValue( final int config, final Number value )
		{
			final SGAxis axis = (config==-1) ? this.mAxis : SGFigureElementTimingLine.this.mAxisElement.getAxisInPlane( config );
			final double v = (value!=null) ? value.doubleValue() : this.getValue();
			return axis.isValidValue(v);
		}



		/**
		 * 
		 */
		public void actionPerformed( final ActionEvent e )
		{
//			final Object source = e.getSource();
			final String command = e.getActionCommand();

			if( command.equals( MENUCMD_PROPERTY ) )
			{
				SGFigureElementTimingLine.this.setPropertiesOfSelectedObjects();
			}
			else if(
				command.equals( MENUCMD_COPY )
				| command.equals( MENUCMD_CUT )
				| command.equals( MENUCMD_PASTE )
				| command.equals( MENUCMD_DELETE )
				| command.equals( MENUCMD_DUPLICATE )
				| command.equals( MENUCMD_MOVE_TO_FRONT )
				| command.equals( MENUCMD_MOVE_TO_BACK )
			)
			{
				notifyToListener( command );
			}

		}



		/**
		 * 
		 */
		public boolean commit()
		{
			SGProperties pTemp = this.mTemporaryProperties;
			SGProperties pPresent = this.getProperties();
			if( pTemp.equals(pPresent) == false )
			{
				this.setChanged(true);
			}
			this.mTemporaryProperties = null;

			this.setDrawingElementsLocation();

			repaint();
			notifyChange();

			return true;
		}


		/**
		 * 
		 */
		public boolean cancel()
		{
			if( this.setProperties( this.mTemporaryProperties ) == false )
			{
				return false;
			}

			this.setDrawingElementsLocation();

			repaint();
			notifyChange();

			return true;
		}



		/**
		 * 
		 */
		public boolean preview()
		{
			this.setDrawingElementsLocation();

			repaint();
			notifyChange();

			return true;
		}


		/**
		 * Returns a property dialog.
		 * @return property dialog
		 */
		public SGPropertyDialog getPropertyDialog()
		{
			return SGFigureElementTimingLine.this.mDialog;
		}


		/**
		 * Returns a list of child nodes.
		 * @return a list of chid nodes
		 */
		public ArrayList getChildNodes()
		{
			return new ArrayList();
		}



		//
		private SGUndoManager mUndoManager = new SGUndoManager(this);


		/**
		 * 
		 * @return
		 */
		public SGProperties getMemento()
		{
			return this.getProperties();
		}


		/**
		 * 
		 * @param p
		 * @return
		 */
		public boolean setMemento( SGProperties p )
		{
			return this.setProperties(p);
		}


		/**
		 * 
		 * @return
		 */
		public boolean isUndoable()
		{
			return this.mUndoManager.isUndoable();
		}

	
		/**
		 * 
		 * @return
		 */
		public boolean isRedoable()
		{
			return this.mUndoManager.isRedoable();
		}


		/**
		 * 
		 */
		public boolean initPropertiesHistory()
		{
			return this.mUndoManager.initPropertiesHistory();
		}


		/**
		 * アンドゥを行う
		 */
		public boolean setMementoBackward()
		{
			if( this.mUndoManager.setMementoBackward() == false )
			{
				return false;
			}

			setAllDrawingElementsLocation();
			notifyChangeOnUndo();

			return true;
		}


		/**
		 * リドゥを行う
		 */
		public boolean setMementoForward()
		{
			if( this.mUndoManager.setMementoForward() == false )
			{
				return false;
			}

			setAllDrawingElementsLocation();
			notifyChangeOnUndo();

			return true;
		}



		/**
		 * アンドゥ操作を依頼する
		 */
		public boolean undo()
		{
			return this.setMementoBackward();
		}



		/**
		 * リドゥ操作を依頼する
		 */
		public boolean redo()
		{
			return this.setMementoForward();
		}


		/**
		 * 履歴の更新の実行。アンドゥ対象の操作が行われたときに、真っ先に呼ばれる。
		 */
		public boolean updateHistory()
		{
			return this.mUndoManager.updateHistory();
		}


		/**
		 * 
		 */
		public void initUndoBuffer()
		{
			this.mUndoManager.initUndoBuffer();
		}


		/**
		 * 
		 *
		 */
		public void notifyToRoot()
		{
			SGFigureElementTimingLine.this.notifyToRoot();
		}


		/**
		 * 
		 * @return
		 */
		public boolean isChanged()
		{
			return this.mUndoManager.isChanged();
		}

				
		public void setChanged( final boolean b )
		{
			this.mUndoManager.setChanged( b );
		}

		public boolean isChangedRoot()
		{
			return this.isChanged();
		}


		/**
		 * 
		 */
		public Element createElement( final Document document )
		{
			Element element = document.createElement( this.getTagName() );
			if( this.writeProperty( element ) == false )
			{
				return null;
			}
			return element;
		}


		
		public static final String TAG_NAME_TIMING_LINE = "TimingLine";
		
		public String getTagName()
		{
			return TAG_NAME_TIMING_LINE;
		}
		
		

		/**
		 * 
		 */
		public boolean writeProperty( final Element el )
		{
			if( super.writeProperty(el) == false )
			{
				return false;
			}
			el.setAttribute( KEY_AXIS_POSITION, SGFigureElementTimingLine.this.mAxisElement.getLocationName( this.mAxis ) );
			el.setAttribute( KEY_VALUE, Double.toString( this.mValue ) );
			return true;
		}

		
		/**
		 * 
		 * @param el
		 * @param p
		 * @return
		 */
		public boolean readProperty( final Element el )
		{
			if( super.readProperty(el) == false )
			{
				return false;
			}
			
			String str = null;
			Number num = null;
//			Color cl = null;
//			Boolean b = null;
//			ArrayList list = null;


			// axis
			str = el.getAttribute( KEY_AXIS_POSITION );
			if( str.length()!=0 )
			{
				SGAxis axis = SGFigureElementTimingLine.this.mAxisElement.getAxis(str);
				if( axis==null )
				{
					return false;
				}
				this.setAxis( axis );
			}

			
			// value
			str = el.getAttribute( KEY_VALUE );
			if( str.length()!=0 )
			{
				num = SGUtilityText.getDouble(str);
				if( num==null )
				{
					return false;
				}
				final double value = num.doubleValue();
				if( this.mAxis.isValidValue(value) == false )
				{
					return false;
				}
				this.setValue( value );
			}

			return true;
		}

	
	
	
		/**
		 * 
		 */
		public Object copy()
		{
			TimingLine el = new TimingLine();
			el.setMagnification( this.mMagnification );
			el.setProperties( this.getProperties() );
			el.mTempAxis = SGFigureElementTimingLine.this.mAxisElement.getLocationInCube( this.mAxis );
			return el;
		}

		private int mTempAxis = -1;

	}

	
	
	
	/**
	 * 
	 */
	public static class TimingElementProperties extends SGProperties
	{

		ArrayList visibleTimingElementList = new ArrayList();


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


		public void dispose()
		{
			this.visibleTimingElementList.clear();
			this.visibleTimingElementList = null;
		}

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

			TimingElementProperties p = (TimingElementProperties)obj;
			if( p.visibleTimingElementList.equals(this.visibleTimingElementList) == false )
			{
				return false;
			}

			return true;
		}


		/**
		 * 
		 */
		public String toString()
		{
			String str = new String("[");
			str += this.visibleTimingElementList.toString();
			str += new String("]");

			return str;
		}
		
	}



}
