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

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.io.*;
import java.net.*;
import javax.swing.*;
import jp.riken.brain.ni.samuraigraph.base.*;
import jp.riken.brain.ni.samuraigraph.data.*;

/**
 *
 */

public abstract class SGFigureElement extends JPanel implements SGIFigureElement
{

	/**
	 * X座標値
	 */
	protected float mGraphAreaX;

	/**
	 * Y座標値
	 */
	protected float mGraphAreaY;

	/**
	 * 幅
	 */
	protected float mGraphAreaWidth;

	/**
	 * 高さ
	 */
	protected float mGraphAreaHeight;


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


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


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


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




	/**
	 * 
	 */
	protected Cursor mCursor = null;



	/**
	 * 
	 */
	public final static String FROM_FIGURE_ELEMENT = "from the SGIFigureElement object";



	/**
	 * 
	 */
	protected float mRulerUnit = SGConstants.CM_POINT_RATIO;



	/**
	 * 
	 */
	protected Frame mDialogOwner = null;


	/**
	 * 
	 */
	protected boolean mComponentBoundsVisibleFlag = false;



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



	/**
	 * 
	 */
	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 boolean removeAllElements()
	{
		this.mDataList.clear();
		return true;
	}



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



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

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

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

		return true;
	}



	/**
	 * 
	 */
	public void setComponentBoundsVisibleFlag( boolean b )
	{
		this.mComponentBoundsVisibleFlag = b;
	}



	/**
	 * 
	 */
	public boolean getComponentBoundsVisibleFlag()
	{
		return this.mComponentBoundsVisibleFlag;
	}



	protected Rectangle2D mTempGraphAreaRect = new Rectangle2D.Float();


	/**
	 * 
	 */
	public boolean setGraphAreaRect(
		final float x, final float y, final float width, final float height )
	{
		this.setGraphAreaLocation(x,y);
		this.setGraphAreaSize(width,height);
		return true;
	}




	/**
	 * 
	 */
	public boolean setGraphAreaLocation( final float x, final float y )
	{
		this.mGraphAreaX = x;
		this.mGraphAreaY = y;
		return true;
	}


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



	/**
	 * 
	 */
	public Rectangle2D getGraphAreaRect()
	{
		final Rectangle2D rect = new Rectangle2D.Float(
			this.mGraphAreaX,
			this.mGraphAreaY,
			this.mGraphAreaWidth,
			this.mGraphAreaHeight );
		
		return rect;
	}



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


		final float gx = mGraphAreaX;
		final float gy = mGraphAreaY;
		final float gw = mGraphAreaWidth;
		final float gh = mGraphAreaHeight;


		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 = (float)this.getWidth();
		size.y = (float)this.getHeight();

		return size;
	}


	/**
	 * 
	 */
	public SGTuple2f getILocation()
	{
		final SGTuple2f pos = new SGTuple2f();
		pos.x = (float)this.getX();
		pos.y = (float)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;
	}



	/**
	 * 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 )
	{
		mViewBounds = rect;
		return true;
	}



	/**
	 * 
	 */
	public boolean createDataObject( final BufferedReader reader, final SGData data ) throws IOException
	{

		if( reader==null || data==null )
		{
			return false;
		}

		this.mDataList.add(data);		

		return true;
	}



	/**
	 * name : name of resource file
	 */
	protected ImageIcon createIcon( final String name )
	{
		if( name==null )
		{
			return null;
		}

		Class inClass = getClass();
		URL url = inClass.getResource(name);

		ImageIcon icon;
		if( url!=null )
		{
			icon = new ImageIcon(url);
		}
		else
		{
			icon = new ImageIcon(name);
		}


		return icon;
	}
	



	/**
	 * 
	 * @param e
	 */
	public abstract boolean onMouseClicked( MouseEvent e );


	/**
	 * 
	 * @param e
	 */
	public abstract boolean onMousePressed( MouseEvent e );


	/**
	 * 
	 * @param e
	 */
	public abstract boolean onMouseDragged( MouseEvent e );


	/**
	 * 
	 * @param e
	 */
	public abstract boolean onMouseReleased( MouseEvent e );




	/**
	 * 
	 */
	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 );
	}


	/**
	 * 
	 */
	public boolean updateParentHistory()
	{
		this.notifyToListener( SGFigure.UPDATE_HISTORY );
		return true;
	}


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


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



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


	/**
	 * 
	 */
	private ActionEvent getActionEvent( final int id )
	{
		return new ActionEvent( this, id, FROM_FIGURE_ELEMENT );
	}



	/**
	 * 
	 */
	public float getGraphAreaX()
	{
		return mGraphAreaX;
	}


	/**
	 * 
	 */
	public float getGraphAreaY()
	{
		return mGraphAreaY;
	}


	/**
	 * 
	 */
	public float getGraphAreaWidth()
	{
		return mGraphAreaWidth;
	}


	/**
	 * 
	 */
	public float getGraphAreaHeight()
	{
		return mGraphAreaHeight;
	}



	/**
	 * 値に対応するグラフ内での座標値を計算
	 * 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 = ( mGraphAreaX + ratio*mGraphAreaWidth );
		}
		else
		{
			pos = ( mGraphAreaY + (1.0f-ratio)*mGraphAreaHeight );
		}

		return pos;

	}



	/**
	 * 
	 */
	protected double calcValue(
		final int 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();

		// グラフ領域内での点の比の値を計算
		double ratio = 0.0;
		if( flag )
		{
			ratio = ( (double)pos - this.mGraphAreaX )/this.mGraphAreaWidth;
		}
		else
		{
			ratio = 1.0 - ( (double)pos - this.mGraphAreaY )/this.mGraphAreaHeight;
		}

		// 値を計算
		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) );

//System.out.println("ratio="+ratio);
//System.out.println("logMin="+logMin);
//System.out.println("logMax="+logMax);
//System.out.println("value="+value);
//System.out.println();
		}


		return value;
	}



	/**
	 * 描画メソッド
	 */
	public void paintComponent(final Graphics g)
	{
		super.paintComponent(g);
		final Graphics2D g2d = (Graphics2D) g;
		this.paintGraphics2D(g2d);
	}



	/**
	 * 
	 * @param g2d
	 * @return
	 */
	public void paintGraphics2D( Graphics2D g2d )
	{
		// フレームを描画する
		if( mComponentBoundsVisibleFlag )
		{
			this.drawComponentBounds(g2d);
		}
	}



	/**
	 * 
	 */
	protected boolean drawComponentBounds( final Graphics2D g2d )
	{
		g2d.setPaint(Color.BLACK);
		g2d.setStroke( new BasicStroke(1.0f) );
		Rectangle rect = this.getBounds();
		rect.setBounds(
			(int)rect.getX(),
			(int)rect.getY(),
			(int)rect.getWidth() -1,
			(int)rect.getHeight() -1 );
		g2d.draw(rect);

		return true;
	}



	/**
	 * 
	 * @return
	 */
	public boolean clearSelectedElements()
	{
		return true;
	}



	/**
	 * 
	 */
	public boolean isResizable( final double w, final double h )
	{


		return true;

	}



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



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



	/**
	 * 
	 * @return
	 */
	public boolean addPropertiesHistory( SGProperties p )
	{
//System.out.println("<< addPropertiesHistory >>");
//System.out.println(p);
//System.out.println(this.mFigureElementStateCounter);
//System.out.println(this.mPropertyHistoryList);
//System.out.println();

		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()
	{

		//
		this.updateParentHistory();

		//
		this.updateThisObjectHistory();

		return true;
	}



	/**
	 * 
	 * @return
	 */
	public boolean updateThisObjectHistory()
	{
		// オブジェクト履歴を更新
		this.updateObjectHistory(this);


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


		this.addPropertiesHistory( this.getProperties() );

		return true;
	}



	/**
	 * 
	 */
	protected boolean callParentUpdateHistory()
	{
		return this.updateHistory();
	}




	/**
	 * オブジェクトの履歴を更新する
	 * @return
	 */
	public boolean updateObjectHistory( final SGIUndoable obj )
	{
//System.out.println("<< SGFigureElement : updataObjectHistory >>");
//System.out.println(obj);
//System.out.println();

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

//System.out.println(this.mUndoableObjectHistoryList);
//System.out.println(this.mCurrentStateCounter);
//System.out.println();

		return true;
	}



	/**
	 * 
	 */
	public boolean onUndo()
	{
//System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@ undo @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");


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

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

//System.out.println(objList);
//System.out.println();

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



}
