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

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;

import jp.riken.brain.ni.samuraigraph.figure.SGDrawingElementString;

/**
 * A class of drawing elements with a text. Java2D API is used.
 */
public class SGDrawingElementString2D extends SGDrawingElementString implements
		SGIDrawingElementJava2D
{
	
	/**
	 * Visual bounding box of this string element on zero angle and given
	 * magnification and font properties.
	 */
	private Rectangle2D mStringRect = null;

	/**
	 * Element bounds
	 */
	private Rectangle2D mElementBounds = null;
	
	/**
	 * test metrics
	 */
	private float mAscent = 0.0f;
	private float mDescent = 0.0f;
	private float mLeading = 0.0f;
	private float mStrikethroughOffset = 0.0f;
	private float mAdvance = 0.0f;
	
	/**
	 *  
	 */
	public SGDrawingElementString2D()
	{
		super();
		this.updateMetrics();
	}

	/**
	 *  
	 */
	public SGDrawingElementString2D(final String str)
	{
		super( str );
		this.updateMetrics();
		this.updateElementBounds();
	}

	/**
	 *  
	 */
	public SGDrawingElementString2D(final SGDrawingElementString element)
	{
		super( element );
		this.updateMetrics();
		this.updateElementBounds();
	}

	/**
	 *  
	 */
	public SGDrawingElementString2D(
			final String str,
			final String fontName,
			final int fontStyle,
			final float fontSize,
			final Color color,
			final float mag,
			final float angle )
	{
		super( str, fontName, fontStyle, fontSize, color, mag, angle );
		this.updateMetrics();
		this.updateElementBounds();
	}

	/**
	 *  
	 */
	public void dispose()
	{
		super.dispose();
		this.mStringRect = null;
		this.mElementBounds = null;
	}

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

	/**
	 *  
	 */
	public boolean setMagnification(final float mag)
	{
		super.setMagnification(mag);
		this.updateMetrics();
		this.updateElementBounds();
		return true;
	}
	
	/**
	 *  
	 */
	public boolean setFont(final String name, final int style, final float size)
	{
		super.setFont(name, style, size);
		this.updateMetrics();
		this.updateElementBounds();
		return true;
	}
	
	/**
	 *  
	 */
	public boolean setX( final float x )
	{
		super.setX( x );
		this.updateElementBounds();
		return true;
	}
	
	/**
	 *  
	 */
	public boolean setY( final float y )
	{
		super.setY( y );
		this.updateElementBounds();
		return true;
	}
	
	/**
	 * Set the angle of this string.
	 */
	public boolean setAngle( final float angle )
	{
		super.setAngle( angle );
		this.updateElementBounds();		
		return true;
	}

	/**
	 *  
	 */
	public final boolean contains(final int x, final int y)
	{
		Shape shape = this.getElementBounds();
		return shape.contains(x, y);
	}

	/**
	 * Update the attribute of bounding box.
	 * 
	 * @return
	 */
	private void updateMetrics()
	{
		final Font font = this.getFont();
		final String str = this.getString();
		
		if ( str.length() == 0 ) {
			this.mStringRect = new Rectangle2D.Float(); // (0,0), (0,0)
			this.mAscent = 0.0f;
			this.mDescent = 0.0f;
			this.mLeading = 0.0f;
			this.mStrikethroughOffset = 0.0f;
			this.mAdvance = 0.0f;
			return;
		}

		// create font recder context
		final FontRenderContext frc = new FontRenderContext( null, false, false );

		// create text layout
		final TextLayout layout = new TextLayout( str, font, frc );

		// get a visual bounds rectangle from Font object
		this.mStringRect = layout.getBounds();
		if( this.mStringRect.isEmpty() )
			this.mStringRect.setRect(0,0,0,0);
		
		// get a line metrics from the Font object
		final LineMetrics metrics = font.getLineMetrics( str, frc );

//		this.mAscent = metrics.getAscent();
//		this.mDescent = metrics.getDescent();
		this.mAscent = (float)( -this.mStringRect.getY() );
		this.mDescent = (float)( this.mStringRect.getHeight() + this.mStringRect.getY() );
		this.mLeading = metrics.getLeading();
		this.mStrikethroughOffset = metrics.getStrikethroughOffset();
		this.mAdvance = layout.getAdvance();
		
	}
	
	/**
	 * Returns the bounding box with the given magnification, location, font and
	 * angle properties.
	 */
	private void updateElementBounds()
	{
		final String str = this.getString();
		if ( str.length() == 0 ) {
			this.mElementBounds = new Rectangle2D.Float(); // (0,0), (0,0)
			return;
		}
		
		// shift position
		final float angle = this.getAngle();
		final float cv = (float)Math.cos(angle);
		final float sv = (float)Math.sin(angle);
		final float dy = (float)(this.mStringRect.getHeight() + this.mStringRect.getY());
		final float dx = 0.0f;
		final float pos_x = super.getX() + dx * cv + dy * sv;
		final float pos_y = super.getY() - dx * sv + dy * cv;
		
		// create affine trans form
		final AffineTransform af = new AffineTransform();
		af.translate( pos_x, pos_y );
		// rotate position
		af.rotate( -angle );
		
		// get element bounds
		final Rectangle2D sRect = (Rectangle2D)this.mStringRect.clone();
		sRect.setRect(0, 0, this.mAdvance, this.mStringRect.getHeight());
		final Shape sh = af.createTransformedShape( sRect );
		this.mElementBounds = sh.getBounds2D();
	}

	/**
	 * Returns the bounding box with the given magnification, location, font and
	 * angle properties.
	 */
	public Rectangle2D getElementBounds()
	{
		return (Rectangle2D)this.mElementBounds.clone();
	}

	/**
	 * Returns the bounding box with the given magnification and font
	 * properties. The angle does not affect the returned value. The x- and
	 * y-coordinate of this rectangle always equals to zero.
	 */
	public Rectangle2D getStringRect()
	{
		return (Rectangle2D)this.mStringRect.clone();
	}

	/**
	 *  get ascent of text line
	 */
	protected float getAscent()
	{
		return this.mAscent;
	}
	
	/**
	 *  get descent of text line
	 */
	protected float getDescent()
	{
		return this.mDescent;
	}
	
	/**
	 * get leading of text line
	 *  
	 */
	protected float getLeading()
	{
		return this.mLeading;
	}

	/**
	 * get strike through offset of text line
	 * @return
	 */
	protected float getStrikethroughOffset()
	{
		return this.mStrikethroughOffset;
	}

	/**
	 *  get advance of text line
	 */
	protected float getAdvance()
	{
		return this.mAdvance;
	}
	

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

		final String str = this.getString();

		g2d.setPaint(this.getColor(0));

		// set the font
		g2d.setFont(this.getFont());

		// create an affine transformation matrix
		final AffineTransform af = new AffineTransform();
		// shift position
		af.translate( super.getX(), super.getY() );
		// rotate position
		af.rotate( -this.getAngle() );

		// transform
		final AffineTransform saveAT = g2d.getTransform();
		g2d.transform(af);

		final boolean useAntiAliasing = true;

		if ( useAntiAliasing ) {
			// enable anti aliasing for text
			g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		}
		// draw string
		g2d.drawString(str, 0, (float)this.mStringRect.getHeight());

		if ( useAntiAliasing ) {
			// disable anti aliasing for text
			g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
		}

		g2d.setTransform(saveAT);
	}

	/**
	 *  
	 */
	public void paint(final Graphics2D g2d, final Rectangle2D clipRect)
	{
		this.paintElement(g2d);
	}

}
