package org.basex.gui.layout;

import static org.basex.gui.layout.BaseXKeys.*;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Window;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import org.basex.gui.GUIConstants;
import org.basex.gui.GUIConstants.Fill;
import org.basex.gui.dialog.Dialog;

/**
 * Project specific slider implementation.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Christian Gruen
 */
public final class BaseXSlider extends BaseXPanel {
  /** Slider width. */
  private static final double SLIDERW = 20;
  /** Listener. */
  private final Dialog dl;
  /** Minimum slider value. */
  private final int min;
  /** Maximum slider value. */
  private final int max;

  /** Listener. */
  private ActionListener al;
  /** Current slider value. */
  private int curr;
  /** Current slider value. */
  private int oldCurr = -1;
  /** Mouse position for dragging operations. */
  private int mouseX;

  /**
   * Constructor.
   * @param mn min value
   * @param mx max value
   * @param i initial value
   * @param list listener
   * @param w reference to the main window
   */
  public BaseXSlider(final ActionListener list, final int mn, final int mx,
      final int i, final Window w) {
    this(mn, mx, i, w);
    al = list;
  }

  /**
   * Constructor.
   * @param mn min value
   * @param mx max value
   * @param i initial value
   * @param w parent window
   */
  public BaseXSlider(final int mn, final int mx, final int i, final Window w) {
    super(w);
    min = mn;
    max = mx;
    curr = i;
    dl = w instanceof Dialog ? (Dialog) w : null;
    mode(Fill.NONE).setFocusable(true);

    BaseXLayout.setHeight(this, getFont().getSize() + 3);

    addFocusListener(new FocusAdapter() {
      @Override
      public void focusGained(final FocusEvent e) {
        repaint();
      }
      @Override
      public void focusLost(final FocusEvent e) {
        repaint();
      }
    });
    addKeyListener(this);
    addMouseListener(this);
    addMouseMotionListener(this);
  }

  /**
   * Returns the current value.
   * @return current value
   */
  public int value() {
    return curr;
  }

  /**
   * Sets a slider value.
   * @param v new value
   */
  public void value(final int v) {
    curr = v;
    repaint();
  }

  @Override
  public void paintComponent(final Graphics g) {
    super.paintComponent(g);

    final int w = getWidth();
    final int h = getHeight();
    final int hh = h / 2;

    g.setColor(hasFocus() ? Color.white : GUIConstants.LGRAY);
    g.fillRect(0, hh - 2, w, 4);
    g.setColor(Color.black);
    g.drawLine(0, hh - 2, w, hh - 2);
    g.drawLine(0, hh - 2, 0, hh + 2);
    g.setColor(GUIConstants.GRAY);
    g.drawLine(w - 1, hh - 2, w - 1, hh + 2);
    g.drawLine(0, hh + 2, w, hh + 2);

    final double x = (curr - min) * (w - SLIDERW) / (max - min);
    BaseXLayout.drawCell(g, (int) x, (int) (x + SLIDERW), hh - 5, hh + 5,
        oldCurr != -1);
  }

  @Override
  public void mouseMoved(final MouseEvent e) {
    mouseX = e.getX();
  }

  @Override
  public void mousePressed(final MouseEvent e) {
    requestFocusInWindow();
    mouseX = e.getX();
    final double w = getWidth() - SLIDERW;
    final double r = max - min;
    final double x = (curr - min) * w / r;
    if(mouseX < x || mouseX >= x + SLIDERW) curr = (int) (mouseX * r / w + min);
    oldCurr = curr;
    repaint();
  }

  @Override
  public void mouseReleased(final MouseEvent e) {
    oldCurr = -1;
    repaint();
  }

  @Override
  public void mouseDragged(final MouseEvent e) {
    final double prop = (max - min) * (mouseX - e.getX()) /
      (getWidth() - SLIDERW);

    final int old = curr;
    curr = Math.max(min, Math.min(max, (int) (oldCurr - prop)));

    if(curr != old) {
      if(dl != null) dl.action(null);
      else al.actionPerformed(null);
      repaint();
    }
  }

  @Override
  public void keyPressed(final KeyEvent e) {
    final int old = curr;
    if(PREV.is(e) || PREVLINE.is(e)) {
      curr = Math.max(min, curr - 1);
    } else if(NEXT.is(e) || NEXTLINE.is(e)) {
      curr = Math.min(max, curr + 1);
    } else if(NEXTPAGE.is(e)) {
      curr = Math.max(min, curr + 10);
    } else if(PREVPAGE.is(e)) {
      curr = Math.min(max, curr - 10);
    } else if(LINESTART.is(e)) {
      curr = min;
    } else if(LINEEND.is(e)) {
      curr = max;
    }
    if(curr != old) {
      if(dl != null) dl.action(null);
      else al.actionPerformed(null);
      repaint();
    }
  }
}
