package gnu.java.awt.peer.wce.font;

import gnu.java.awt.peer.wce.WCEGraphics2D;
import java.awt.Font;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphMetrics;
import java.awt.font.GlyphVector;
import java.awt.font.GlyphJustificationInfo;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Float;
import java.awt.geom.Rectangle2D;

public class FreeTypeGlyphVector extends GlyphVector
{
    private final FreeTypeFace face;
    private final Font font;
    private final FontRenderContext fontRenderContext;
    private final int nativePointer;

    public FreeTypeGlyphVector(FreeTypeFace face, Font font, FontRenderContext frc, char[] text, int start, int limit, int flags) throws FreeTypeException
    {
	this.face = face;
	this.face.setCharSize(font.getSize2D());
	this.nativePointer = createNative(face.getNativePointer(), frc, text, start, limit, flags);
	this.font = font;
	this.fontRenderContext = frc;
    }

    private native int createNative(int nativeFacePointer, FontRenderContext frc, char[] text, int start, int limit, int flags);

    public Font getFont()
    {
	return this.font;
    }

    public FontRenderContext getFontRenderContext()
    {
	return this.fontRenderContext;
    }

    public void performDefaultLayout()
    {
	try
	{
	    this.face.setCharSize(this.font.getSize2D());
	    performNativeDefaultLayout(this.nativePointer, this.face.getNativePointer());
	}
	catch (FreeTypeException fte)
	{
	    fte.printStackTrace();
	}
    }

    private native void performNativeDefaultLayout(int nativePointer, int nativeFacePointer);

    public int getNumGlyphs()
    {
	return getNativeNumGlyphs(this.nativePointer);
    }

    public native int getNativeNumGlyphs(int nativePointer);

    public int getGlyphCode(int glyphIndex)
    {
	return getNativeGlyphCode(this.nativePointer, glyphIndex);
    }

    private native int getNativeGlyphCode(int nativePointer, int glyphIndex);

    public int[] getGlyphCodes(int beginGlyphIndex,
                                    int numEntries,
                                    int[] codeReturn)
    {
	checkGlyphIndex(beginGlyphIndex);
	checkGlyphIndex(beginGlyphIndex + numEntries);

	final int numGlyphs = getNumGlyphs();
	int[] result = codeReturn;
	if (codeReturn == null || codeReturn.length < numGlyphs)
	{
	    codeReturn = new int[numGlyphs];
	}
	for (int i = beginGlyphIndex; i < beginGlyphIndex + numGlyphs; ++i)
	{
	    result[i] = getGlyphCode(i);
	}
	return result;
    }

    public Rectangle2D getLogicalBounds()
    {
	return getNativeLogicalBounds(this.nativePointer);
    }

    private native Rectangle2D getNativeLogicalBounds(int nativePointer);

    public Rectangle2D getVisualBounds()
    {
	return getNativeVisualBounds(this.nativePointer);
    }

    private native Rectangle2D getNativeVisualBounds(int nativePointer);

    public Shape getOutline()
    {
	GeneralPath gp = new GeneralPath();
	gp.append(new FreeTypeGlyphPathIterator(this), false);
	return gp;
    }

    public Shape getOutline(float x, float y)
    {
	GeneralPath shape = (GeneralPath) getOutline();
	AffineTransform transform = new AffineTransform();
	transform.translate(x, y);
	shape.transform(transform);

	return shape;
    }

    /**
     * 指定されたインデックスが正しいかをチェックする
     * 正しい値は 0 - getNumGlyphs()である。
     * (getNumGlyphs() は「最後のグリフの次の位置」を表す）
     */
    private void checkGlyphIndex(int glyphIndex)
    {
	if (glyphIndex < 0 || glyphIndex > getNumGlyphs())
	{
	    throw new IndexOutOfBoundsException("glyphIndex=" + glyphIndex);
	}
    }

    public Shape getGlyphOutline(int glyphIndex)
    {
	checkGlyphIndex(glyphIndex);
	GeneralPath gp = new GeneralPath();
	gp.append(new FreeTypeGlyphPathIterator(this, glyphIndex), false);
	return gp;
    }

    public Point2D getGlyphPosition(int glyphIndex)
    {
	checkGlyphIndex(glyphIndex);
	float[] result = new float[2];
	getNativeGlyphPosition(this.nativePointer, glyphIndex, result);
	return new Point2D.Float(result[0], result[1]);
    }

    private native void getNativeGlyphPosition(int nativePointer, int glyphIndex, float[] result);

    public void setGlyphPosition(int glyphIndex,
                                 Point2D newPos)
    {
	checkGlyphIndex(glyphIndex);
	setNativeGlyphPosition(this.nativePointer, glyphIndex, (float)newPos.getX(), (float)newPos.getY());
    }

    private native void setNativeGlyphPosition(int nativePointer, int glyphIndex, float x, float y);

    public AffineTransform getGlyphTransform(int glyphIndex)
    {
	checkGlyphIndex(glyphIndex);
	throw new UnsupportedOperationException("Not implemented");
    }

    public void setGlyphTransform(int glyphIndex,
                                  AffineTransform newTX)
    {
	checkGlyphIndex(glyphIndex);
	throw new UnsupportedOperationException("Not implemented");
    }

    public float[] getGlyphPositions(int beginGlyphIndex,
                                          int numEntries,
                                          float[] positionReturn)
    {
	checkGlyphIndex(beginGlyphIndex);
	checkGlyphIndex(beginGlyphIndex + numEntries);
	float[] result = positionReturn;
	if (result == null)
	{
	    result = new float[numEntries*2];
	}

	float[] tmp = new float[2];
	for (int i = 0; i < numEntries; ++i)
	{
	    int glyphIndex = beginGlyphIndex + i;
	    getNativeGlyphPosition(this.nativePointer, glyphIndex, tmp);
	    result[i * 2] = tmp[0];
	    result[i * 2 + 1] = tmp[1];
	}
	return result;
    }

    public Shape getGlyphLogicalBounds(int glyphIndex)
    {
	if (glyphIndex < 0 || glyphIndex >= getNumGlyphs())
	{
	    throw new IndexOutOfBoundsException("glyphIndex=" + glyphIndex);
	}
	return getNativeGlyphLogicalBounds(this.nativePointer, glyphIndex);
    }

    private native Shape getNativeGlyphLogicalBounds(int nativePointer, int glyphIndex);

    public Shape getGlyphVisualBounds(int glyphIndex)
    {
	if (glyphIndex < 0 || glyphIndex >= getNumGlyphs())
	{
	    throw new IndexOutOfBoundsException("glyphIndex=" + glyphIndex);
	}
	return getNativeGlyphVisualBounds(this.nativePointer, glyphIndex);
    }

    private native Shape getNativeGlyphVisualBounds(int nativePointer, int glyphIndex);

    public GlyphMetrics getGlyphMetrics(int glyphIndex)
    {
	Shape s = getGlyphLogicalBounds(glyphIndex);
	Rectangle2D bounds = s.getBounds2D();
	return new GlyphMetrics((float)bounds.getWidth(), bounds, GlyphMetrics.STANDARD);
    }

    public GlyphJustificationInfo getGlyphJustificationInfo(int glyphIndex)
    {
	throw new UnsupportedOperationException("Not implemented");
    }

    public boolean equals(GlyphVector set)
    {
	if (set instanceof FreeTypeGlyphVector)
	{
	    return equalsNative(this.nativePointer, ((FreeTypeGlyphVector)set).nativePointer);
	}
	return false;
    }

    private native boolean equalsNative(int nativePointer, int nativePointer2);

    /**
     * Returns the native object's pointer.
     * 
     * @return the native FreeTypeGlyphVector object's pointer.
     */
    public int getNativePointer()
    {
	return this.nativePointer;
    }

    protected void finalize()
    {
	disposeNative(this.nativePointer);
    }
    private native void disposeNative(int nativePointer);

}
