package ij.gui;
import ij.*;
import ij.process.*;
import ij.util.*;
import ij.macro.Interpreter;
import ij.plugin.frame.Recorder;
import ij.plugin.Colors;
import java.awt.geom.*;
import java.awt.*;
import java.awt.image.BufferedImage;


/** This class is a rectangular ROI containing text. */
public class TextRoi extends Roi {

    public static final int LEFT=0, CENTER=1, RIGHT=2;
    static final int MAX_LINES = 50;

    private static final String line1 = "Enter text, then press";
    private static final String line2 = "ctrl+b to add to overlay";
    private static final String line3 = "or ctrl+d to draw.";
    private static final String line1a = "Enter text...";
    private String[] theText = new String[MAX_LINES];
    private static String name = "SansSerif";
    private static int style = Font.PLAIN;
    private static int size = 18;
    private Font font;
    private static boolean antialiasedText = true; // global flag used by text tool
    private static int globalJustification = LEFT;
    private static Color defaultFillColor;
    private int justification = LEFT;
    private double previousMag;
    private boolean firstChar = true;
    private boolean firstMouseUp = true;
    private double angle;  // degrees
    private static double defaultAngle;
    private static boolean firstTime = true;
    private Roi previousRoi;
    private Graphics fontGraphics;
    private static Font defaultFont = IJ.font12;

    /** Creates a TextRoi using the defaultFont.*/
    public TextRoi(int x, int y, String text) {
        this(x, y, text, defaultFont);
    }
    
    /** Use this constructor as a drop-in replacement for ImageProcessor.drawString(). */
    public TextRoi(String text, double x, double y, Font font) {
        super(x, y, 1, 1);
        init(text,font);
        if (font!=null) {
            Graphics g = getFontGraphics(font);
            FontMetrics metrics = g.getFontMetrics(font);
            Rectangle2D.Double fbounds = getFloatBounds();
            fbounds.y = fbounds.y-metrics.getAscent();
            setBounds(fbounds);
        }
    }

    /** Creates a TextRoi using sub-pixel coordinates.*/
    public TextRoi(double x, double y, String text) {
        super(x, y, 1.0, 1.0);
        init(text, null);
    }

    /** Creates a TextRoi using the specified location and Font.
     * @see ij.gui.Roi#setStrokeColor
     * @see ij.gui.Roi#setNonScalable
     * @see ij.ImagePlus#setOverlay(ij.gui.Overlay)
     */
    public TextRoi(int x, int y, String text, Font font) {
        super(x, y, 1, 1);
        init(text, font);
    }

    /** Creates a TextRoi using the specified sub-pixel location and Font. */
    public TextRoi(double x, double y, String text, Font font) {
        super(x, y, 1.0, 1.0);
        init(text, font);
    }

    /** Creates a TextRoi using the specified sub-pixel location, size and Font. */
    public TextRoi(double x, double y, double width, double height, String text, Font font) {
        super(x, y, width, height);
        init(text, font);
    }
    
    /** Creates a TextRoi using the specified text and location. */
    public static TextRoi create(String text, double x, double y, Font font) {
        return new TextRoi(text, x, y, font);
    }

    /** Obsolete. */
    public static TextRoi create(double x, double y, String text, Font font) {
        return new TextRoi(x, y, text, font);
    }

    private void init(String text, Font font) {
        String[] lines = Tools.split(text, "\n");
        int count = Math.min(lines.length, MAX_LINES);
        for (int i=0; i<count; i++)
            theText[i] = lines[i];
        if (font==null)
            font = defaultFont;
        if (font==null)
            font = new Font("SansSerif", Font.PLAIN, 14);
        this.font = font;
        setAntiAlias(antialiasedText);
        firstChar = false;
        if (defaultColor!=null)
            setStrokeColor(defaultColor);
        updateBounds();
    }

    /** @deprecated */
    public TextRoi(int x, int y, String text, Font font, Color color) {
        super(x, y, 1, 1);
        if (font==null) font = new Font(name, style, size);
        this.font = font;
        IJ.error("TextRoi", "API has changed. See updated example at\n../../../../macros/js/TextOverlay.js");
    }

    public TextRoi(int x, int y, ImagePlus imp) {
        super(x, y, imp);
        ImageCanvas ic = imp.getCanvas();
        double mag = getMagnification();
        if (mag>1.0)
            mag = 1.0;
        if (size<(12/mag))
            size = (int)(12/mag);
        if (firstTime) {
            theText[0] = line1;
            theText[1] = line2;
            theText[2] = line3;
            firstTime = false;
        } else
            theText[0] = line1a;
        if (previousRoi!=null && (previousRoi instanceof TextRoi)) {
            firstMouseUp = false;
            previousRoi = null;
        }
        font = new Font(name, style, size);
        justification = globalJustification;
        setStrokeColor(Toolbar.getForegroundColor());
        setAntiAlias(antialiasedText);
        if (WindowManager.getWindow("Fonts")!=null) {
            setFillColor(defaultFillColor);
            setAngle(defaultAngle);
        }
    }

    /** This method is used by the text tool to add typed
        characters to displayed text selections. */
    public void addChar(char c) {
        if (imp==null) return;
        if (!(c>=' ' || c=='\b' || c=='\n')) return;
        int cline = 0;
        if (firstChar) {
            theText[cline] = new String("");
            for (int i=1; i<MAX_LINES; i++)
                theText[i] = null;
        } else {
            for (int i=0; i<theText.length && theText[i] != null; i++)
                cline = i; //add the character to the last line
        }
        if ((int)c=='\b') {
            // backspace
            if (theText[cline].length()>0)
                theText[cline] = theText[cline].substring(0, theText[cline].length()-1);
            else if (cline>0) {
                theText[cline] = null;
                cline--;
            }
            if (angle!=0.0)
                imp.draw();
            else
                imp.draw(clipX, clipY, clipWidth, clipHeight);
            firstChar = false;
            return;
        } else if ((int)c=='\n') {
            // newline
            if (cline<(MAX_LINES-1)) cline++;
            theText[cline] = "";
            updateBounds();
            updateText();
        } else {
            char[] chr = {c};
            theText[cline] += new String(chr);
            updateBounds();
            updateText();
            firstChar = false;
            return;
        }
    }

    Font getScaledFont() {
        if (font==null)
            font = new Font("SansSerif", Font.PLAIN, 14);
        double mag = getMagnification();
        if (nonScalable || imp==null || mag==1.0)
            return font;
        else
            return font.deriveFont((float)(font.getSize()*mag));
    }
    
    /** Renders the text on the image. Draws the text in
     * the foreground color if ip.setColor(Color) has
     * not been called.
     *  @see ij.process.ImageProcessor#setFont(Font)
     *  @see ij.process.ImageProcessor#setAntialiasedText(boolean)
     *  @see ij.process.ImageProcessor#setColor(Color)
    */
    public void drawPixels(ImageProcessor ip) {
        if (!ip.fillValueSet())
            ip.setColor(Toolbar.getForegroundColor());
        ip.setFont(font);
        ip.setAntialiasedText(getAntiAlias());
        FontMetrics metrics = ip.getFontMetrics();
        int fontHeight = metrics.getHeight();
        int descent = metrics.getDescent();
        int i = 0;
        int yy = 0;
        int xi = (int)Math.round(getXBase());
        int yi = (int)Math.round(getYBase());
        while (i<MAX_LINES && theText[i]!=null) {
            switch (justification) {
                case LEFT:
                    ip.drawString(theText[i], xi, yi+yy+fontHeight);
                    break;
                case CENTER:
                    int tw = metrics.stringWidth(theText[i]);
                    ip.drawString(theText[i], xi+(this.width-tw)/2, yi+yy+fontHeight);
                    break;
                case RIGHT:
                    tw = metrics.stringWidth(theText[i]);
                    ip.drawString(theText[i], xi+this.width-tw, yi+yy+fontHeight);
                    break;
            }
            i++;
            yy += fontHeight;
        }
    }

    /** Draws the text on the screen, clipped to the ROI. */
    public void draw(Graphics g) {
        if (IJ.debugMode) IJ.log("draw: "+theText[0]+"  "+this.width+","+this.height);
        if (Interpreter.isBatchMode() && ic!=null && ic.getDisplayList()!=null)
            return;
        Color c = getStrokeColor();
        setStrokeColor(getColor());
        super.draw(g); // draw the rectangle
        setStrokeColor(c);
        double mag = getMagnification();
        int sx = screenXD(getXBase());
        int sy = screenYD(getYBase());
        int swidth = (int)((bounds!=null?bounds.width:this.width)*mag);
        int sheight = (int)((bounds!=null?bounds.height:this.height)*mag);
        Rectangle r = null;
        if (angle!=0.0)
            drawText(g);
        else {
            r = g.getClipBounds();
            g.setClip(sx, sy, swidth, sheight);
            drawText(g);
            if (r!=null)
                g.setClip(r.x, r.y, r.width, r.height);
        }
    }
    
    public void drawOverlay(Graphics g) {
        drawText(g);
    }

    void drawText(Graphics g) {
        g.setColor( strokeColor!=null? strokeColor:ROIColor);
        Java2.setAntialiasedText(g, getAntiAlias());
        double mag = getMagnification();
        int xi = (int)Math.round(getXBase());
        int yi = (int)Math.round(getYBase());
        double widthd = bounds!=null?bounds.width:this.width;
        double heightd = bounds!=null?bounds.height:this.height;
        int widthi = (int)Math.round(widthd);
        int heighti = (int)Math.round(heightd);
        Font font = getScaledFont();
        FontMetrics metrics = g.getFontMetrics(font);
        int fontHeight = metrics.getHeight();
        int descent = metrics.getDescent();
        g.setFont(font);
        Graphics2D g2d = (Graphics2D)g;
        int sx = nonScalable?xi:screenXD(getXBase());
        int sy = nonScalable?yi:screenYD(getYBase());
        int sw = nonScalable?widthi:(int)(getMagnification()*widthd);
        int sh = nonScalable?heighti:(int)(getMagnification()*heightd);
        AffineTransform at = null;
        if (angle!=0.0) {
            at = g2d.getTransform();
            double cx=sx, cy=sy;
            double theta = Math.toRadians(angle);
            g2d.rotate(-theta, cx, cy);
        }
        int i = 0;
        if (fillColor!=null) {
            Color c = g.getColor();
            int alpha = fillColor.getAlpha();
            g.setColor(fillColor);
            g.fillRect(sx, sy, sw, sh);
            g.setColor(c);
        }
        while (i<MAX_LINES && theText[i]!=null) {
            switch (justification) {
                case LEFT:
                    g.drawString(theText[i], sx, sy+fontHeight-descent);
                    break;
                case CENTER:
                    int tw = metrics.stringWidth(theText[i]);
                    g.drawString(theText[i], sx+(sw-tw)/2, sy+fontHeight-descent);
                    break;
                case RIGHT:
                    tw = metrics.stringWidth(theText[i]);
                    g.drawString(theText[i], sx+sw-tw, sy+fontHeight-descent);
                    break;
            }
            i++;
            sy += fontHeight;
        }
        if (at!=null)  // restore transformation matrix used to rotate text
            g2d.setTransform(at);
    }

    /** Returns the name of the default font. Use getCurrentFont().getName()
         to get the name of the font that this TextRoi is using. */
    public static String getDefaultFontName() {
        return name;
    }

    /** Returns the default font size. Use getCurrentFont().getSize()
         to get the size of the font that this TextRoi is using. */
    public static int getDefaultFontSize() {
        return size;
    }

    /** Returns the default font style. Use getCurrentFont().getStyle()
         to get the style of the font that this TextRoi is using. */
    public static int getDefaultFontStyle() {
        return style;
    }
    
    /** Sets the current font. */
    public void setFont(Font font) {
        this.font = font;
        updateBounds();
    }
    
    /** Sets the size of the current font. */
    public void setFontSize(int size) {
        if (font==null)
            font = defaultFont;
        font = font.deriveFont((float)size);
    }
        
    /** Returns the current font. */
    public Font getCurrentFont() {
        return font;
    }
    
    /** Returns the state of the global 'antialiasedText' variable, which is used by the "Fonts" widget. */
    public static boolean isAntialiased() {
        return antialiasedText;
    }

    /** Sets the state of the global 'antialiasedText' variable. */
    public static void setAntialiasedText(boolean antialiased) {
        antialiasedText = antialiased;
    }
    
    /** Sets the 'antiAlias' instance variable. */
    public void setAntialiased(boolean antiAlias) {
        setAntiAlias(antiAlias);
        if (angle>0.0)
            setAntiAlias(true);
    }
    
    /** Returns the state of the 'antiAlias' instance variable. */
    public boolean getAntialiased() {
        return getAntiAlias();
    }
        
    /** Sets the default text tool justification (LEFT, CENTER or RIGHT). */
    public static void setGlobalJustification(int justification) {
        if (justification<0 || justification>RIGHT)
            justification = LEFT;
        globalJustification = justification;
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp!=null) {
            Roi roi = imp.getRoi();
            if (roi instanceof TextRoi) {
                ((TextRoi)roi).setJustification(justification);
                imp.draw();
            }
        }
    }
    
    /** Returns the default text tool justification (LEFT, CENTER or RIGHT). */
    public static int getGlobalJustification() {
        return globalJustification;
    }

    /** Sets the 'justification' instance variable (LEFT, CENTER or RIGHT) */
    public void setJustification(int justification) {
        if (justification<0 || justification>RIGHT)
            justification = LEFT;
        this.justification = justification;
        updateBounds();
        if (imp!=null)
            imp.draw();
    }
    
    /** Returns the value of the 'justification' instance variable (LEFT, CENTER or RIGHT). */
    public int getJustification() {
        return justification;
    }

    /** Sets the global font face, size and style that will be used by
        TextROIs interactively created using the text tool. */
    public static void setFont(String fontName, int fontSize, int fontStyle) {
        setFont(fontName, fontSize, fontStyle, true);
    }
    
    /** Sets the font face, size, style and antialiasing mode that will 
        be used by TextROIs interactively created using the text tool. */
    public static void setFont(String fontName, int fontSize, int fontStyle, boolean antialiased) {
        name = fontName;
        size = fontSize;
        style = fontStyle;
        globalJustification = LEFT;
        antialiasedText = antialiased;
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp!=null) {
            Roi roi = imp.getRoi();
            if (roi instanceof TextRoi) {
                roi.setAntiAlias(antialiased);
                ((TextRoi)roi).setCurrentFont(new Font(name, style, size));
                imp.draw();
            }
        }
            
    }

    /** Sets the default font. */
    public static void setDefaultFont(Font font) {
        defaultFont = font;
    }
    
    /** Sets the default font size. */
    public static void setDefaultFontSize(int size) {
        defaultFont = defaultFont.deriveFont((float)size);
    }

    /** Sets the default fill (background) color. */
    public static void setDefaultFillColor(Color fillColor) {
        defaultFillColor = fillColor;
    }

    /** Sets the default angle. */
    public static void setDefaultAngle(double angle) {
        defaultAngle = angle;
    }

    protected void handleMouseUp(int screenX, int screenY) {
        super.handleMouseUp(screenX, screenY);
        if (this.width<5 && this.height<5 && imp!=null && previousRoi==null) {
            int ox = ic!=null?ic.offScreenX(screenX):screenX;
            int oy = ic!=null?ic.offScreenY(screenY):screenY;
            TextRoi roi = new TextRoi(ox, oy, line1a);
            roi.setStrokeColor(Toolbar.getForegroundColor());
            roi.firstChar = true;
            imp.setRoi(roi);
            return;
        } else if (firstMouseUp) {
            updateBounds();
            updateText();
            firstMouseUp = false;
        }
        if (this.width<5 || this.height<5)
            imp.deleteRoi();
    }
    
    /** Increases the size of bounding rectangle so it's large enough to hold the text. */ 
    private void updateBounds() {
        if (firstChar )
            return;
        double lineHeight = 0;
        double mag = getMagnification();
        Font font = getScaledFont();
        Graphics g = getFontGraphics(font);
        Java2.setAntialiasedText(g, getAntiAlias());
        FontMetrics metrics = g.getFontMetrics(font);
        double fontHeight = metrics.getHeight()/mag;
        int i=0, nLines=0;
        Rectangle2D.Double b = getFloatBounds();
        double newWidth = 10;
        while (i<MAX_LINES && theText[i]!=null) {
            nLines++;
            double w = stringWidth(theText[i],metrics,g)/mag;
            if (w>newWidth)
                newWidth = w;
            i++;
        }
        newWidth += 2.0;
        b.width = newWidth;
        switch (justification) {
            case LEFT:
                break;
            case CENTER:
                b.x = this.oldX+this.oldWidth - newWidth/2.0;
                break;
            case RIGHT:
                b.x = this.oldX+this.oldWidth - newWidth;
                break;
        }
        b.height = nLines*fontHeight+2;
        setBounds(b);
    }
    
    private Graphics getFontGraphics(Font font) {
        if (fontGraphics==null) {
            BufferedImage bi =new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
            fontGraphics = (Graphics2D)bi.getGraphics();
        }
        fontGraphics.setFont(font);
        return  fontGraphics;
    }
    
    void updateText() {
        if (imp!=null) {
            updateClipRect();
            if (angle!=0.0)
                imp.draw();
            else
                imp.draw(clipX, clipY, clipWidth, clipHeight);
        }
    }

    double stringWidth(String s, FontMetrics metrics, Graphics g) {
        java.awt.geom.Rectangle2D r = metrics.getStringBounds(s, g);
        return r.getWidth();
    }
    
    /** Used by the Recorder for recording the text tool. */
    public String getMacroCode(String cmd, ImagePlus imp) {
        String code = "";
        boolean script = Recorder.scriptMode();
        boolean addSelection = cmd.startsWith("Add");
        if (script && !addSelection)
            code += "ip = imp.getProcessor();\n";
        if (script) {
            String str = "Font.PLAIN";
            if (style==Font.BOLD)
                str =  "Font.BOLD";
            else if (style==Font.ITALIC)
                str =  "Font.ITALIC";
            code += "font = new Font(\""+name+"\", "+str+", "+size+");\n";
            if (addSelection)
                return getAddSelectionScript(code);
            code += "ip.setFont(font);\n";
        } else {
            String options = "";
            if (style==Font.BOLD)
                options += "bold";
            if (style==Font.ITALIC)
                options += " italic";
            if (antialiasedText)
                options += " antialiased";
            if (options.equals(""))
                options = "plain";
            code += "setFont(\""+name+"\", "+size+", \""+options+"\");\n";
        }
        ImageProcessor ip = imp.getProcessor();
        ip.setFont(new Font(name, style, size));
        FontMetrics metrics = ip.getFontMetrics();
        int fontHeight = metrics.getHeight();
        if (script)
            code += "ip.setColor(new Color("+getColorArgs(getStrokeColor())+"));\n";
        else
            code += "setColor(\""+Colors.colorToString(getStrokeColor())+"\");\n";
        if (addSelection) {
            code += "Overlay.drawString(\""+text()+"\", "+this.x+", "+(this.y+fontHeight)+", "+getAngle()+");\n";
            code += "Overlay.show();\n";
        } else {
            code += (script?"ip.":"")+"drawString(\""+text()+"\", "+this.x+", "+(this.y+fontHeight)+");\n";
            if (script)
                code += "imp.updateAndDraw();\n";
            else
                code += "//makeText(\""+text()+"\", "+this.x+", "+(this.y+fontHeight)+");\n";
        }
        return (code);
    }
    
    private String text() {
        String text = "";
        for (int i=0; i<MAX_LINES; i++) {
            if (theText[i]==null) break;
            text += theText[i];
            if (theText[i+1]!=null) text += "\\n";
        }
        return text;
    }
    
    private String getAddSelectionScript(String code) {
        code += "roi = new TextRoi("+this.x+", "+this.y+", \""+text()+"\", font);\n";
        code += "roi.setStrokeColor(new Color("+getColorArgs(getStrokeColor())+"));\n";
        if (getFillColor()!=null)
            code += "roi.setFillColor(new Color("+getColorArgs(getFillColor())+"));\n";
        int just = getJustification();
        if (just>LEFT) {
            if (just==CENTER)
                code += "roi.setJustification(TextRoi.CENTER);\n";
            else if (just==RIGHT)
                code += "roi.setJustification(TextRoi.RIGHT);\n";
        }
        if (getAngle()!=0.0)
            code += "roi.setAngle("+getAngle()+");\n";
        code += "overlay.add(roi);\n";
        return code;
    }
    
    private String getColorArgs(Color c) {
        return IJ.d2s(c.getRed()/255.0,2)+", "+IJ.d2s(c.getGreen()/255.0,2)+", "+IJ.d2s(c.getBlue()/255.0,2);
    }
    
    public String getText() {
        String text = "";
        for (int i=0; i<MAX_LINES; i++) {
            if (theText[i]==null) break;
            text += theText[i]+"\n";
        }
        return text;
    }

    public void setText(String text) {
        String[] lines = Tools.split(text, "\n");
        boolean changes = false;
        for (int i=0; i<Math.min(lines.length, theText.length-1); i++) {
            if (!lines[i].equals(theText[i])) {
                theText[i] = lines[i];
                changes = true;
            }
        }
        if (lines.length < theText.length && theText[lines.length] != null) {
            theText[lines.length] = null;
            changes = true;
        }
        if (changes) {
            firstChar = false;
            updateBounds();
        }
    }

    public boolean isDrawingTool() {
        return true;
    }
    
    public void clear(ImageProcessor ip) {
        if (font==null)
            ip.fill();
        else {
            ip.setFont(font);
            ip.setAntialiasedText(antialiasedText);
            int i=0, w=0;
            while (i<MAX_LINES && theText[i]!=null) {
                int w2 = ip.getStringWidth(theText[i]);
                if (w2>w);
                    w = w2;
                i++;
            }
            Rectangle r = ip.getRoi();
            if (w>r.width) {
                r.width = w;
                ip.setRoi(r);
            }
            ip.fill();
        }
    }

    @Override
    public void setLocation(int x, int y) {
        super.setLocation(x, y);
        oldWidth = this.width;
    }

    /** Returns a copy of this TextRoi. */
    public synchronized Object clone() {
        TextRoi tr = (TextRoi)super.clone();
        tr.theText = new String[MAX_LINES];
        for (int i=0; i<MAX_LINES; i++)
            tr.theText[i] = theText[i];
        return tr;
    }
    
    public double getAngle() {
        return angle;
    }
    
    public void setAngle(double angle) {
        this.angle = angle;
        if (angle!=0.0)
            setAntiAlias(true);
    }

    public boolean getDrawStringMode() {
        return false;
    }
    
    public void setDrawStringMode(boolean drawStringMode) {
    }
    
    public void setPreviousTextRoi(Roi previousRoi) {
        this.previousRoi = previousRoi;
    }
    
    /** @deprecated Replaced by getDefaultFontName */
    public static String getFont() {
        return name;
    }

    /** @deprecated Replaced by getDefaultFontSize */
    public static int getSize() {
        return size;
    }

    /** @deprecated Replaced by getDefaultFontStyle */
    public static int getStyle() {
        return style;
    }
    
    /** @deprecated Replaced by setFont(font) */
    public void setCurrentFont(Font font) {
        this.font = font;
        updateBounds();
    }

        
}