package controlP5;

/**
 * controlP5 is a processing gui library.
 * 
 * 2006-2012 by Andreas Schlegel
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA
 * 
 * @author Andreas Schlegel (http://www.sojamo.de)
 * @modified 07/30/2015
 * @version 2.2.5
 * 
 */

import java.util.ArrayList;

import processing.core.PApplet;
import processing.core.PGraphics;

/**
 * A slider is either used horizontally or vertically. when adding a slider to controlP5, the width
 * is compared against the height. if the width is bigger, you get a horizontal slider, is the
 * height bigger, you get a vertical slider. a slider can have a fixed slider handle (one end of the
 * slider is fixed to the left or bottom side of the controller), or a flexible slider handle (a
 * handle you can drag).
 * 
 * 
 * @example controllers/ControlP5slider
 */
public class Slider extends Controller< Slider > {

	public final static int FIX = 1;
	public final static int FLEXIBLE = 0;
	protected int _mySliderMode = FIX;
	protected float _myValuePosition;
	protected int _myHandleSize = 0;
	protected int _myDefaultHandleSize = 10;
	protected int triggerId = PRESSED;
	protected ArrayList< TickMark > _myTickMarks = new ArrayList< TickMark >( );
	protected boolean isShowTickMarks;
	protected boolean isSnapToTickMarks;
	protected static int autoWidth = 99;
	protected static int autoHeight = 9;
	protected float scrollSensitivity = 0.1f;
	protected int _myColorTickMark = 0xffffffff;
	private SliderView _myView;
	protected float _myMinReal = 0;
	protected float _myMaxReal = 1;
	protected float _myInternalValue = 0;

	/**
	 * Convenience constructor to extend Slider.
	 * 
	 * @example use/ControlP5extendController
	 * @param theControlP5
	 * @param theName
	 */
	public Slider( ControlP5 theControlP5 , String theName ) {
		this( theControlP5 , theControlP5.getDefaultTab( ) , theName , 0 , 100 , 0 , 0 , 0 , autoWidth , autoHeight );
		theControlP5.register( theControlP5.papplet , theName , this );
	}

	public Slider( ControlP5 theControlP5 , ControllerGroup< ? > theParent , String theName , float theMin , float theMax , float theDefaultValue , int theX , int theY , int theWidth , int theHeight ) {
		super( theControlP5 , theParent , theName , theX , theY , theWidth , theHeight );

		_myMin = 0;
		_myMax = 1;

		// with _myMinReal and _myMaxReal the range of values can now range
		// from big to small (e.g. 255 to 0) as well as from small to big (e.g. 0 to 255)
		_myMinReal = theMin;
		_myMaxReal = theMax;

		_myValue = PApplet.map( theDefaultValue , _myMinReal , _myMaxReal , 0 , 1 );

		_myCaptionLabel = new Label( cp5 , theName ).setColor( color.getCaptionLabel( ) );
		_myValueLabel = new Label( cp5 , "" + getValue( ) ).setColor( color.getValueLabel( ) );
		setSliderMode( FIX );

	}

	@ControlP5.Invisible @Override public void init( ) {
		// need to override init here since _myValue will only be a
		// normalized value here but _myDefaultValue needs to be absolute.
		// by normalizing _myValue the range of values can be from 'big-to-small'
		// as well as from 'small-to-big'
		// in order not to break anything, init() will be overwritten here.

		_myDefaultValue = getValue( );
		cp5.getControlBroadcaster( ).plug( cp5.papplet , this , _myName );
		initControllerValue( );
		isInit = cp5.isAutoInitialization;
		setValue( _myDefaultValue );
		isInit = true;
		updateDisplayMode( DEFAULT );

	}

	/**
	 * use the slider mode to set the mode of the slider bar, which can be Slider.FLEXIBLE or
	 * Slider.FIX
	 * 
	 * @param theMode
	 *            int
	 */
	public Slider setSliderMode( int theMode ) {
		_myView = ( getWidth( ) > getHeight( ) ) ? new SliderViewH( ) : new SliderViewV( );
		_myControllerView = ( getWidth( ) > getHeight( ) ) ? new SliderViewH( ) : new SliderViewV( );
		_mySliderMode = theMode;
		if ( _mySliderMode == FLEXIBLE ) {
			_myHandleSize = ( _myDefaultHandleSize >= getHeight( ) / 2 ) ? _myDefaultHandleSize / 2 : _myDefaultHandleSize;
		} else {
			_myHandleSize = 0;
		}
		_myView.updateUnit( );
		setValue( PApplet.map( _myValue , 0 , 1 , _myMinReal , _myMaxReal ) );
		return this;
	}

	public int getSliderMode( ) {
		return _mySliderMode;
	}

	/**
	 * sets the size of the Slider handle, by default it is set to either the width or height of the
	 * slider.
	 * 
	 * @param theSize
	 */
	public Slider setHandleSize( int theSize ) {
		_myDefaultHandleSize = theSize;
		setSliderMode( _mySliderMode );
		return this;
	}

	public int getHandleSize( ) {
		return _myHandleSize;
	}

	/**
	 * @see ControllerInterface.updateInternalEvents
	 * 
	 */
	@ControlP5.Invisible public Slider updateInternalEvents( PApplet theApplet ) {
		if ( isVisible ) {
			if ( isMousePressed && !cp5.isAltDown( ) ) {
				_myView.updateInternalEvents( theApplet );
			}
		}
		return this;
	}

	/**
	 * the trigger event is set to Slider.PRESSED by default but can be changed to Slider.RELEASE so
	 * that events are triggered when the slider is released.
	 * 
	 * @param theEventID
	 */
	public Slider setTriggerEvent( int theEventID ) {
		triggerId = theEventID;
		return this;
	}

	/**
	 * returns the current trigger event which is either Slider.PRESSED or Slider.RELEASE
	 * 
	 * @return int
	 */
	public int getTriggerEvent( ) {
		return triggerId;
	}

	@Override protected void mouseReleasedOutside( ) {
		mouseReleased( );
	}

	@Override protected void mouseReleased( ) {
		if ( triggerId == RELEASE ) {
			_myView.update( );
			broadcast( FLOAT );
		}
	}

	protected Slider snapValue( float theValue ) {
		if ( isSnapToTickMarks ) {
			_myValuePosition = ( ( _myValue - _myMin ) / _myUnit );
			_myView.setSnapValue( );
		}
		return this;
	}

	public float getValuePosition( ) {
		return _myValuePosition;
	}

	/**
	 * set the value of the slider.
	 * 
	 * @param theValue
	 *            float
	 */
	@Override public Slider setValue( float theValue ) {
		if ( isMousePressed && theValue == getValue( ) ) {
			return this;
		}
		_myInternalValue = theValue;
		_myValue = PApplet.map( theValue , _myMinReal , _myMaxReal , 0 , 1 );
		snapValue( _myValue );
		_myValue = ( _myValue <= _myMin ) ? _myMin : _myValue;
		_myValue = ( _myValue >= _myMax ) ? _myMax : _myValue;
		_myValuePosition = ( ( _myValue - _myMin ) / _myUnit );
		_myValueLabel.set( adjustValue( getValue( ) ) );
		if ( triggerId == PRESSED ) {
			broadcast( FLOAT );
		}
		return this;
	}

	@Override public float getValue( ) {
		return PApplet.map( _myValue , 0 , 1 , _myMinReal , _myMaxReal );
	}

	/**
	 * assigns a random value to the slider.
	 */
	public Slider shuffle( ) {
		float r = ( float ) Math.random( );
		setValue( PApplet.map( r , 0 , 1 , _myMinReal , _myMaxReal ) );
		return this;
	}

	/**
	 * sets the sensitivity for the scroll behavior when using the mouse wheel or the scroll
	 * function of a multi-touch track pad. The smaller the value (closer to 0) the higher the
	 * sensitivity. by default this value is set to 0.1
	 * 
	 * @param theValue
	 * @return Slider
	 */
	public Slider setScrollSensitivity( float theValue ) {
		scrollSensitivity = theValue;
		return this;
	}

	/**
	 * changes the value of the slider when hovering and using the mouse wheel or the scroll
	 * function of a multi-touch track pad.
	 * 
	 * @param theRotationValue
	 * @return Slider
	 */
	@ControlP5.Invisible public Slider scrolled( int theRotationValue ) {
		if ( isVisible ) {
			float f = _myValue;
			float steps = isSnapToTickMarks ? ( 1.0f / getNumberOfTickMarks( ) ) : scrollSensitivity * 0.1f;
			f += ( _myMax - _myMin ) * ( -theRotationValue * steps );
			setValue( PApplet.map( f , 0 , 1 , _myMinReal , _myMaxReal ) );
			if ( triggerId == RELEASE ) {
				broadcast( FLOAT );
			}
		}
		return this;
	}

	@Override public Slider update( ) {
		return setValue( PApplet.map( _myValue , 0 , 1 , _myMinReal , _myMaxReal ) );
	}

	/**
	 * sets the minimum value of the slider.
	 * 
	 * @param theValue
	 *            float
	 */
	@Override public Slider setMin( float theValue ) {
		float f = getValue( );
		_myMinReal = theValue;
		_myValue = PApplet.map( f , _myMinReal , _myMaxReal , 0 , 1 );
		setSliderMode( _mySliderMode );
		return this;
	}

	/**
	 * set the maximum value of the slider.
	 * 
	 * @param theValue
	 *            float
	 */
	@Override public Slider setMax( float theValue ) {
		float f = getValue( );
		_myMaxReal = theValue;
		_myValue = PApplet.map( f , _myMinReal , _myMaxReal , 0 , 1 );
		setSliderMode( _mySliderMode );
		return this;
	}

	@Override public float getMin( ) {
		return _myMinReal;
	}

	@Override public float getMax( ) {
		return _myMaxReal;
	}

	public Slider setRange( float theMin , float theMax ) {
		float f = _myInternalValue;
		_myMinReal = theMin;
		_myMaxReal = theMax;
		_myValue = PApplet.map( f , _myMinReal , _myMaxReal , 0 , 1 );
		setSliderMode( _mySliderMode );
		return this;
	}

	/**
	 * set the width of the slider.
	 * 
	 * @param theValue
	 *            int
	 */
	@Override public Slider setWidth( int theValue ) {
		super.setWidth( theValue );
		setSliderMode( _mySliderMode );
		return this;
	}

	/**
	 * set the height of the slider.
	 * 
	 * @param theValue
	 *            int
	 */
	@Override public Slider setHeight( int theValue ) {
		super.setHeight( theValue );
		setSliderMode( _mySliderMode );
		return this;
	}

	@Override public Slider setSize( int theWidth , int theHeight ) {
		super.setWidth( theWidth );
		setHeight( theHeight );
		_myView = ( getWidth( ) > getHeight( ) ) ? new SliderViewH( ) : new SliderViewV( );
		return this;
	}

	/* TODO new implementations follow: http://www.ibm.com/developerworks/java/library/j-dynui/ take
	 * interface builder as reference */
	protected Slider setTickMarks( ) {
		return this;
	}

	/**
	 * sets the number of tickmarks for a slider, by default tick marks are turned off.
	 * 
	 * @param theNumber
	 */
	public Slider setNumberOfTickMarks( int theNumber ) {
		_myTickMarks.clear( );
		if ( theNumber > 0 ) {
			for ( int i = 0 ; i < theNumber ; i++ ) {
				_myTickMarks.add( new TickMark( this ) );
			}
			showTickMarks( true );
			snapToTickMarks( true );
			setHandleSize( 20 );
		} else {
			showTickMarks( false );
			snapToTickMarks( false );
			setHandleSize( _myDefaultHandleSize );
		}
		setValue( PApplet.map( _myValue , 0 , 1 , _myMinReal , _myMaxReal ) );
		return this;
	}

	/**
	 * returns the amount of tickmarks available for a slider
	 * 
	 * @return int
	 */
	public int getNumberOfTickMarks( ) {
		return _myTickMarks.size( );
	}

	/**
	 * shows or hides tickmarks for a slider
	 * 
	 * @param theFlag
	 * @return Slider
	 */
	public Slider showTickMarks( boolean theFlag ) {
		isShowTickMarks = theFlag;
		return this;
	}

	/**
	 * enables or disables snap to tick marks.
	 * 
	 * @param theFlag
	 * @return Slider
	 */
	public Slider snapToTickMarks( boolean theFlag ) {
		isSnapToTickMarks = theFlag;
		return this;
	}

	/**
	 * returns an instance of a tickmark by index.
	 * 
	 * @see TickMark
	 * @param theIndex
	 * @return
	 */
	public TickMark getTickMark( int theIndex ) {
		if ( theIndex >= 0 && theIndex < _myTickMarks.size( ) ) {
			return _myTickMarks.get( theIndex );
		} else {
			return null;
		}
	}

	/**
	 * returns an ArrayList of available tick marks for a slider.
	 * 
	 * @return ArrayList<TickMark>
	 */
	public ArrayList< TickMark > getTickMarks( ) {
		return _myTickMarks;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override @ControlP5.Invisible public Slider linebreak( ) {
		cp5.linebreak( this , true , autoWidth , autoHeight , autoSpacing );
		return this;
	}

	/**
	 * sets the color of tick marks if enabled. by default the color is set to white.
	 * 
	 * @param theColor
	 * @return Slider
	 */
	public Slider setColorTickMark( int theColor ) {
		_myColorTickMark = theColor;
		return this;
	}

	public int getDirection( ) {
		return ( _myView instanceof SliderViewH ) ? HORIZONTAL : VERTICAL;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override @ControlP5.Invisible public Slider updateDisplayMode( int theMode ) {
		_myDisplayMode = theMode;
		switch ( theMode ) {
		case ( DEFAULT ):
			_myControllerView = ( getWidth( ) > getHeight( ) ) ? new SliderViewH( ) : new SliderViewV( );
			break;
		case ( IMAGE ):
			// TODO
			break;
		case ( SPRITE ):
			// TODO
			break;
		case ( CUSTOM ):
		default:
			break;
		}
		return this;
	}

	private abstract class SliderView implements ControllerView< Slider > {

		abstract void updateInternalEvents( PApplet theApplet );

		abstract void update( );

		abstract void updateUnit( );

		abstract void setSnapValue( );

	}

	private class SliderViewV extends SliderView {

		SliderViewV( ) {
			_myCaptionLabel.align( LEFT , BOTTOM_OUTSIDE ).setPadding( 0 , Label.paddingY );
			_myValueLabel.set( "" + adjustValue( getValue( ) ) ).align( RIGHT_OUTSIDE , TOP );
		}

		void setSnapValue( ) {
			float n = PApplet.round( PApplet.map( _myValuePosition , 0 , getHeight( ) , 0 , _myTickMarks.size( ) - 1 ) );
			_myValue = PApplet.map( n , 0 , _myTickMarks.size( ) - 1 , _myMin , _myMax );
		}

		void updateUnit( ) {
			_myUnit = ( _myMax - _myMin ) / ( getHeight( ) - _myHandleSize );
		}

		void update( ) {
			float f = _myMin + ( - ( _myControlWindow.mouseY - ( y( _myParent.getAbsolutePosition( ) ) + y( position ) ) - getHeight( ) ) ) * _myUnit;
			setValue( PApplet.map( f , 0 , 1 , _myMinReal , _myMaxReal ) );
		}

		void updateInternalEvents( PApplet theApplet ) {
			float f = _myMin + ( - ( _myControlWindow.mouseY - ( y( _myParent.getAbsolutePosition( ) ) + y( position ) ) - getHeight( ) ) ) * _myUnit;
			setValue( PApplet.map( f , 0 , 1 , _myMinReal , _myMaxReal ) );
		}

		public void display( PGraphics theGraphics , Slider theController ) {
			theGraphics.fill( getColor( ).getBackground( ) );
			theGraphics.noStroke( );
			if ( ( getColor( ).getBackground( ) >> 24 & 0xff ) > 0 ) {
				theGraphics.rect( 0 , 0 , getWidth( ) , getHeight( ) );
			}
			theGraphics.fill( getIsInside( ) ? getColor( ).getActive( ) : getColor( ).getForeground( ) );
			if ( getSliderMode( ) == FIX ) {
				theGraphics.rect( 0 , getHeight( ) , getWidth( ) , -getValuePosition( ) );
			} else {
				if ( isShowTickMarks ) {
					theGraphics.triangle( getWidth( ) , getHeight( ) - getValuePosition( ) , getWidth( ) , getHeight( ) - getValuePosition( ) - getHandleSize( ) , 0 , getHeight( ) - getValuePosition( ) - getHandleSize( ) / 2 );
				} else {
					theGraphics.rect( 0 , getHeight( ) - getValuePosition( ) - getHandleSize( ) , getWidth( ) , getHandleSize( ) );
				}
			}

			if ( isLabelVisible ) {
				getCaptionLabel( ).draw( theGraphics , 0 , 0 , theController );
				theGraphics.pushMatrix( );
				theGraphics.translate( 0 , ( int ) PApplet.map( _myValue , _myMax , _myMin , 0 , getHeight( ) - _myValueLabel.getHeight( ) ) );
				getValueLabel( ).draw( theGraphics , 0 , 0 , theController );
				theGraphics.popMatrix( );
			}

			if ( isShowTickMarks ) {
				theGraphics.pushMatrix( );
				theGraphics.pushStyle( );
				theGraphics.translate( -4 , ( getSliderMode( ) == FIX ) ? 0 : getHandleSize( ) / 2 );
				theGraphics.fill( _myColorTickMark );
				float x = ( getHeight( ) - ( ( getSliderMode( ) == FIX ) ? 0 : getHandleSize( ) ) ) / ( getTickMarks( ).size( ) - 1 );
				for ( TickMark tm : getTickMarks( ) ) {
					tm.draw( theGraphics , getDirection( ) );
					theGraphics.translate( 0 , x );
				}
				theGraphics.popStyle( );
				theGraphics.popMatrix( );
			}
		}
	}

	private class SliderViewH extends SliderView {

		SliderViewH( ) {
			_myCaptionLabel.align( RIGHT_OUTSIDE , CENTER );
			_myValueLabel.set( "" + adjustValue( getValue( ) ) ).align( LEFT , CENTER );
		}

		void setSnapValue( ) {
			float n = PApplet.round( PApplet.map( _myValuePosition , 0 , getWidth( ) , 0 , _myTickMarks.size( ) - 1 ) );
			_myValue = PApplet.map( n , 0 , _myTickMarks.size( ) - 1 , _myMin , _myMax );
		}

		void updateUnit( ) {
			_myUnit = ( _myMax - _myMin ) / ( getWidth( ) - _myHandleSize );
		}

		void update( ) {
			float f = _myMin + ( _myControlWindow.mouseX - ( x( _myParent.getAbsolutePosition( ) ) + x( position ) ) ) * _myUnit;
			setValue( PApplet.map( f , 0 , 1 , _myMinReal , _myMaxReal ) );
		}

		void updateInternalEvents( PApplet theApplet ) {
			float f = _myMin + ( _myControlWindow.mouseX - ( x( _myParent.getAbsolutePosition( ) ) + x( position ) ) ) * _myUnit;
			setValue( PApplet.map( f , 0 , 1 , _myMinReal , _myMaxReal ) );
		}

		public void display( PGraphics theGraphics , Slider theController ) {
			theGraphics.fill( getColor( ).getBackground( ) );
			theGraphics.noStroke( );
			if ( ( getColor( ).getBackground( ) >> 24 & 0xff ) > 0 ) {
				theGraphics.rect( 0 , 0 , getWidth( ) , getHeight( ) );
			}
			theGraphics.fill( getIsInside( ) ? getColor( ).getActive( ) : getColor( ).getForeground( ) );
			if ( getSliderMode( ) == FIX ) {
				theGraphics.rect( 0 , 0 , getValuePosition( ) , getHeight( ) );
			} else {
				if ( isShowTickMarks ) {
					theGraphics.triangle( getValuePosition( ) , 0 , getValuePosition( ) + getHandleSize( ) , 0 , getValuePosition( ) + _myHandleSize / 2 , getHeight( ) );
				} else {
					theGraphics.rect( getValuePosition( ) , 0 , getHandleSize( ) , getHeight( ) );
				}

			}
			theGraphics.fill( 255 );

			if ( isLabelVisible ) {
				getValueLabel( ).draw( theGraphics , 0 , 0 , theController );
				getCaptionLabel( ).draw( theGraphics , 0 , 0 , theController );
			}

			if ( isShowTickMarks ) {
				theGraphics.pushMatrix( );
				//				theGraphics.pushStyle( );
				theGraphics.translate( ( getSliderMode( ) == FIX ) ? 0 : getHandleSize( ) / 2 , getHeight( ) );
				theGraphics.fill( _myColorTickMark );
				theGraphics.noStroke( );
				float x = ( getWidth( ) - ( ( getSliderMode( ) == FIX ) ? 0 : getHandleSize( ) ) ) / ( getTickMarks( ).size( ) - 1 );
				for ( TickMark tm : getTickMarks( ) ) {
					tm.draw( theGraphics , getDirection( ) );
					theGraphics.translate( x , 0 );
				}
				//				theGraphics.popStyle( );
				theGraphics.popMatrix( );
			}
		}
	}

	@Deprecated public void setSliderBarSize( int theSize ) {
		_myDefaultHandleSize = theSize;
		setSliderMode( _mySliderMode );
	}

	/**
	 * @see controlP5.Slider#setScrollSensitivity(float)
	 * 
	 * @param theValue
	 * @return Slider
	 */
	@Deprecated public Slider setSensitivity( float theValue ) {
		return setScrollSensitivity( theValue );
	}

}
