package ij.plugin.tool;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.plugin.Colors;
import java.awt.*;
import java.awt.event.*;
import java.awt.BasicStroke;
import java.awt.geom.*;
import java.util.Vector;

//Version history
// 2012-07-14 shift to confine horizontally or vertically, ctrl-shift to resize
// 2012-07-22 options allow width=0; width&transparency range checking, alt for BG, CTRL to pick color

public class OverlayBrushTool extends PlugInTool implements Runnable {
    private final static int UNCONSTRAINED=0, HORIZONTAL=1, VERTICAL=2, DO_RESIZE=3, RESIZED=4, IDLE=5; //mode flags
    private static String WIDTH_KEY = "obrush.width";
    private static final String LOC_KEY = "obrush.loc";
    private float width = (float)Prefs.get(WIDTH_KEY, 5);
    private int transparency;
    private BasicStroke stroke;
    private GeneralPath path;
    private int mode;  //resizing brush or motion constrained horizontally or vertically
    private float xStart, yStart;
    private float oldWidth = width;
    private boolean newPath;
    private Options options;
    private GenericDialog gd;

    public void mousePressed(ImagePlus imp, MouseEvent e) {
        ImageCanvas ic = imp.getCanvas();
        float x = (float)ic.offScreenXD(e.getX());
        float y = (float)ic.offScreenYD(e.getY());
        xStart = x;
        yStart = y;
        oldWidth = width;
        int ctrlMask = IJ.isMacintosh() ? InputEvent.META_MASK : InputEvent.CTRL_MASK;
        int resizeMask = InputEvent.SHIFT_MASK | ctrlMask;
        if ((e.getModifiers() & resizeMask) == resizeMask) {
            mode = DO_RESIZE;
            return;
        } else if ((e.getModifiers() & ctrlMask) != 0) {  //Pick the color from image or overlay
            //Limitiation: no sub-pixel accuracy here.
            //Don't use awt.robot to pick the color, it is influenced by screen color calibration
            int[] rgbValues = imp.flatten().getPixel((int)x,(int)y);
            Color color = new Color(rgbValues[0],rgbValues[1],rgbValues[2]);
            boolean altKeyDown = (e.getModifiers() & InputEvent.ALT_MASK) != 0;
            if (altKeyDown)
                Toolbar.setBackgroundColor(color);
            else {
                Toolbar.setForegroundColor(color);
                if (gd != null)
                    options.setColor(color);
            }
            mode = IDLE;
            return;
        }
        mode = UNCONSTRAINED;   //prepare drawing
        path = new GeneralPath();
        path.moveTo(x, y);
        newPath = true;
        stroke = new BasicStroke(width, BasicStroke.CAP_ROUND/*CAP_BUTT*/, BasicStroke.JOIN_ROUND);
    }

    public void mouseDragged(ImagePlus imp, MouseEvent e) {
        if (mode == IDLE) return;
        ImageCanvas ic = imp.getCanvas();
        float x = (float)ic.offScreenXD(e.getX());
        float y = (float)ic.offScreenYD(e.getY());
        Overlay overlay = imp.getOverlay();
        if (overlay==null)
            overlay = new Overlay();
        if (mode == DO_RESIZE || mode == RESIZED) {
            changeBrushSize((float)(x-xStart), imp);
            return;
        }
        if ((e.getModifiers() & InputEvent.SHIFT_MASK) != 0) { //shift constrains
            if (mode == UNCONSTRAINED) {    //first movement with shift down determines direction
                if (Math.abs(x-xStart) > Math.abs(y-yStart))
                    mode = HORIZONTAL;
                else if (Math.abs(x-xStart) < Math.abs(y-yStart))
                    mode = VERTICAL;
                else return; //constraint direction still unclear
            }
            if (mode == HORIZONTAL)
                y = yStart;
            else if (mode == VERTICAL)
                x = xStart;
        } else {
            xStart = x;
            yStart = y;
            mode = UNCONSTRAINED;
        }
        path.lineTo(x, y);
        ShapeRoi roi = new ShapeRoi(path);
        boolean altKeyDown = (e.getModifiers() & InputEvent.ALT_MASK) != 0;
        Color color = altKeyDown ? Toolbar.getBackgroundColor() : Toolbar.getForegroundColor();
        float red = (float)(color.getRed()/255.0);
        float green = (float)(color.getGreen()/255.0);
        float blue = (float)(color.getBlue()/255.0);
        float alpha = (float)((100-transparency)/100.0);
        roi.setStrokeColor(new Color(red, green, blue, alpha));
        roi.setStroke(stroke);
        if (newPath) {
            overlay.add(roi);
            newPath = false;
        } else {
            overlay.remove(overlay.size()-1);
            overlay.add(roi);
        }
        imp.setOverlay(overlay);
    }

    public void mouseReleased(ImagePlus imp, MouseEvent e) {
        if (mode == RESIZED) {
            Overlay overlay = imp.getOverlay();
            overlay.remove(overlay.size()-1); //delete brush resizing circle
            imp.setOverlay(overlay);
            Prefs.set(WIDTH_KEY, width);
            if (gd!=null)
                options.setWidth(width);
        } else if (newPath)     // allow drawing a single dot
            mouseDragged(imp, e);
    }

    private void changeBrushSize(float deltaWidth, ImagePlus imp) {
        if (deltaWidth!=0) {
            Overlay overlay = imp.getOverlay();
            width = oldWidth + deltaWidth;
            if (width < 0) width = 0;
            Roi circle = new OvalRoi(xStart-width/2, yStart-width/2, width, width);
            circle.setStrokeColor(Color.red);
            overlay = imp.getOverlay();
            if (overlay==null)
                overlay = new Overlay();
            if (mode == RESIZED)
                overlay.remove(overlay.size()-1);
            overlay.add(circle);
            imp.setOverlay(overlay);
        }
        IJ.showStatus("Overlay Brush width: "+IJ.d2s(width));
        mode = RESIZED;
    }

    public void showOptionsDialog() {
        Thread thread = new Thread(this, "Brush Options");
        thread.setPriority(Thread.NORM_PRIORITY);
        thread.start();
    }

    public String getToolName() {
        return "Overlay Brush Tool";
    }

    public String getToolIcon() {
        return "C037La077Ld098L6859L4a2fL2f4fL3f99L5e9bL9b98L6888L5e8dL888cC123P2f7f9ebdcaf70";
    }

    public void run() {
        new Options();
    }

    class Options implements DialogListener {

        Options() {
            if (gd != null) {
                gd.toFront();
                return;
            }
            options = this;
            if (IJ.debugMode) IJ.log("Options: true");
            showDialog();
        }

        //set 'width' textfield and adjust scrollbar
        void setWidth(float width) {
            Vector numericFields = gd.getNumericFields();
            TextField widthField  = (TextField)numericFields.elementAt(0);
            widthField.setText(IJ.d2s(width,1));
            Vector sliders = gd.getSliders();
            Scrollbar sb = (Scrollbar)sliders.elementAt(0);
            sb.setValue((int)(width+0.5f));
        }

        void setColor(Color c) {
            String name = Colors.getColorName(c, "");
            if (name.length() > 0) {
                Vector choices = gd.getChoices();
                Choice ch = (Choice)choices.elementAt(0);
                ch.select(name);
            }
        }

        public void showDialog() {
            Color color = Toolbar.getForegroundColor();
            String colorName = Colors.colorToString2(color);
            gd = new NonBlockingGenericDialog("Overlay Brush Options");
            gd.addSlider("Brush width (pixels):", 0, 50, width);
            gd.addSlider("Transparency (%):", 0, 100, transparency);
            gd.addChoice("Color:", Colors.getColors(colorName), colorName);
            gd.setInsets(10, 0, 0);
            String ctrlString = IJ.isMacintosh()? "CMD":"CTRL";
            gd.addMessage("SHIFT for horizontal or vertical lines\n"+
                    "ALT to draw in background color\n"+
                    ctrlString+"-SHIFT-drag to change brush width\n"+
                    ctrlString+"-(ALT) click to change foreground (background) color\n"+
                    "or use this dialog or the Color Picker (shift-k).", null, Color.darkGray);
            gd.hideCancelButton();
            gd.addHelp("");
            gd.setHelpLabel("Undo");
            gd.setOKLabel("Close");
            gd.addDialogListener(this);
            Point loc = Prefs.getLocation(LOC_KEY);
            if (loc!=null) {
                gd.centerDialog(false);
                gd.setLocation (loc);
            }
            gd.showDialog();
            Prefs.saveLocation(LOC_KEY, gd.getLocation());
            if (IJ.debugMode) IJ.log("Options: false");
            gd = null;
        }

        public boolean dialogItemChanged(GenericDialog gd, AWTEvent e) {
            if (e!=null && e.toString().contains("Undo")) {
                ImagePlus imp = WindowManager.getCurrentImage();
                if (imp==null) return true;
                Overlay overlay = imp.getOverlay();
                if (overlay!=null && overlay.size()>0) {
                    overlay.remove(overlay.size()-1);
                    imp.draw();
                }
                return true;
            }
            width = (float)gd.getNextNumber();
            if (gd.invalidNumber() || width<0)
                width = (float)Prefs.get(WIDTH_KEY, 5);
            transparency = (int)gd.getNextNumber();
            if (gd.invalidNumber() || transparency<0 || transparency>100)
                transparency = 100;
            String colorName = gd.getNextChoice();
            Color color = Colors.decode(colorName, null);
            Toolbar.setForegroundColor(color);
            Prefs.set(WIDTH_KEY, width);
            return true;
        }

    }
}