/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2003-2006, Geotools Project Managment Committee (PMC)
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 */
package org.geotools.renderer.style;

// J2SE dependencies
import java.awt.BasicStroke;
import java.awt.Composite;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.GlyphVector;
import java.awt.image.BufferedImage;
import java.text.Bidi;

import org.geotools.resources.Utilities;


/**
 * Style used to represent labels over lines, polygons and points
 * 
 *
 * @author Andrea Aime
 * @author dblasby
 * @version $Id: TextStyle2D.java 27390 2007-10-09 08:09:54Z aaime $
 */

/** DJB:
	 * 
	 *  This class was fundamentally wrong - it tried to convert <LinePlacement> into <PointPlacement>. 
	 *   Not only was it doing a really crappy job, but its fundamentally the wrong place to do it.
	 * 
	 *   The SLD spec defines a <PointPlacement> as:
	 * <xsd:sequence>
	 *    <xsd:element ref="sld:AnchorPoint" minOccurs="0"/>
	 *    <xsd:element ref="sld:Displacement" minOccurs="0"/>
	 *    <xsd:element ref="sld:Rotation" minOccurs="0"/>
	 * </xsd:sequence>
	 * 
	 *  and <LinePlacement> as:
	 * <xsd:sequence>
	 *  <xsd:element ref="sld:PerpendicularOffset "minOccurs="0"/>
	 * </xsd:sequence>
	 * 
	 *   its annotated as:
	 * A "PerpendicularOffset" gives the perpendicular distance away from a line to draw a label.
     * which is a bit vague, but there's a little more details here:
     * 
     * The PerpendicularOffset element of a LinePlacement gives the perpendicular distance away from a line to draw a label.   ...
     * The distance is in pixels and is positive to the left-hand.
     * 
     *  Left hand/right hand for perpendicularOffset is just crap - I'm assuming them mean +ive --> "up" and -ive --> "down".
     *  See the actual label code for how it deals with this.
     * 
     *  I've removed all the absoluteLineDisplacement stuff and replaced it with
     *     isPointPlacement() (true) --> render normally (PointPlacement Attributes)
     *     isPointPlacement() (false) --> render LinePlacement 
     * 
     *   This replaces the old behavior which converted a LinePlacement -> pointplacement and set the absoluteLineDisplacement flag!
	 * 
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/render/src/main/java/org/geotools/renderer/style/TextStyle2D.java $
	 * */
	 
public class TextStyle2D extends Style2D {
    GlyphVector textGlyphVector;
    Shape haloShape;
    String label;
    Font font;
    double rotation;
      /** yes = <PointPlacement> no = <LinePlacement>  default = yes**/
    boolean pointPlacement = true;
    int     perpendicularOffset =0; // only valid when using a LinePlacement
    double anchorX;
    double anchorY;
    double displacementX;
    double displacementY;
    Paint haloFill;
    Composite haloComposite;
    float haloRadius;
    Style2D graphic;


    /** Holds value of property fill. */
    private Paint fill;

    /** Holds value of property composite. */
    private Composite composite;

    /**
     */
    public double getAnchorX() {
        return anchorX;
    }

    /**
     */
    public double getAnchorY() {
        return anchorY;
    }

    /**
     */
    public Font getFont() {
        return font;
    }

    /**
     */
    public Composite getHaloComposite() {
        return haloComposite;
    }

    /**
     */
    public Paint getHaloFill() {
        return haloFill;
    }

    /**
     */
    public float getHaloRadius() {
        return haloRadius;
    }

    /**
     */
    public double getRotation() {
        return rotation;
    }

    /**
     * recompute each time
     */
    public GlyphVector getTextGlyphVector(Graphics2D graphics) {
        // arabic and hebrew are scripted and right to left, they do require full layout
        // whilst western chars are easier to deal with. Find out which case we're dealing with,
        // and create the glyph vector with the appropriate call
        final char[] chars = label.toCharArray();
        final int length = label.length();
        if(Bidi.requiresBidi(chars, 0, length) && 
                new Bidi(label, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft())
            textGlyphVector = font.layoutGlyphVector(graphics.getFontRenderContext(), chars, 
                    0, length, Font.LAYOUT_RIGHT_TO_LEFT);
        else
            textGlyphVector = font.createGlyphVector(graphics.getFontRenderContext(), chars);
        return textGlyphVector;
    }

    /**
     */
    public Shape getHaloShape(Graphics2D graphics) 
    {
            GlyphVector gv = getTextGlyphVector(graphics);
            haloShape = new BasicStroke(2f * haloRadius, BasicStroke.CAP_ROUND,
                    BasicStroke.JOIN_ROUND).createStrokedShape(gv.getOutline());
        return haloShape;
    }

    /**
     * @param f
     */
    public void setAnchorX(double f) {
        anchorX = f;
    }

    /**
     * @param f
     */
    public void setAnchorY(double f) {
        anchorY = f;
    }

    /**
     * @param font
     */
    public void setFont(Font font) {
        this.font = font;
    }

    /**
     * @param composite
     */
    public void setHaloComposite(Composite composite) {
        haloComposite = composite;
    }

    /**
     * @param paint
     */
    public void setHaloFill(Paint paint) {
        haloFill = paint;
    }

    /**
     * @param f
     */
    public void setHaloRadius(float f) {
        haloRadius = f;
    }

    /**
     * @param f
     */
    public void setRotation(double f) {
        rotation = f;
    }

    /**
     * @return Returns the label.
     */
    public String getLabel() {
        return label;
    }

    /**
     * @param label The label to set.
     */
    public void setLabel(String label) {
        this.label = label;
    }

    /**
     * @return Returns the pointPlacement (true => point placement, false => line placement)
     */
    public boolean isPointPlacement() {
        return pointPlacement;
    }

    /**
     * @param pointPlacement (true => point placement, false => line placement.)
     */
    public void setPointPlacement(boolean pointPlacement) {
        this.pointPlacement = pointPlacement;
    }

    /**
     * @return Returns the displacementX.
     */
    public double getDisplacementX() {
        return displacementX;
    }

    /**
     * @param displacementX The displacementX to set.
     */
    public void setDisplacementX(double displacementX) {
        this.displacementX = displacementX;
    }

    /**
     * @return Returns the displacementY.
     */
    public double getDisplacementY() {
        return displacementY;
    }

    /**
     * @param displacementY The displacementY to set.
     */
    public void setDisplacementY(double displacementY) {
        this.displacementY = displacementY;
    }

    /**
     * Getter for property fill.
     *
     * @return Value of property fill.
     */
    public Paint getFill() {
        return this.fill;
    }

    /**
     * Setter for property fill.
     *
     * @param fill New value of property fill.
     */
    public void setFill(Paint fill) {
        this.fill = fill;
    }
    
    /**
     *  only valid for a isPointPlacement=false (ie. a lineplacement)
     * @param displace in pixels
     */
    public void setPerpendicularOffset(int displace)
    {
    	perpendicularOffset = displace;
    }
    
    /**
     * only valid for a isPointPlacement=false (ie. a lineplacement)
     * @return displacement in pixels
     */
    public int getPerpendicularOffset()
    {
    	return perpendicularOffset;
    }
	
	

    /**
     * Getter for property composite.
     *
     * @return Value of property composite.
     */
    public Composite getComposite() {
        return this.composite;
    }

    /**
     * Setter for property composite.
     *
     * @param composite New value of property composite.
     */
    public void setComposite(Composite composite) {
        this.composite = composite;
    }

    /**
     * Returns a string representation of this style.
     */
    public String toString() {
        return Utilities.getShortClassName(this) + "[\"" + label + "\"]";
    }
    
        
        /**
         * Sets the style2D to be drawn underneath this text
         */
        public void setGraphic(Style2D s) {
        	graphic = s;
        }
        
       /**
         * gets the Style2D to be drawn underneath this text
         */
        public Style2D getGraphic() {
        	return graphic;
        }
        
        public Rectangle getGraphicDimensions() {
        	if (graphic instanceof MarkStyle2D)
        		return ((MarkStyle2D)graphic).getShape().getBounds();
        	else if (graphic instanceof GraphicStyle2D) {
        		BufferedImage i = ((GraphicStyle2D)graphic).getImage();
        		return new Rectangle(i.getWidth(),i.getHeight());
        	} else {
        		throw new RuntimeException("Can't render graphic which is not a MarkStyle2D or a GraphicStyle2D");
        	}
        }
    
}
