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

import java.awt.Cursor;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.JPopupMenu;

import jp.riken.brain.ni.samuraigraph.base.SGAxis;
import jp.riken.brain.ni.samuraigraph.base.SGData;
import jp.riken.brain.ni.samuraigraph.base.SGDrawingElement;
import jp.riken.brain.ni.samuraigraph.base.SGFigure;
import jp.riken.brain.ni.samuraigraph.base.SGICopiable;
import jp.riken.brain.ni.samuraigraph.base.SGIFigureElement;
import jp.riken.brain.ni.samuraigraph.base.SGIMovable;
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.SGTuple2d;
import jp.riken.brain.ni.samuraigraph.base.SGTuple2f;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityNumber;

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

/**
 *
 */

public abstract class SGFigureElement extends JPanel
	implements SGIFigureElement, SGIFigureElementConstants
{

	/**
	 * X-coordinate of the rectangle of the graph area.
	 */
	protected float mGraphRectX;

	
	/**
	 * Y-coordinate of the rectangle of the graph area.
	 */
	protected float mGraphRectY;

	
	/**
	 * Width of the rectangle of the graph area.
	 */
	protected float mGraphRectWidth;

	
	/**
	 * Height of the rectangle of the graph area.
	 */
	protected float mGraphRectHeight;


	/**
	 * Magnification.
	 */
	protected float mMagnification = 1.0f;


	/**
	 * 
	 */
	protected Rectangle2D mViewBounds = null;


	/**
	 * List of the SGData objects.
	 */
	protected ArrayList mDataList = new ArrayList();


	/**
	 * List of action listeners.
	 */
	protected final ArrayList mActionListenerList = new ArrayList();


	/**
	 * 
	 */
	private Cursor mCursor = null;



	/**
	 * Owner of property dialogs.
	 */
	protected Frame mDialogOwner = null;


	/**
	 * Default constructor.
	 */
	public SGFigureElement()
	{
		super();
	}


	/**
	 * Add a new data.
	 * @param data a new data.
	 * @return true:secceeded, false:failed
	 */
/*	public boolean addData( final SGData data )
	{
		mDataList.add( data );
		return true;
	}
*/

	/**
	 * 
	 * @param data
	 * @param name
	 * @return
	 */
	public boolean addData( final SGData data, final String name )
	{
		this.mDataList.add(data);
		return true;
	}



	/**
	 * 
	 */
	public boolean removeData( final SGData data )
	{
		for( int ii=this.mDataList.size()-1; ii>=0; ii-- )
		{
			final SGData data_ = (SGData)this.mDataList.get(ii);
			if( data_.equals(data) )
			{
				this.mDataList.remove(ii);
				return true;
			}
		}
		
		return true;
	}



	/**
	 * 
	 */
	public ArrayList getDataList()
	{
		return this.mDataList;
	}



	/**
	 * 
	 */
	public boolean getMarginAroundGraphRect(
		final SGTuple2f topAndBottom,
		final SGTuple2f leftAndRight )
	{

		if( topAndBottom==null || leftAndRight==null )
		{
			return false;
		}

		topAndBottom.clear();
		leftAndRight.clear();

		return true;
	}



	/**
	 * 
	 */
	public boolean setGraphRect(
		final float x,
		final float y,
		final float width,
		final float height )
	{
		this.setGraphRectLocation(x,y);
		this.setGraphRectSize(width,height);
		return true;
	}


	/**
	 * 
	 */
	public boolean setGraphRect( Rectangle2D rect )
	{
		return this.setGraphRect(
			(float)rect.getX(),
			(float)rect.getY(),
			(float)rect.getWidth(),
			(float)rect.getHeight()
		);
	}



	/**
	 * 
	 */
	public boolean setGraphRectLocation( final float x, final float y )
	{
		this.mGraphRectX = x;
		this.mGraphRectY = y;
		return true;
	}


	/**
	 * 
	 */
	public boolean setGraphRectSize( final float width, final float height )
	{
		if( width<0.0 || height<0.0 )
		{
			throw new IllegalArgumentException("width<0.0 || height<0.0");
		}
		this.mGraphRectWidth = width;
		this.mGraphRectHeight = height;
		return true;
	}



	/**
	 * 
	 */
	public Rectangle2D getGraphRect()
	{
		final Rectangle2D rect = new Rectangle2D.Float(
			this.mGraphRectX,
			this.mGraphRectY,
			this.mGraphRectWidth,
			this.mGraphRectHeight );
		
		return rect;
	}



	/**
	 * 
	 */
	public boolean isInsideGraphArea( final SGTuple2f pos )
	{
		final float x = pos.x;
		final float y = pos.y;


		final float gx = mGraphRectX;
		final float gy = mGraphRectY;
		final float gw = mGraphRectWidth;
		final float gh = mGraphRectHeight;


		if( x < gx || x > gx + gw )
		{
			return false;
		}

		if( y < gy || y > gy + gh )
		{
			return false;
		}

		return true;		
	}



	/**
	 * 
	 */
	public boolean isInsideGraphArea( final Point2D pos )
	{
		return this.isInsideGraphArea( (int)pos.getX(), (int)pos.getY() );
	}



	/**
	 * 
	 */
	public boolean isInsideGraphArea( final int x, final int y )
	{
		final SGTuple2f pos = new SGTuple2f( x, y );
		return this.isInsideGraphArea( pos );
	}



	/**
	 * 
	 */
	public SGTuple2f getISize()
	{
		final SGTuple2f size = new SGTuple2f();
		size.x = this.getWidth();
		size.y = this.getHeight();
		return size;
	}


	/**
	 * 
	 */
	public SGTuple2f getILocation()
	{
		final SGTuple2f pos = new SGTuple2f();
		pos.x = this.getX();
		pos.y = this.getY();
		return pos;
	}


	/**
	 * 
	 */
	public boolean setISize( final SGTuple2f size )
	{
		this.setSize( (int)size.x, (int)size.y );

		return true;
	}


	/**
	 * 
	 */
	public boolean setILocation( final SGTuple2f pos )
	{
		this.setLocation( (int)pos.x, (int)pos.y );

		return true;
	}



	/**
	 * 
	 */
	public boolean setDialogOwner( final Frame frame )
	{
		this.mDialogOwner = frame;
		return true;
	}



	/**
	 * 
	 */
	public float getMagnification()
	{
		return this.mMagnification;
	}


	/**
	 * 
	 */
	public boolean setMagnification( final float mag )
	{
		this.mMagnification = mag;
		return true;
	}


	/**
	 * 
	 */
	public Cursor getFigureElementCursor()
	{
		return this.mCursor;
	}


	/**
	 * 
	 * @param type
	 */
	protected void setMouseCursor( int type )
	{
		this.mCursor = Cursor.getPredefinedCursor(type);
		this.notifyChangeCursor();
	}


	/**
	 * 
	 * @param cur
	 */
	protected void setMouseCursor( Cursor cur )
	{
		this.mCursor = cur;
		this.notifyChangeCursor();
	}

	
	/**
	 *
	 */
	public void notifyChangeCursor()
	{
		this.notifyToListener( SGFigure.NOTIFY_CHANGE_CURSOR );
	}


	/**
	 * Zoom in/out this component.<BR>
	 */
	public boolean zoom( final float ratio )
	{
		this.setMagnification(ratio);
		return true;
	}



	/**
	 * Synchronize this component to the other component. <BR>
	 * @param element the SGFigureElement object whose property has changed.
	 * @return true:succeeded, false:failed
	 */
	public abstract boolean synchronize( SGIFigureElement element );


	/**
	 * 
	 */
	public abstract boolean synchronizeArgument( SGIFigureElement element );



	/**
	 * 
	 */
	public boolean setViewBounds( final Rectangle2D rect )
	{
		this.mViewBounds = rect;
		return true;
	}


	/**
	 * 
	 * @return
	 */
	public Rectangle2D getViewBounds()
	{
		return this.mViewBounds;
	}



	/**
	 * 
	 * @param el
	 * @param data
	 * @return
	 */
	public boolean createDataObject( Element el, SGData data )
	{
		if( el==null || data==null )
		{
			throw new IllegalArgumentException();
		}
		this.mDataList.add(data);		
		return true;
	}
	


	/**
	 * 
	 * @param e
	 */
	public boolean onMouseClicked( MouseEvent e )
	{
		return true;
	}


	/**
	 * 
	 * @param e
	 */
	public boolean onMousePressed( MouseEvent e )
	{
		return true;
	}


	/**
	 * 
	 * @param e
	 */
	public boolean onMouseDragged( MouseEvent e )
	{
		return true;
	}



	/**
	 * 
	 * @param e
	 */
	public boolean onMouseReleased( MouseEvent e )
	{
		return true;
	}



	/**
	 * 
	 * @param el
	 * @param mod
	 * @return
	 */
	protected boolean updateFocusedObjectsList(
		final SGISelectable el, final int mod )
	{
		final ArrayList fList = this.getFocusedObjectsList();

		// Neither CTRL key nor SHIFT key is pressed.
		if( ( ( mod & MouseEvent.CTRL_MASK ) == 0 )
			& ( ( mod & MouseEvent.SHIFT_MASK ) == 0 ) )
		{
			// If the list already contains this object.
			if( fList.contains(el) )
			{
				// There is nothing to do.
			}
			else
			{
				// set all objects unselected
//				this.clearFocusedObjects();
				this.notifyToListener( SGFigure.CLEAR_FOCUSED_OBJECTS );
				
				// set given object selected
				el.setSelected(true);
			}

		}
		// otherwise
		else
		{
			// If the list already contains this object.
			el.setSelected( !el.isSelected() );
		}

		return true;
	}


	/**
	 * Update the focused object with a mouse event.
	 * @param el - a selectable object
	 * @param e - a mouse event
	 * @return
	 */
	protected boolean updateFocusedObjectsList(
		final SGISelectable el, final MouseEvent e )
	{
		final int mod = e.getModifiers();
		return this.updateFocusedObjectsList( el, mod );
	}
	
	
	/**
	 * 
	 * @param list
	 * @return
	 */
	public abstract boolean getFocusedObjectsList( ArrayList list );
	


	/**
	 * 
	 */
	public void translateSelectedObjects( final int dx, final int dy )
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			Object obj = list.get(ii);
			if( obj instanceof SGIMovable )
			{
				SGIMovable m = (SGIMovable)obj;
				m.translate(dx,dy);
			}
		}
	}

	
	
	/**
	 * 
	 * @return
	 */
	public boolean clearFocusedObjects()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGISelectable s = (SGISelectable)list.get(ii);
			s.setSelected(false);
		}
		return true;
	}




	/**
	 * 
	 */
	public void addActionListener( final ActionListener listener )
	{
		for( int ii=0; ii<mActionListenerList.size(); ii++ )
		{
			final ActionListener el = (ActionListener)mActionListenerList.get(ii);
			if( el.equals(listener) )
			{
				return;
			}
		}
		mActionListenerList.add(listener);
	}


	/**
	 * 
	 */
	public void removeActionListener( ActionListener listener )
	{
		for( int ii=mActionListenerList.size()-1; ii>=0; ii-- )
		{
			final ActionListener el = (ActionListener)mActionListenerList.get(ii);
			if( el.equals(listener) )
			{
				mActionListenerList.remove(listener);
			}
		}
	}


	/**
	 *
	 */
	public void notifyChange()
	{
		this.notifyToListener( SGFigure.NOTIFY_CHANGE );
	}



	/**
	 * 
	 * @return
	 */
	public boolean setPropertyOfSelectedData()
	{
		this.notifyToListener( SGFigure.SET_PROPERTY_OF_SELECTED_DATA );
		return true;
	}


	/**
	 * 
	 * @return
	 */
/*	public boolean focusedDataChanged()
	{
		this.notifyToListener( SGFigure.FOCUSED_OBJECTS_CHANGED );
		return true;
	}
*/

	/**
	 * 
	 */
	public void notifyToListener( final String command )
	{
		for( int ii=0; ii<mActionListenerList.size(); ii++ )
		{
			final ActionListener el = (ActionListener)mActionListenerList.get(ii);
			el.actionPerformed( this.getActionEvent( command ) );
		}
	}


	/**
	 * 
	 */
	private ActionEvent getActionEvent( final String command )
	{
		return new ActionEvent( this, 0, command );
	}



	/**
	 * 
	 */
	public float getGraphRectX()
	{
		return mGraphRectX;
	}


	/**
	 * 
	 */
	public float getGraphRectY()
	{
		return mGraphRectY;
	}


	/**
	 * 
	 */
	public float getGraphRectWidth()
	{
		return mGraphRectWidth;
	}


	/**
	 * 
	 */
	public float getGraphRectHeight()
	{
		return mGraphRectHeight;
	}



	/**
	 * 値に対応するグラフ内での座標値を計算
	 * flagはtrueなら水平方向、falseなら垂直方向
	 */
	protected float calcLocation(
		final double value, final SGAxis axis, final boolean flag )
	{

		final SGTuple2d range = axis.getRange();
		final double min = range.x;
		final double max = range.y;

		final int type = axis.getScaleType();


		// グラフ領域内でのデータ点の比率を計算
		float ratio = 0.0f;
		if( type == SGAxis.LINEAR_TYPE )
		{
			ratio = (float)( (value-min)/(max-min) );
		}
		else if( type == SGAxis.LOG_TYPE )
		{
			final double logMin = Math.log(min);
			final double logMax = Math.log(max);
			final double logValue = Math.log(value);
			ratio = (float)( ( logValue - logMin)/( logMax - logMin ) );
		}


		// グラフ全体における位置を計算
		float pos = 0.0f;
		if( flag )
		{
			pos = ( mGraphRectX + ratio*mGraphRectWidth );
		}
		else
		{
			pos = ( mGraphRectY + (1.0f-ratio)*mGraphRectHeight );
		}

		return pos;

	}



	/**
	 * 
	 */
	protected double calcValue(
		final float pos, final SGAxis axis, final boolean flag )
	{

		final SGTuple2d range = axis.getRange();
		final double min = range.x;
		final double max = range.y;

		final int type = axis.getScaleType();

		// グラフ領域内での点の比の値を計算
		float ratio;
		if( flag )
		{
			ratio = ( pos - this.mGraphRectX )/this.mGraphRectWidth;
		}
		else
		{
			ratio = 1.0f - ( pos - this.mGraphRectY )/this.mGraphRectHeight;
		}

		// 値を計算
		double value = 0.0;
		if( type == SGAxis.LINEAR_TYPE )
		{
			value = min + ratio*(max-min);
		}
		else if( type == SGAxis.LOG_TYPE )
		{
			final double logMin = Math.log(min);
			final double logMax = Math.log(max);
			value = Math.exp( logMin + ratio*(logMax-logMin) );
		}

		return value;
	}



	/**
	 * 
	 * @return
	 */
	protected boolean showPopupMenu( JPopupMenu menu, final int x, final int y )
	{
		menu.show( this, x, y );
		return true;
	}



	/**
	 * 
	 */
	public abstract SGProperties getProperties();



	/**
	 * 
	 */
	public abstract boolean setProperties( final SGProperties p );



	/**
	 * 
	 * @return
	 */
	public boolean addPropertiesHistory( SGProperties p )
	{
		ArrayList list = new ArrayList();
		for( int ii=0; ii<this.mFigureElementStateCounter; ii++ )
		{
			list.add( this.mPropertyHistoryList.get(ii) );
		}
		list.add(p);

		this.mPropertyHistoryList = list;

		return true;
	}


	/**
	 * 
	 */
	public boolean initPropertiesHistory()
	{
		this.addPropertiesHistory( this.getProperties() );
		return true;
	}


	/**
	 * 状態カウンタ変数。
	 * SGIFigureElementオブジェクトと、その下の階層にあるオブジェクトについてカウントする。
	 */
	protected int mFigureElementStateCounter = 0;



	/**
	 * SGIFigureElementオブジェクトのプロパティの履歴
	 */
	protected ArrayList mPropertyHistoryList = new ArrayList();




	/**
	 * 現在、状態が履歴のどの位置にあるのかを示すカウンタ
	 * 自身と、その下の階層にあるオブジェクトの状態変更において変更される
	 */
	protected int mCurrentStateCounter = 0;



	/**
	 * アンドゥ対象オブジェクトの履歴リスト
	 */
	protected ArrayList mUndoableObjectHistoryList = new ArrayList();

	
	
	
	public boolean updateHistory()
	{
		if( this.isChanged() )
		{
			this.updateThisObjectHistory();
			this.updateObjectHistory(this);
			this.setChanged(false);
		}
		
		return true;
	}

	
	/**
	 * 
	 */
	protected boolean updateHistory_( ArrayList list )
	{

		ArrayList changedObjList = new ArrayList();
		if( this.mChangedFlag )
		{
			this.updateThisObjectHistory();
			changedObjList.add(this);
			this.setChanged(false);
		}

		for( int ii=0; ii<list.size(); ii++ )
		{
			SGIUndoable el = (SGIUndoable)list.get(ii);
			if( el.isChanged() )
			{
				el.updateHistory();
				changedObjList.add( el );
			}
		}

		if( changedObjList.size()!=0 )
		{
			this.updateObjectHistory( changedObjList );
		}

		return true;

	}


	
	/**
	 * 
	 * @return
	 */
	public boolean updateThisObjectHistory()
	{


		// フィギュアのプロパティ履歴を更新
		this.mFigureElementStateCounter++;


		this.addPropertiesHistory( this.getProperties() );

		return true;
	}



	/**
	 * オブジェクトの履歴を更新する
	 * @return
	 */
	public boolean updateObjectHistory( final SGIUndoable obj )
	{
		ArrayList objList = new ArrayList();
		objList.add(obj);
		boolean flag = this.updateObjectHistory(objList);
		if( !flag )
		{
			return false;
		}

		return true;
	}



	/**
	 * オブジェクトの履歴を更新する
	 */
	public boolean updateObjectHistory( final ArrayList objList )
	{
		ArrayList list = new ArrayList();
		for( int ii=0; ii<this.mCurrentStateCounter; ii++ )
		{
			Object obj = this.mUndoableObjectHistoryList.get(ii);
			list.add(obj);
		}
		list.add( new ArrayList(objList) );

		this.mUndoableObjectHistoryList = list;
		this.mCurrentStateCounter++;

		return true;
	}



	/**
	 * 
	 */
	public boolean onUndo()
	{
		// 記録されているundo操作対象オブジェクトの取得
		if( this.mCurrentStateCounter == 0 )
		{
			return false;
		}

		ArrayList objList = (ArrayList)this.mUndoableObjectHistoryList.get(
			this.mCurrentStateCounter - 1 );

		for( int ii=0; ii<objList.size(); ii++ )
		{
			SGIUndoable obj = (SGIUndoable)objList.get(ii);
			// アンドゥ操作の実行の依頼
			boolean flag;
			if( obj.equals(this) )
			{
				flag = this.undo();
			}
			else
			{
				flag = obj.onUndo();
			}
		
			if( !flag )
			{
				return false;
			}
		}

		// decrement
		this.mCurrentStateCounter--;

		return true;
	}
	

	
	/**
	 * 
	 */
	public boolean onRedo()
	{

		// 記録されているundo操作対象オブジェクトの取得
		if( this.mCurrentStateCounter == this.mUndoableObjectHistoryList.size() )
		{
			return false;
		}


		ArrayList objList = (ArrayList)this.mUndoableObjectHistoryList.get( this.mCurrentStateCounter );
		for( int ii=0; ii<objList.size(); ii++ )
		{
			SGIUndoable obj = (SGIUndoable)objList.get(ii);
			// リドゥ操作の実行の依頼
			boolean flag;
			if( obj.equals(this) )
			{
				flag = this.redo();
			}
			else
			{
				flag = obj.onRedo();
			}

			if( !flag )
			{
				return false;
			}
		}


		// decrement
		this.mCurrentStateCounter++;

		return true;

	}



	/**
	 * 
	 */
	public boolean undo()
	{
		this.mFigureElementStateCounter--;

		SGProperties p
			= (SGProperties)this.mPropertyHistoryList.get(this.mFigureElementStateCounter);
		
		if( this.setProperties(p) == false ) return false;

		return true;
	}



	/**
	 * 
	 */
	public boolean redo()
	{
		this.mFigureElementStateCounter++;

		SGProperties p
			= (SGProperties)this.mPropertyHistoryList.get(this.mFigureElementStateCounter);

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

		return true;
	}



	/**
	 * 
	 *
	 */
	public void notifyToRoot()
	{
		this.notifyToListener( SGFigure.NOTIFY_CHANGE_TO_ROOT );
	}


	/**
	 * 
	 *
	 */
	protected void notifyToRootFromFigureElement()
	{
		this.notifyToRoot();
	}



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


	/**
	 * 
	 */
	protected boolean mChangedFlag = false;

	
	public void setChanged( final boolean b )
	{
		this.mChangedFlag = b;
	}
	
	
	/**
	 * 
	 * @param listAll
	 * @param listVisible
	 * @return
	 */
	protected boolean setVisibleDrawingElement(
		final ArrayList listAll, final ArrayList listVisible )
	{
		ArrayList listInvisible = new ArrayList();
		for( int ii=0; ii<listAll.size(); ii++ )
		{
			SGDrawingElement el
				= (SGDrawingElement)listAll.get(ii);
			final boolean b = listVisible.contains(el);
			el.setVisible(b);
			if(!b)
			{
				listInvisible.add( el );
			}
		}

		listAll.clear();
		for( int ii=0; ii<listVisible.size(); ii++ )
		{
			listAll.add( listVisible.get(ii) );
		}
		for( int ii=0; ii<listInvisible.size(); ii++ )
		{
			listAll.add( listInvisible.get(ii) );
		}

		return true;
	}



	/**
	 * 
	 * @param b
	 */
	protected void setParentChanged( final boolean b )
	{
		this.setChanged(b);
	}



	/**
	 * 
	 * @param obj
	 * @param list
	 * @return
	 */
	protected boolean moveObjectToFront(
		final Object obj, final ArrayList list )
	{
		return this.moveObject( obj, list, list.size()-1 );
	}



	/**
	 * 
	 * @param obj
	 * @param list
	 * @return
	 */
	protected boolean moveObjectToBack(
		final Object obj, final ArrayList list )
	{
		return this.moveObject( obj, list, 0 );
	}



	/**
	 * 
	 * @param obj
	 * @param list
	 * @param index
	 * @return
	 */
	private boolean moveObject(
		final Object obj, final ArrayList list, final int index )
	{

		if( list.contains(obj)==false )
		{
			return false;
		}

		for( int ii=0; ii<list.size(); ii++ )
		{
			if( obj.equals( list.get(ii) ) )
			{
				list.remove(ii);
				break;
			}
		}
		list.add( index, obj );
		
		notifyChange();
		this.setChanged(true);

		return true;
	}



	/**
	 * 
	 * @param el
	 * @param list
	 * @return
	 */
	protected boolean hideObject( final SGDrawingElement el )
	{
		el.setVisible(false);
		notifyChange();
		this.setChanged(true);
		this.notifyToRoot();
		return true;
	}

	
	/**
	 * 
	 *
	 */
	public abstract boolean hideSelectedObject( SGISelectable s );


	/**
	 * 
	 *
	 */
	public boolean hideSelectedObjects()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGISelectable s = (SGISelectable)list.get(ii);
			if( this.hideSelectedObject(s) == false )
			{
				return false;
			}
		}

		if( list.size()!=0 )
		{
			this.setChanged(true);
			notifyChange();
		}

		return true;
	}

	
	/**
	 * 
	 * @return
	 */
	public abstract String getTagName();

	
	/**
	 * 
	 * @param el
	 * @return
	 */
	public abstract boolean writeProperty( final Element el );

	
	/**
	 * 
	 * @param document
	 * @return
	 */
	protected Element createThisElement( final Document document )
	{
		Element el = document.createElement( this.getTagName() );

		// set common properties
		if( this.writeProperty(el) == false )
		{
			return null;
		}

		return el;
	}


	/**
	 * 
	 */
	protected boolean mSymbolsVisibleFlagAroundFocusedObjects = true;

	
	/**
	 *
	 *
	 */
	public void setSymbolsVisibleAroundFocusedObjects( final boolean b )
	{
		this.mSymbolsVisibleFlagAroundFocusedObjects = b;
	}


	/**
	 * 
	 */
	protected boolean mSymbolsVisibleFlagAroundAllObjects = false;


	/**
	 * 
	 * @param b visibility
	 */
	public void setSymbolsVisibleAroundAllObjects( final boolean b )
	{
		this.mSymbolsVisibleFlagAroundAllObjects = b;
	}


	
	/**
	 * 
	 */
	protected double getNumberInRangeOrder( final double value, final SGAxis axis )
	{
		SGTuple2d range = axis.getRange();
		final double num = SGUtilityNumber.getNumberInRangeOrder(
			value, range, AXIS_SCALE_EFFECTIVE_DIGIT );
		return num;
	}

	
	/**
	 * 
	 * @param list
	 * @return
	 */
	protected ArrayList getCopyList( ArrayList list )
	{
		ArrayList cList = new ArrayList();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGICopiable obj = (SGICopiable)list.get(ii);
			cList.add( obj.copy() );
		}
		return cList;
	}



	/**
	 * 
	 * @return
	 */
	public ArrayList getFocusedObjectsList()
	{
		ArrayList list = new ArrayList();
		this.getFocusedObjectsList(list);
		return list;
	}


	/**
	 * Returns the list of focused copiable objects.
	 * @return a list of focused copiable objects
	 */
	protected ArrayList getCopiableFocusedObjectsList()
	{
		ArrayList list = this.getFocusedObjectsList();
		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			Object obj = list.get(ii);
			if( ( obj instanceof SGICopiable ) == false )
			{
				list.remove(ii);
			}
		}
		return list;
	}



	/**
	 * Offset x-value of the location for duplicated objects.
	 */
	public static final int OFFSET_DUPLICATED_OBJECT_X = 10;


	/**
	 * Offset y-value of the location for duplicated objects.
	 */
	public static final int OFFSET_DUPLICATED_OBJECT_Y = 10;



	/**
	 * Create copies of the focused objects.
	 * @return
	 */
	public boolean duplicateFocusedObjects()
	{
		return true;
	}


	/**
	 * Duplicate the focused objects.
	 * @return list of duplicated objects
	 */
	protected ArrayList duplicateObjects()
	{
		// get the list of copiable focused objects
		ArrayList list = this.getCopiableFocusedObjectsList();

		// create copies
		ArrayList cList = this.getCopyList( list );

		// clear all focused objects
		this.clearFocusedObjects();		

		return cList;		
	}



	/**
	 * Returns the list of copied objects.
	 * @return list of copied objects
	 */
	public ArrayList getCopiedObjectsList()
	{
		// get the list of copiable focused objects
		ArrayList list = this.getCopiableFocusedObjectsList();

		// create copies
		return this.getCopyList( list );
	}


	/**
	 * Paste the objects.
	 * @param list of the objects to be pasted
	 * @return true:succeeded, false:failed
	 */
	public boolean paste( ArrayList list )
	{
		return true;
	}



	/**
	 * Cut focused copiable objects.
	 * @return a list of cut objects
	 */
	public ArrayList cutFocusedObjects()
	{
		ArrayList list = this.getCopiedObjectsList();
		this.hideSelectedObjects();
		return list;
	}


	/**
	 * 
	 */
	public SGProperties getDataProperties( SGData data )
	{
		return null;
	}


	/**
	 * 
	 * @param data
	 * @param name
	 * @param p
	 * @return
	 */
	public boolean addData( final SGData data, final String name, final SGProperties p )
	{
		return this.addData(data,name);
	}



	/**
	 * Move the focused objects to front or back.
	 * @param toFront - flag whether to front or back
	 * @return true:succeeded, false:failed
	 */
	public boolean moveFocusedObjects( boolean toFront )
	{
		return true;
	}

}

