/*
 * @(#)GlyphPainter2.java	1.18 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package javax.swing.text;

import java.text.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.Rectangle2D;

/**
 * A class to perform rendering of the glyphs.
 * This can be implemented to be stateless, or
 * to hold some information as a cache to 
 * facilitate faster rendering and model/view
 * translation.  At a minimum, the GlyphPainter
 * allows a View implementation to perform its
 * duties independent of a particular version
 * of JVM and selection of capabilities (i.e.
 * shaping for i18n, etc).
 * <p>
 * This implementation is intended for operation
 * under the Java 2 SDK.  It uses the 
 * java.awt.font.TextLayout class to do i18n capable
 * rendering.
 *
 * @author  Timothy Prinzing
 * @version 1.18 12/03/01
 * @see GlyphView
 */
class GlyphPainter2 extends GlyphView.GlyphPainter {

    public GlyphPainter2(TextLayout layout) {
	this.layout = layout;
    }

    /**
     * Create a painter to use for the given GlyphView.  
     */
    public GlyphView.GlyphPainter getPainter(GlyphView v, int p0, int p1) {
	return null;
    }

    /**
     * Determine the span the glyphs given a start location
     * (for tab expansion).  This implementation assumes it
     * has no tabs (i.e. TextLayout doesn't deal with tab
     * expansion).
     */
    public float getSpan(GlyphView v, int p0, int p1, 
			 TabExpander e, float x) {

	if ((p0 == v.getStartOffset()) && (p1 == v.getEndOffset())) {
	    return layout.getAdvance();
	}
	int p = v.getStartOffset();
	int index0 = p0 - p;
	int index1 = p1 - p;

	TextHitInfo hit0 = TextHitInfo.afterOffset(index0);
	TextHitInfo hit1 = TextHitInfo.beforeOffset(index1);
	float[] locs = layout.getCaretInfo(hit0);
	float x0 = locs[0];
	locs = layout.getCaretInfo(hit1);
	float x1 = locs[0];
	return (x1 > x0) ? x1 - x0 : x0 - x1;
    }

    public float getHeight(GlyphView v) {
	return layout.getAscent() + layout.getDescent() + layout.getLeading();
    }

    /**
     * Fetch the ascent above the baseline for the glyphs
     * corresponding to the given range in the model.
     */
    public float getAscent(GlyphView v) {
	return layout.getAscent();
    }

    /**
     * Fetch the descent below the baseline for the glyphs
     * corresponding to the given range in the model.
     */
    public float getDescent(GlyphView v) {
	return layout.getDescent();
    }

    /**
     * Paint the glyphs for the given view.  This is implemented
     * to only render if the Graphics is of type Graphics2D which
     * is required by TextLayout (and this should be the case if
     * running on the Java2 SDK).
     */
    public void paint(GlyphView v, Graphics g, Shape a, int p0, int p1) {
	if (g instanceof Graphics2D) {
	    Rectangle2D alloc = a.getBounds2D();
	    Graphics2D g2d = (Graphics2D)g;
	    float y = (float) alloc.getY() + layout.getAscent() + layout.getLeading();
	    float x = (float) alloc.getX();
            if( p0 > v.getStartOffset() || p1 < v.getEndOffset() ) {
                try {
                    //TextLayout can't render only part of it's range, so if a
                    //partial range is required, add a clip region.
                    Shape s = v.modelToView(p0, Position.Bias.Forward,
                                            p1, Position.Bias.Backward, a);
                    Shape savedClip = g.getClip();
                    g2d.clip(s);
                    layout.draw(g2d, x, y);
                    g.setClip(savedClip);
                } catch (BadLocationException e) {}
            } else {
                layout.draw(g2d, x, y);
            }
	}
    }

    public Shape modelToView(GlyphView v, int pos, Position.Bias bias,
			     Shape a) throws BadLocationException {
	int offs = pos - v.getStartOffset();
	Rectangle2D alloc = a.getBounds2D();
	TextHitInfo hit = (bias == Position.Bias.Forward) ? 
	    TextHitInfo.afterOffset(offs) : TextHitInfo.beforeOffset(offs);
	float[] locs = layout.getCaretInfo(hit);

	// vertical at the baseline, should use slope and check if glyphs
	// are being rendered vertically.
	alloc.setRect(alloc.getX() + locs[0], alloc.getY(), 1, alloc.getHeight());
	return alloc;
    }

    /**
     * Provides a mapping from the view coordinate space to the logical
     * coordinate space of the model.
     *
     * @param v the view containing the view coordinates
     * @param x the X coordinate
     * @param y the Y coordinate
     * @param a the allocated region to render into
     * @param biasReturn either <code>Position.Bias.Forward</code>
     *  or <code>Position.Bias.Backward</code> is returned as the
     *  zero-th element of this array
     * @return the location within the model that best represents the
     *  given point of view
     * @see View#viewToModel
     */
    public int viewToModel(GlyphView v, float x, float y, Shape a, 
			   Position.Bias[] biasReturn) {

	Rectangle2D alloc = (a instanceof Rectangle2D) ? (Rectangle2D)a : a.getBounds2D();
        //Move the y co-ord of the hit onto the baseline.  This is because TextLayout supports
        //italic carets and we do not.
	TextHitInfo hit = layout.hitTestChar(x - (float)alloc.getX(), 0);
	int pos = hit.getInsertionIndex();
	biasReturn[0] = hit.isLeadingEdge() ? Position.Bias.Forward : Position.Bias.Backward;
	return pos + v.getStartOffset();
    }

    /**
     * Determines the model location that represents the
     * maximum advance that fits within the given span.
     * This could be used to break the given view.  The result 
     * should be a location just shy of the given advance.  This
     * differs from viewToModel which returns the closest
     * position which might be proud of the maximum advance.
     *
     * @param v the view to find the model location to break at.
     * @param p0 the location in the model where the
     *  fragment should start it's representation >= 0.
     * @param pos the graphic location along the axis that the
     *  broken view would occupy >= 0.  This may be useful for
     *  things like tab calculations.
     * @param len specifies the distance into the view
     *  where a potential break is desired >= 0.  
     * @return the maximum model location possible for a break.
     * @see View#breakView
     */
    public int getBoundedPosition(GlyphView v, int p0, float x, float len) {
        if( len < 0 )
            throw new IllegalArgumentException("Length must be >= 0.");

        TextHitInfo hit = layout.hitTestChar(len, 0);

        if( (hit.getCharIndex() == -1) && !layout.isLeftToRight() ) {
            return v.getEndOffset();
        }
	int pos = (hit.isLeadingEdge()) ? hit.getInsertionIndex()
                  : hit.getInsertionIndex() - 1;
	return pos + v.getStartOffset(); 
    }
    
    /**
	 * Provides a way to determine the next visually represented model
	 * location that one might place a caret.  Some views may not be
	 * visible, they might not be in the same order found in the model, or
	 * they just might not allow access to some of the locations in the
	 * model.
	 *
	 * @param v the view to use
	 * @param pos the position to convert >= 0
	 * @param a the allocated region to render into
	 * @param direction the direction from the current position that can
	 *  be thought of as the arrow keys typically found on a keyboard.
	 *  This may be SwingConstants.WEST, SwingConstants.EAST, 
	 *  SwingConstants.NORTH, or SwingConstants.SOUTH.  
	 * @return the location within the model that best represents the next
	 *  location visual position.
	 * @exception BadLocationException
	 * @exception IllegalArgumentException for an invalid direction
	 */
        public int getNextVisualPositionFrom(GlyphView v, int pos, 
                                             Position.Bias b, Shape a, 
					     int direction,
					     Position.Bias[] biasRet) 
	    throws BadLocationException {

	    int startOffset = v.getStartOffset();
	    int endOffset = v.getEndOffset();
	    Segment text;
            AbstractDocument doc;
            boolean viewIsLeftToRight;
            TextHitInfo currentHit, nextHit;
	    
	    switch (direction) {
	    case View.NORTH:
		break;
	    case View.SOUTH:
		break;
	    case View.EAST:
                doc = (AbstractDocument)v.getDocument();
                viewIsLeftToRight = doc.isLeftToRight(startOffset, endOffset);
                
		if(startOffset == doc.getLength()) {
		    if(pos == -1) {
			biasRet[0] = Position.Bias.Forward;
			return startOffset;
		    }
		    // End case for bidi text where newline is at beginning
		    // of line.
		    return -1;
		}
		if(pos == -1) {
                    // Entering view from the left.
                    if( viewIsLeftToRight ) {
                        biasRet[0] = Position.Bias.Forward;
                        return startOffset;
                    } else {
                        text = v.getText(endOffset - 1, endOffset);
                        char c = text.array[text.offset];
                        SegmentCache.releaseSharedSegment(text);
                        if(c == '\n') {
                            biasRet[0] = Position.Bias.Forward;
                            return endOffset-1;
                        }
                        biasRet[0] = Position.Bias.Backward;
                        return endOffset;
                    }
		}
                if( b==Position.Bias.Forward )
                    currentHit = TextHitInfo.afterOffset(pos-startOffset);
                else
                    currentHit = TextHitInfo.beforeOffset(pos-startOffset);
                nextHit = layout.getNextRightHit(currentHit);
                if( nextHit == null ) {
                    return -1;
                }
                if( viewIsLeftToRight != layout.isLeftToRight() ) {
                    // If the layout's base direction is different from
                    // this view's run direction, we need to use the weak
                    // carrat. 
                    nextHit = layout.getVisualOtherHit(nextHit);
                }
                pos = nextHit.getInsertionIndex() + startOffset;
                
		if(pos == endOffset) {
                    // A move to the right from an internal position will
                    // only take us to the endOffset in a left to right run.
		    text = v.getText(endOffset - 1, endOffset);
                    char c = text.array[text.offset];
                    SegmentCache.releaseSharedSegment(text);
		    if(c == '\n') {
			return -1;
		    }
		    biasRet[0] = Position.Bias.Backward;
		}
		else {
		    biasRet[0] = Position.Bias.Forward;
		}
		return pos;
	    case View.WEST:
                doc = (AbstractDocument)v.getDocument();
                viewIsLeftToRight = doc.isLeftToRight(startOffset, endOffset);
                
		if(startOffset == doc.getLength()) {
		    if(pos == -1) {
			biasRet[0] = Position.Bias.Forward;
			return startOffset;
		    }
		    // End case for bidi text where newline is at beginning
		    // of line.
		    return -1;
		}
		if(pos == -1) {
                    // Entering view from the right
                    if( viewIsLeftToRight ) {
                        text = v.getText(endOffset - 1, endOffset);
                        char c = text.array[text.offset];
                        SegmentCache.releaseSharedSegment(text);
                        if(c == '\n') {
                            biasRet[0] = Position.Bias.Forward;
                            return endOffset - 1;
                        }
                        biasRet[0] = Position.Bias.Backward;
                        return endOffset;
                    } else {
                        biasRet[0] = Position.Bias.Forward;
                        return startOffset;
                   }
		}
                if( b==Position.Bias.Forward )
                    currentHit = TextHitInfo.afterOffset(pos-startOffset);
                else
                    currentHit = TextHitInfo.beforeOffset(pos-startOffset);
                nextHit = layout.getNextLeftHit(currentHit);
                if( nextHit == null ) {
                    return -1;
                }
                if( viewIsLeftToRight != layout.isLeftToRight() ) {
                    // If the layout's base direction is different from
                    // this view's run direction, we need to use the weak
                    // carrat. 
                    nextHit = layout.getVisualOtherHit(nextHit);
                }
                pos = nextHit.getInsertionIndex() + startOffset;

		if(pos == endOffset) {
                    // A move to the left from an internal position will
                    // only take us to the endOffset in a right to left run.
		    text = v.getText(endOffset - 1, endOffset);
                    char c = text.array[text.offset];
                    SegmentCache.releaseSharedSegment(text);
		    if(c == '\n') {
			return -1;
		    }
		    biasRet[0] = Position.Bias.Backward;
		}
		else {
		    biasRet[0] = Position.Bias.Forward;
		}
		return pos;
	    default:
		throw new IllegalArgumentException("Bad direction: " + direction);
	    }
	    return pos;

	}
    // --- variables ---------------------------------------------

    TextLayout layout;

}
