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

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import jp.riken.brain.ni.samuraigraph.base.SGUtility;
import jp.riken.brain.ni.samuraigraph.base.SGUtilityText;
import jp.riken.brain.ni.samuraigraph.figure.SGDrawingElementString;

/**
 * An extended class of string element.
 * This drawing element represent a string with superscript and subscript.
 */
public class SGDrawingElementString2DExtended extends SGDrawingElementString2D
{

	/**
	 * The list of string elements of base characters.
	 */
	private final ArrayList mBaseElementList = new ArrayList();


	/**
	 * The list of string elements of subscript.
	 */
	private final ArrayList mSubscriptElementList = new ArrayList();


	/**
	 * The list of string elements of superscript.
	 */
	private final ArrayList mSuperscriptElementList = new ArrayList();


	/**
	 * 
	 */
	public SGDrawingElementString2DExtended()
	{
		super();
		this.createStringElements();
	}


	/**
	 * 
	 */
	public SGDrawingElementString2DExtended( final String str )
	{
		super( str );
		this.createStringElements();
	}


	/**
	 * 
	 */
	public SGDrawingElementString2DExtended( final SGDrawingElementString element )
	{
		super( element );
		this.createStringElements();
	}


	/**
	 *
	 */
	public SGDrawingElementString2DExtended(
		final String str,
		final String fontName,
		final int fontStyle,
		final float fontSize )
	{
		super( str, fontName, fontStyle, fontSize );
		this.createStringElements();
	}
	

	/**
	 *
	 */
	public SGDrawingElementString2DExtended(
		final String str,
		final String fontName,
		final int fontStyle,
		final float fontSize,
		final float x,
		final float y )
	{
		super( str, fontName, fontStyle, fontSize, x, y );
		this.createStringElements();
	}



	/**
	 * Returns whether this string element has superscripts.
	 * @return
	 */
	public boolean containsSuperscripts()
	{
		return ( this.mString.indexOf("^")!=-1 );
	}


	/**
	 * Returns whether this string element has subscripts.
	 * @return
	 */
	public boolean containsSubscripts()
	{
		return ( this.mString.indexOf("_")!=-1 );
	}


	/**
	 * Returns whether this string element has superscripts or subscripts.
	 * @return
	 */
	public boolean containsSubscriptsOrSuperscripts()
	{
		return ( this.containsSubscripts() | this.containsSuperscripts() );
	}



	/**
	 * Returns a list of all string elements without distinction
	 * of base character, subscript or superscript.
	 * @return
	 */
	protected ArrayList getAllStringElement()
	{
		ArrayList list = new ArrayList();

		if( this.mBaseElementList!=null )
		{
			list.addAll( this.mBaseElementList );
		}

		if( this.mSubscriptElementList!=null )
		{
			list.addAll( this.mSubscriptElementList );
		}

		if( this.mSuperscriptElementList!=null )
		{
			list.addAll( this.mSuperscriptElementList );
		}

		for( int ii=list.size()-1; ii>=0; ii-- )
		{
			Object obj = list.get(ii);
			if( obj==null )
			{
				list.remove(ii);
			}
		}

		return list;
	}


	/**
	 * 
	 */
	public boolean setMagnification( final float mag )
	{
		super.setMagnification( mag );

		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString el = (SGDrawingElementString)list.get(ii);
			el.setMagnification( mag );
		}

		this.updateLocation();

		return true;
	}



	/**
	 * 
	 */
	public boolean setColor( final ArrayList colorList )
	{
		super.setColor( colorList );

		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString el = (SGDrawingElementString)list.get(ii);
			el.setColor( colorList );
		}
		return true;
	}


	/**
	 * 
	 */
	public boolean setColor( final Color color )
	{
		super.setColor( color );

		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString el = (SGDrawingElementString)list.get(ii);
			el.setColor( color );
		}
		return true;
	}


	/**
	 * 
	 * @return
	 */
	public boolean addColor( final Color color )
	{
		super.addColor( color );

		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString el = (SGDrawingElementString)list.get(ii);
			el.addColor( color );
		}
		return true;
	}


	/**
	 *
	 */
	public boolean setLocation( final float x, final float y )
	{
		final float dx = x - this.getX();
		final float dy = y - this.getY();

		super.setLocation(x,y);

		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString2D el = (SGDrawingElementString2D)list.get(ii);
			el.translate( dx, dy );
		}

		return true;
	}



	/**
	 *
	 */
	public boolean setFont( final String name, final int style, final float size )
	{
		super.setFont( name, style, size );

		if( this.mBaseElementList==null )
		{
			return true;
		}

		Font f = new Font( name, style, (int)size );
		Font f2 = new Font( name, style, (int)(size/2.0f) );

		ArrayList bList = this.mBaseElementList;
		for( int ii=0; ii<bList.size(); ii++ )
		{
			SGDrawingElementString el = (SGDrawingElementString)bList.get(ii);
			el.setFont(f);
		}

		ArrayList subList = this.mSubscriptElementList;
		for( int ii=0; ii<subList.size(); ii++ )
		{
			SGDrawingElementString el = (SGDrawingElementString)subList.get(ii);
			if( el!=null )
			{
				el.setFont(f2);
			}
		}

		ArrayList superList = this.mSuperscriptElementList;
		for( int ii=0; ii<superList.size(); ii++ )
		{
			SGDrawingElementString el = (SGDrawingElementString)superList.get(ii);
			if( el!=null )
			{
				el.setFont(f2);
			}
		}
		this.updateLocation();

		return true;
	}



	/**
	 *
	 */
	public boolean setString( final String str )
	{
		super.setString( str );
		this.createStringElements();
		this.updateLocation();
		return true;
	}



	/**
	 * 
	 */
	public boolean setAngle( final float angle )
	{
		super.setAngle( angle );

		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString el = (SGDrawingElementString)list.get(ii);
			el.setAngle( angle );
		}
		this.updateLocation();
		return true;
	}



	/**
	 * 
	 * @return
	 */
	private boolean createStringElementsDirectly()
	{
		SGDrawingElementString2D el
			= new SGDrawingElementString2D( this.mString );
		this.mBaseElementList.add(el);
		this.mSubscriptElementList.add(null);
		this.mSuperscriptElementList.add(null);
		this.setPropertiesToAllStringElements();

		return true;
	}



	/**
	 * Create all string elements.
	 * This method is called when the text is set or changed.
	 * @return
	 */
	private boolean createStringElements()
	{

		// clear all lists
		this.mBaseElementList.clear();
		this.mSubscriptElementList.clear();
		this.mSuperscriptElementList.clear();


		// if the string does not contain subscripts nor superscripts,
		// set the string to the base element
		if( this.containsSubscriptsOrSuperscripts() == false )
		{
			return this.createStringElementsDirectly();
		}


		// get information of the input line
		String line = this.getString();
		ArrayList charList = new ArrayList();
		ArrayList superList = new ArrayList();
		ArrayList subList = new ArrayList();
		if( SGUtilityText.getSubscriptAndSuperscriptInfo(line,charList,superList,subList) == false )
		{
			return this.createStringElementsDirectly();
		}
		if( charList.size()!=superList.size() || superList.size()!=subList.size() )
		{
			return this.createStringElementsDirectly();
		}


		//
		for( int ii=0; ii<charList.size(); ii++ )
		{
			// characters
			{
				// create a string
				Character c = (Character)charList.get(ii);
				SGDrawingElementString2D el
					= new SGDrawingElementString2D( c.toString() );
				this.mBaseElementList.add(el);
			}
		

			// subscript characters
			Object sb = subList.get(ii);
			if( sb!=null )
			{
				String str = (String)sb;
				SGDrawingElementString2DExtended el
					= new SGDrawingElementString2DExtended(str);
				this.mSubscriptElementList.add(el);
			}
			else
			{
				this.mSubscriptElementList.add( null );
			}


			// superscript characters
			Object sp = superList.get(ii);
			if( sp!=null )
			{
				String str = (String)sp;
				SGDrawingElementString2DExtended el
					= new SGDrawingElementString2DExtended(str);
				this.mSuperscriptElementList.add( el );
			}
			else
			{
				this.mSuperscriptElementList.add( null );
			}
		
		}


		this.setPropertiesToAllStringElements();

		return true;
	}



	/**
	 * Set properties to the base, superscript and subscript elements.
	 * @return
	 */
	private boolean setPropertiesToAllStringElements()
	{
		final float mag = this.getMagnification();
		final ArrayList colorList = this.getColorList();
		final float size = this.getFontSize();
		final String name = this.getFontName();
		final int style = this.getFontStyle();
		final float angle = this.getAngle();

		ArrayList baseList = this.mBaseElementList;
		ArrayList subList = this.mSubscriptElementList;
		ArrayList superList = this.mSuperscriptElementList;
		for( int ii=0; ii<baseList.size(); ii++ )
		{
			// characters
			{
				SGDrawingElementString2D el
					= (SGDrawingElementString2D)baseList.get(ii);
				el.setMagnification( mag );
				el.setColor( colorList );
				el.setFont( name, style, size );
				el.setAngle( angle );
			}

			// subscript and superscript characters
			SGDrawingElementString2D subscript
				= (SGDrawingElementString2D)subList.get(ii);
			if( subscript!=null )
			{
				this.setSubAndSuper(
					subscript, mag, name, style, size, angle, colorList );
			}

			SGDrawingElementString2D superscript
				= (SGDrawingElementString2D)superList.get(ii);
			if( superscript!=null )
			{
				this.setSubAndSuper(
					superscript, mag, name, style, size, angle, colorList );
			}
		}

		return true;
	}



	private void setSubAndSuper(
		final SGDrawingElementString2D el,
		final float mag, final String name, final int style, final float size,
		final float angle, final ArrayList colorList
	)
	{
		el.setMagnification( mag );
		el.setColor( colorList );
		el.setFont( name, style, size/2.0f );
		el.setAngle( angle );

		if( el instanceof SGDrawingElementString2DExtended )
		{
			SGDrawingElementString2DExtended el_
				= (SGDrawingElementString2DExtended)el;
			el_.setPropertiesToAllStringElements();
		}
	}



	/**
	 * 
	 */
	public Rectangle2D getStringRect()
	{
//System.out.println("<< SGDrawingElementString2D::getStringRect >>");

		//
		float width = 0.0f;
		float height = 0.0f;

		ArrayList baseList = this.mBaseElementList;
		ArrayList subList = this.mSubscriptElementList;
		ArrayList superList = this.mSuperscriptElementList;

		for( int ii=0; ii<baseList.size(); ii++ )
		{
			// characters
			{
				SGDrawingElementString2D el = (SGDrawingElementString2D)baseList.get(ii);

				// get a bounding box
				Rectangle2D stringRect = el.getStringRect();
				width += (float)stringRect.getWidth();
				height = (float)stringRect.getHeight();
			}


			// subscript characters
			Object sb = subList.get(ii);
			float widthSub = 0.0f;
			if( sb!=null )
			{
				SGDrawingElementString2D el = (SGDrawingElementString2D)sb;

				// get a bounding box
				Rectangle2D stringRect = el.getStringRect();

				widthSub += (float)stringRect.getWidth();
			}


			// superscript characters
			Object sp = superList.get(ii);
			float widthSuper = 0.0f;
			if( sp!=null )
			{
				SGDrawingElementString2D el = (SGDrawingElementString2D)sp;

				// get a bounding box
				Rectangle2D stringRect = el.getStringRect();

				widthSuper += (float)stringRect.getWidth();
			}


			if( sp!=null || sb!=null )
			{
				width += Math.max(widthSuper,widthSub);
			}

		}

		// create a rectangle
		Rectangle2D rect = new Rectangle2D.Float(
			0.0f, 0.0f,
			width, height );


		return rect;

	}



	/**
	 * 
	 */
	public Rectangle2D getElementBounds()
	{
		ArrayList rectList = new ArrayList();

		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString2D el	= (SGDrawingElementString2D)list.get(ii);
			rectList.add( el.getElementBounds() );
		}

		// create a rectangle
		Rectangle2D rect = SGUtility.createUnion( rectList );

		return rect;
	}



	/**
	 * 
	 * @return
	 */
	protected boolean updateStringRect()
	{
		super.updateStringRect();

		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString2D el = (SGDrawingElementString2D)list.get(ii);
			el.updateStringRect();
		}

		return true;
	}



	/**
	 * Update the location of the base, superscript and subscript elements.
	 * @return
	 */
	protected boolean updateLocation()
	{

		// angle
		final float angle = this.getAngle();
		final float cv = (float)Math.cos(angle);
		final float sv = (float)Math.sin(angle);


		// location
		final float x = this.getX();
		final float y = this.getY();

		ArrayList baseList = this.mBaseElementList;
		ArrayList subList = this.mSubscriptElementList;
		ArrayList superList = this.mSuperscriptElementList;

		float d = 0.0f;
		for( int ii=0; ii<baseList.size(); ii++ )
		{

			// characters
			{
				SGDrawingElementString2D el
					= (SGDrawingElementString2D)baseList.get(ii);
				Rectangle2D stringRect = el.getStringRect();

				el.setLocation( x + d*cv, y - d*sv );

				// update the location
				d += (float)stringRect.getWidth();
			}


			// superscript characters
			Object sp = superList.get(ii);
			float widthSuper = 0.0f;
			if( sp!=null )
			{
				SGDrawingElementString2D el = (SGDrawingElementString2D)sp;
				Rectangle2D stringRect = el.getStringRect();

				// update the location
				el.setLocation( x + d*cv, y - d*sv );

				// update the location of subscript and superscript
				if( el instanceof SGDrawingElementString2DExtended )
				{
					SGDrawingElementString2DExtended el_
						= (SGDrawingElementString2DExtended)el;
					el_.updateLocation();
				}

				widthSuper += (float)stringRect.getWidth();
			}


			// subscript characters
			Object sb = subList.get(ii);
			float widthSub = 0.0f;
			if( sb!=null )
			{
				SGDrawingElementString2D el = (SGDrawingElementString2D)sb;
				Rectangle2D stringRect = el.getStringRect();

				final float h = (float)stringRect.getHeight();

				// update the location
				el.setLocation( x + d*cv + h*sv, y - d*sv + h*cv );

				// update the location of subscript and superscript
				if( el instanceof SGDrawingElementString2DExtended )
				{
					SGDrawingElementString2DExtended el_
						= (SGDrawingElementString2DExtended)el;
					el_.updateLocation();
				}

				widthSub += (float)stringRect.getWidth();
			}

			if( sp!=null | sb!=null )
			{
				d += Math.max(widthSuper,widthSub);
			}
		}

		return true;
	}



	/**
	 * 
	 */
	public void paintElement( final Graphics2D g2d )
	{
		if( g2d==null )
		{
			return;
		}

		if( this.isVisible() == false )
		{
			return;
		}

		// draw strings
		ArrayList list = this.getAllStringElement();
		for( int ii=0; ii<list.size(); ii++ )
		{
			SGDrawingElementString2D el	= (SGDrawingElementString2D)list.get(ii);
			el.paintElement(g2d);
		}
	}


}

