import se.datadosen.jalbum.JAFilter;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.*;
import se.datadosen.util.*;
import java.awt.font.*;
import java.awt.geom.*;
import static java.lang.Math.*;

/**
 * Title:        JAlbum text image filter
 * Company:      JAlbum AB
 * @author David Ekholm
 * @version 1.1
 */
public class TextFilter implements JAFilter, Cloneable {
    public static final String LEFT_ALIGNMENT = "left";
    public static final String CENTER_ALIGNMENT = "center";
    public static final String RIGHT_ALIGNMENT = "right";
    public static final String TOP_ALIGNMENT = "top";
    public static final String BOTTOM_ALIGNMENT = "bottom";

    private String text = "$comment";
    private Font font = new Font("SansSerif", Font.BOLD, 16);
    private Color color = new Color(0,0,0,100);
    private boolean antialias = true;
    private String align = LEFT_ALIGNMENT;
    private String valign = TOP_ALIGNMENT;
    private int margin = 5;
    private int offset = 0;
    private int voffset = 0;
    private float angle = 0;
    private int referenceWidth = 0;

    // Implements JAFilter
    public String getName() {
        return "Text filter";
    }

	// Implements JAFilter
    public String getDescription() {
        return "Overlay a text string on top of an image. The string may contain $variables";
    }

	// Implements JAFilter
    public BufferedImage filter(BufferedImage bi, Map vars) {
    	
    	Font newFont = font;
    	if (referenceWidth > 0) {
    		newFont = font.deriveFont((float)font.getSize()*bi.getWidth()/referenceWidth);
    	}
		Graphics2D g = bi.createGraphics();
        if (antialias)
            g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setFont(newFont);
        g.setColor(color);
      
        Replacer r = new Replacer();
        fillReplacer(r, vars);
        String s = stripUnknownVariables(r.replace(text));
        FontRenderContext frc = g.getFontRenderContext();
        Rectangle2D rect = newFont.getStringBounds(s, frc);
        int width = bi.getWidth();
        int height = bi.getHeight();
                       
        int x,y;

		double bw = rect.getWidth();
		double bh = rect.getHeight();
		double radians = angle/180 * PI;
		double boxWidth = abs(bw * cos(radians)) + abs(bh * sin(radians));
		double boxHeight = abs(bh * cos(radians)) + abs(-bw * sin(radians));
//		System.out.println("bw: "+ bw + " bh:"  + bh);
//		System.out.println("boxWidth: "+ boxWidth + " boxHeight:"  + boxHeight);
//		g.drawRect(1,1,(int)boxWidth, (int)boxHeight);
		
        if (align.equals(LEFT_ALIGNMENT))
            x = (int)rect.getX() + margin;
        else if (align.equals(RIGHT_ALIGNMENT))
            x = (int)(width-margin-boxWidth);
        else // CENTER
            x = (int)((width-boxWidth)/2);

        if (valign.equals(TOP_ALIGNMENT))
            y = margin;
        else if (valign.equals(BOTTOM_ALIGNMENT))
            y = (int)(height-margin-boxHeight);
        else // CENTER
            y = (int)((height-boxHeight)/2);

        g.translate(x+boxWidth/2+offset, y+boxHeight/2+voffset);
        if (angle != 0) {
	        g.rotate(angle/180 * Math.PI);
        }

        g.drawString(s, (int)(-rect.getWidth()/2 - rect.getX()), (int)(-rect.getHeight()/2 - rect.getY()));
//        g.drawRect((int)(-rect.getWidth()/2), (int)(-rect.getHeight()/2), (int)rect.getWidth(), (int)rect.getHeight());
        return bi;
    }

    // Implements Cloneable
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public void setFont(Font font) {
        this.font = font;
    }

    public Font getFont() {
        return font;
    }

    public void setSize(int newSize) {
        font = new Font(font.getName(), font.getStyle(), newSize);
    }
    public int getSize() {
        return font.getSize();
    }

    public void setAngle(float newAngle) {
        angle = newAngle;
    }
    public float getAngle() {
        return angle;
    }
    
    public void setFace(String fontFace) {
        font = new Font(fontFace, font.getStyle(), font.getSize());
    }
    public String getFace() {
        return font.getFamily();
    }

    public void setStyle(String fontStyle) {
        int style = Font.PLAIN;
        if (fontStyle.equalsIgnoreCase("bold")) style = Font.BOLD;
        else if (fontStyle.equalsIgnoreCase("italic")) style = Font.ITALIC;
        else if (fontStyle.equalsIgnoreCase("bolditalic")) style = Font.BOLD | Font.ITALIC;
        font = new Font(font.getName(), style, font.getSize());
    }
    public String getStyle() {
        switch (font.getStyle()) {
        case Font.BOLD : return "bold";
        case Font.ITALIC : return "italic";
        case Font.BOLD|Font.ITALIC: return "bolditalic";
        default: return "plain";
        }
    }

    public void setColor(Color color) {
        this.color = color;
    }

    public void setColor(String htmlColor) {
        this.color = Colors.getHTMLColor(htmlColor);
    }

    public Color getColor() {
        return color;
    }

    static Replacer fillReplacer(Replacer replacer, Map variables) {
        Iterator it = variables.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry e=(Map.Entry)it.next();
//            if (e.getValue() != null) // We have a safe Map now, so this works, but is slower as Maps are expanded but seldom printed
            if (e.getValue() != null && !(e.getValue() instanceof Map)) // We have circular references e.g. fileVariables
                replacer.add("$" + (String)e.getKey(), e.getValue().toString());
        }
        return replacer;
    }
    public void setMargin(int newMargin) {
        margin = newMargin;
    }
    public int getMargin() {
        return margin;
    }
    public void setAlign(String newAlign) {
        align = newAlign;
    }
    public String getAlign() {
        return align;
    }
    public void setValign(String newValign) {
        valign = newValign;
    }
    public String getValign() {
        return valign;
    }
    public void setAntialias(boolean newAntialias) {
        antialias = newAntialias;
    }
    public boolean isAntialias() {
        return antialias;
    }
    public void setOffset(int newOffset) {
        offset = newOffset;
    }
    public int getOffset() {
        return offset;
    }
    public void setVoffset(int newVoffset) {
        voffset = newVoffset;
    }
    public int getVoffset() {
        return voffset;
    }
    public void setReferenceWidth(int referenceWidth) {
        this.referenceWidth = referenceWidth;
    }
    public int getReferenceWidth() {
        return referenceWidth;
    }
    private String stripUnknownVariables(String s) {
		StringBuffer sb = new StringBuffer();
		char[] chars = s.toCharArray();
		for (int i=0; i<chars.length; i++) {
			if (chars[i] == '$') { // Skip past next space
				i++;
				while (i < chars.length && Character.isJavaIdentifierPart(chars[i])) i++;
			}
			else sb.append(chars[i]);
		}
		return sb.toString();
	}
}