// This here is a slider widget
// Written instead of doing real work by Joey Carr, on 6/1/2005

import processing.core.PApplet;
import java.awt.event.MouseEvent;
import java.lang.reflect.Method;

/*
 * The slider widget is composed of two parts: a thumb and a track.
 * The thumb is the draggable bit that marks the current value.  The
 * track is the rectangle that shows how far the item may be dragged.
 *
 * The class must be declared public in order to receive callbacks
 * from the PApplet.
 */
public class Slider
{

  // -------------------------------------------------------------
  // Members
  // -------------------------------------------------------------

  /*
   * The applet that we're going to render ourself on.
   */
  private PApplet myParent;

  /*
   * Our X and Y position mark our upper left corner.  See width and
   * height to determine our other extents.
   */
  private int myPositionX, myPositionY;

  /*
   * The  width of the track.  The value of the slider will be some
   * value between zero and width... it's up to the user to translate
   * these into a meaningfull number.  Note that the total width of
   * the widget will be at most myWidth + myHeight/2;
   */
  private int myWidth;
  
  /*
   * The height of the widget.  The thumb has the largest vertical
   * extent of the slider, so this translates to the height of the 
   * thumb.
   */
  private int myHeight;

  /*
   * The colors we'll use to render the components of the slider.
   */
  private int myThumbColor, myTrackColor;

  /*
   * The pixel position of the thumb.  We store this and translate
   * to and from our value when the user interacts with it.
   * This keeps us from doing calculations while handling mouse
   * events.
   */
  private int myThumbPosition;

  /*
   * This boolean indicates if we're currently being dragged by the
   * user's mouse.
   */
  private boolean myDragging;

  /*
   * This method refers to the sliderEvent method of the parent applet.
   */
  private Method myCallback;

  // -------------------------------------------------------------
  // Constructors
  // -------------------------------------------------------------

  /*
   * We must be constructed with a reference to our parent PApplet.
   * We'll use this reference to perform all of our drawing routines.
   */
  public Slider( PApplet aParent )
  {
    myParent = aParent;
    // registers us to draw after the applet's draw method runs
    // (see our own draw method).
    myParent.registerDraw(this);
    // registers us to get AWT mouse events, this should allow us
    // to implement drag-and-drop for our slider
    myParent.registerMouseEvent(this);
    
    registerParentCallback();
    
    // These are all of our defaults:
    setWidth(100);
    setHeight(10);
    thumbColor(myParent.color(187, 187, 170));
    trackColor(myParent.color(102, 102, 90));
    setPositionX(10);
    setPositionY(10);
  }

  // -------------------------------------------------------------
  // Getters/Setters
  // -------------------------------------------------------------

  public int getWidth()
  {
    return myWidth;
  }

  /*
   * Sets the width of the track in pixels.  Note that since the 
   * thumb may extend past the end of the track, the total width
   * of the widget is width + height/2.
   */
  public void setWidth(int aWidth)
  {
    myWidth = aWidth;
  }
  
  public void setWidth(float aWidth)
  {
    setWidth(PApplet.toInt(aWidth));
  }
  
  public void setWidth(long aWidth)
  {
    setWidth(PApplet.toInt(aWidth));
  }

  public void setWidth(byte aWidth)
  {
    setWidth(PApplet.toInt(aWidth));
  }
  
  public int getHeight()
  {
    return myHeight;
  }
  
  /*
   * Sets the height of the widget in pixels.  The thumb has the
   * largest vertical extent of the parts of the slider, so this
   * translates to the height of the thumb.
   */
  public void setHeight(int aHeight)
  {
    myHeight = aHeight;
  }
  
  public void setHeight(float aHeight)
  {
    setHeight(PApplet.toInt(aHeight));
  }
  
  public void setHeight(long aHeight)
  {
    setHeight(PApplet.toInt(aHeight));
  }
  
  public void setHeight(byte aHeight)
  {
    setHeight(PApplet.toInt(aHeight));
  }

  public float getValue()
  {
    return PApplet.toFloat(myThumbPosition - myPositionX)/myWidth;
  }
  
  public void setValue(float aValue)
  {
    aValue = PApplet.max(aValue, 1);
    myThumbPosition = PApplet.toInt(myPositionX + aValue*myWidth);
  }

  public void setValue(int aValue)
  {
    setValue(PApplet.toFloat(aValue));
  }

  public void setValue(long aValue)
  {
    setValue(new Long(aValue).floatValue());
  }

  public void setValue(byte aValue)
  {
    setValue(PApplet.toFloat(aValue));
  }

  public int getPositionX()
  {
    return myPositionX;
  }
  
  /*
   * Sets the pixel position of the upper left hand corner of the slider.
   */
  public void setPositionX(int aPosition)
  {
    myPositionX = aPosition;
    myThumbPosition = myPositionX;
  }
  
  public void setPositionX(float aPosition)
  {
    setPositionX(PApplet.toInt(aPosition));
  }

  public void setPositionX(long aPosition)
  {
    setPositionX(PApplet.toInt(aPosition));
  }
  
  public void setPositionX(byte aPosition)
  {
    setPositionX(PApplet.toInt(aPosition));
  }

  public int getPositionY()
  {
    return myPositionY;
  }
  
  /*
   * Sets the pixel position of the upper left hand corner of the slider.
   */
  public void setPositionY(int aPosition)
  {
    myPositionY = aPosition;
  }
  
  public void setPositionY(float aPosition)
  {
    setPositionY(PApplet.toInt(aPosition));
  }

  public void setPositionY(long aPosition)
  {
    setPositionY(PApplet.toInt(aPosition));
  }
  
  public void setPositionY(byte aPosition)
  {
    setPositionY(PApplet.toInt(aPosition));
  }

  /*
   * The color used to paint the thumb.
   * Call this as slider.thumbColor(color(...));
   */
  public void thumbColor( int aColor )
  {
    myThumbColor = aColor;
  }
  
  /*
   * The color used to paint the track.
   * Call this as slider.trackColor(color(...));
   */
  public void trackColor( int aColor )
  {
    myTrackColor = aColor;
  }

  // -------------------------------------------------------------
  // Methods
  // -------------------------------------------------------------

  public void draw()
  {
    //myParent.println("Slider.draw: called to draw");

    // First draw the track.
    myParent.rectMode(PApplet.CORNER);
    myParent.noFill();
    myParent.stroke(myTrackColor);
    // TODO: pre-calculate these and cache them.
    myParent.rect(myPositionX,
                  myPositionY + myHeight/3,
                  myWidth,
                  myHeight/3 );
    myParent.noStroke();
    myParent.fill(myThumbColor);
    // The thumb should be square, so we use our height for its width
    myParent.rect(myThumbPosition - myHeight/2,
                  myPositionY,
                  myHeight,
                  myHeight );
  }

  public void mouseEvent(MouseEvent evt)
  {
    //myParent.println("Slider.mouseEvent: got a mouse event!");
    if( evt.getID() == MouseEvent.MOUSE_PRESSED )
    {
      if( hit() )
      {
        myDragging = true;
      }
    }
    else if( evt.getID() == MouseEvent.MOUSE_RELEASED )
    {
      myDragging = false;
      invokeCallback();
    }
    if( myDragging )
    {
       myThumbPosition = PApplet.min( myPositionX + myWidth,
                                      PApplet.max( myParent.mouseX,
                                                   myPositionX ));
    }
  }
  
  private boolean hit()
  {
    return ( myParent.mouseX > myPositionX - myHeight/2 &&
             myParent.mouseX < myPositionX + myHeight/2 + myWidth &&
             myParent.mouseY > myPositionY &&
             myParent.mouseY < myPositionY + myHeight );
  }

  /*
   * Attempts to find the "sliderEvent" method of the parent PApplet.
   * This method is assigned to the myCallback member.  If no method
   * is found, then the myCallback member is left null.
   */
  private void registerParentCallback()
  {
    try
    {
      myCallback = myParent.getClass().getMethod("sliderEvent",
                                             new Class[]{ Slider.class } );
    }
    catch( NoSuchMethodException excp )
    {
      // simply dump this exception
    }
    catch( SecurityException excp )
    {
      myParent.println("A security exception occurred, note that " +
                       "sliderEvent(Slider) must be public.");
    }
  }
  
  /*
   *
   */
  private void invokeCallback()
  {
    if( myCallback != null )
    {
      try
      {
        myCallback.invoke( myParent, new Object[]{ this } );
      }
      catch( IllegalAccessException excp )
      {
        myParent.println("An illegal access exception occurred, note " +
                         "that sliderEvent(Slider) must be public.");
        // Set this to null so we do not repeat this error.
        myCallback = null;
      }
      catch( IllegalArgumentException excp )
      {
        myParent.println("This is odd indeed, it appears that I " +
                         "haven't got the right method");
        excp.printStackTrace(System.err);
        // Again, set this to null so we don't repeat this constantly.
        myCallback = null;
      }
      catch( java.lang.reflect.InvocationTargetException excp )
      {
        myParent.println("An exception occurred in the sliderEvent method.");
        throw new RuntimeException(excp);
      }
    }
  }
}
