package vismon.component;

import vismon.data.MngOption;
import vismon.util.Constants;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.File;
import java.io.IOException;


public class DualSlider extends JComponent
        implements MouseListener, MouseMotionListener, ChangeListener {

    private static int              PICK_WIDTH = 3;

    //EVENT HANDLING
    private static final int        PICK_NONE = 0;
    private static final int        PICK_MIN = 1;
    private static final int        PICK_MAX = 2;
    private static final int        PICK_MID = 4;

    private double                  value;
    private double                  extent;
    private double                  minimum;
    private double                  maximum;

    private boolean                 enabled = true;

    //PAINT METHOD
    private int                     pick;
    private int                     pickOffset;
    private int                     mouseX;

    private Color                   bgColor = Color.WHITE;
    private Color                   acceptableColor = Constants.MAIN_COLOR;
    private Color                   unacceptableColor = Color.LIGHT_GRAY;

    private int                     totalWidth;
    private int                     totalHeight;

    private int                     startX;
    private int                     startY;
    private int                     sliderWidth;
    private int                     sliderHeight;

    private MngOption               mngOption;

    /**
     * Constructs a new range slider.
     *
     * @param min   - the minimum value of the range.
     * @param max   - the maximum value of the range.
     * @param lowV  - the current low value shown by the range slider's bar.
     * @param highV - the current high value shown by the range slider's bar.
     */
    public DualSlider(double min, double max, double lowV, double highV, int w, int h) {
        minimum = min;
        maximum = max;
        value = lowV;
        extent = highV - lowV;

        addMouseListener(this);
        addMouseMotionListener(this);

        totalWidth = w;
        totalHeight = h;
        setSize(w, h);
    }

    public double getLowValue() {
        return value;
    }

    public double getHighValue() {
        return value + extent;
    }

    public double getMinimum() {
        return minimum;
    }

    public double getMaximum() {
        return maximum;
    }

    public void setLowValue(double lowV) {
        double high;

        if ((lowV + extent) > getMaximum())
            high = getMaximum();
        else
            high = getHighValue();

        double ext = high - lowV;

        setRangeProperties(lowV, ext, getMinimum(), getMaximum());
    }

    public void setRangeProperties(double lowV, double ext, double min, double max) {
        value = lowV;
        extent = ext;
        minimum = min;
        maximum = max;
    }

    public void setHighValue(double highV) {
        extent = highV - getLowValue();
    }

    public void setMinimum(double min) {
        minimum = min;
    }

    public void setMaximum(double max) {
        maximum = max;
    }

    public MngOption getMngOption() {
        return mngOption;
    }

    public void setMngOption(MngOption mngOption) {
        this.mngOption = mngOption;
    }

    private Rectangle getInBounds() {
        Dimension d = getSize();
        Insets insets = getInsets();
        return new Rectangle(insets.left, insets.top, d.width - insets.left - insets.right, d.height - insets.top - insets.bottom);
    }

    @Override
    protected void paintComponent(Graphics g) {
        Rectangle rect = getInBounds();

        startX = rect.x;
        startY = rect.y;
        sliderWidth = rect.width;
        sliderHeight = rect.height;

        int barTopY = startY + sliderHeight;
        int baseLineY = startY + (int)(sliderHeight * 0.5);


        g.setColor(bgColor);
        g.fillRect(rect.x, rect.y, rect.width, rect.height);

        int minX = toScreenX(getLowValue());
        int maxX = toScreenX(getHighValue());

        //DRAWING THE BASE LINE
        g.setColor(unacceptableColor);
        g.drawLine(startX, baseLineY, minX, baseLineY);
        g.setColor(acceptableColor);
        g.drawLine(minX, baseLineY, maxX, baseLineY);
        g.setColor(unacceptableColor);
        g.drawLine(maxX, baseLineY, startX + sliderWidth, baseLineY);

        //DRAWING THE VERTICAL LINES FOR MIN AND MAX VALUES
        g.setColor(acceptableColor);
        g.drawLine(minX, startY, minX, barTopY);
        g.drawLine(minX + 1, startY, minX + 1, barTopY);
        g.drawLine(minX + 2, startY, minX + 2, barTopY);
        g.drawLine(maxX, startY, maxX, barTopY);
        g.drawLine(maxX + 1, startY, maxX + 1, barTopY);
        g.drawLine(maxX + 2, startY, maxX + 2, barTopY);

        try {
            g.drawImage(ImageIO.read(new File("images/arrow_right.png")), minX + 2, startX, null);
            g.drawImage(ImageIO.read(new File("images/arrow_left.png")), maxX - 4, startX, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Converts from screen coordinates to a range value.
    private double toLocalX(int x) {
        double xScale = (sliderWidth - 3) / (double) (getMaximum() - getMinimum());
        return (x / xScale) + getMinimum();
    }

    private int toScreenX(double x) {
        double xScale = (sliderWidth - 3) / (double) (getMaximum() - getMinimum());
        int screenX = (int) ((x - getMinimum()) * xScale);
        return screenX;
    }

    private int pickHandle(int x) {
        int minX = toScreenX(getLowValue());
        int maxX = toScreenX(getHighValue());

        int p = 0;

        if (Math.abs(x - minX) < PICK_WIDTH)
            p |= PICK_MIN;

        if (Math.abs(x - maxX) < PICK_WIDTH)
            p |= PICK_MAX;

        if ((p == 0) && (x > minX) && (x < maxX))
            p = PICK_MID;

        return p;
    }

    private void offset(double dx) {
        value += dx;
    }

    public void mousePressed(MouseEvent e) {
        if (enabled == false)
            return;

        pick = pickHandle(e.getX());
        pickOffset = e.getX() - toScreenX(getLowValue());
        mouseX = e.getX();
    }

    public void mouseDragged(MouseEvent e) {
        if (enabled == false)
            return;

        double x = toLocalX(e.getX());

        if (x < getMinimum())
            x = getMinimum();

        if (x > getMaximum())
            x = getMaximum();

        if (pick == (PICK_MIN | PICK_MAX)) {
            if ((e.getX() - mouseX) > 2)
                pick = PICK_MAX;
            else if ((e.getX() - mouseX) < -2)
                pick = PICK_MIN;
            else
                return;
        }

        switch (pick) {
            case PICK_MIN:
                if (x <= getHighValue())
                    setLowValue(x);

                break;
            case PICK_MAX:
                if (x >= getLowValue())
                    setHighValue(x);

                break;
            case PICK_MID:
                double dx = toLocalX(e.getX() - pickOffset) - getLowValue();
                if ((dx < 0) && ((getLowValue() + dx) < getMinimum()))
                    dx = getMinimum() - getLowValue();

                if ((dx > 0) && ((getHighValue() + dx) > getMaximum()))
                    dx = getMaximum() - getHighValue();

                if (dx != 0)
                    offset(dx);

                break;
        }

        repaint();
        //VismonView.getInstance().dualSliderAction(dim, avgSD);
        //TODO
    }

    public void mouseReleased(MouseEvent e) {

    }

    private void setCurs(int c) {
        Cursor cursor = Cursor.getPredefinedCursor(c);

        if (getCursor() != cursor)
            setCursor(cursor);
    }

    public void mouseMoved(MouseEvent e) {
        if (enabled == false)
            return;

        switch (pickHandle(e.getX())) {
            case PICK_MIN:
                //case PICK_MIN | PICK_MAX:
                setCurs(Cursor.W_RESIZE_CURSOR);
                break;
            case PICK_MAX:
                setCurs(Cursor.E_RESIZE_CURSOR);
                break;
            case PICK_MID:
                setCurs(Cursor.HAND_CURSOR);
                break;
            case PICK_NONE:
                setCurs(Cursor.MOVE_CURSOR);
                break;
        }
    }

    public void mouseClicked(MouseEvent e) {
        if (e.getClickCount() == 2) {
            value = minimum;
            extent = maximum - minimum;
            repaint();
        }
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
        setCurs(Cursor.DEFAULT_CURSOR);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(totalWidth, totalHeight);
    }

    @Override
    public void setEnabled(boolean v) {
        enabled = v;
        repaint();
    }

    public void stateChanged(ChangeEvent e) {
        repaint();
    }

    public void setBGColor(Color bgColor) {
        this.bgColor = bgColor;
    }

    @Override
    public String getToolTipText(MouseEvent event) {
        return
                "<" + getMinimum()
                        + " [" + getLowValue()
                        + ":" + getHighValue()
                        + "] " + getMaximum()
                        + ">";
    }

}
