package controlP5;

/**
 * controlP5 is a processing gui library.
 * 
 * 2006-2015 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 ##date##
 * @version ##version##
 * 
 */

import java.util.ArrayList;
import java.util.logging.Level;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PGraphics;
import controlP5.ControlP5.Invisible;

/**
 * A range slider works just like a slider but can be adjusted on both ends.
 * 
 * @see Slider
 * @example controllers/ControlP5range
 * @nosuperclasses Controller Controller
 */
public class Range extends Controller< Range > {

	/* TODO if range value is int, value labels do initialize as floats. first click makes them
	 * display as ints without decimal point */
	protected static final int HORIZONTAL = 0;
	protected static final int VERTICAL = 1;
	protected int _myDirection;
	protected float _myValuePosition;
	protected boolean isDragging;
	protected boolean isDraggable = true;
	protected boolean isFirstClick;
	protected Label _myHighValueLabel;
	protected float _myValueRange;
	protected boolean isMinHandle;
	protected boolean isMaxHandle;
	protected boolean isMoveHandle;
	protected float distanceHandle;
	protected int handleSize = 10;
	protected int minHandle = 0;
	protected int maxHandle = 0;
	protected int mr = 0;
	protected final ArrayList< TickMark > _myTickMarks = new ArrayList< TickMark >( );
	protected boolean isShowTickMarks;
	protected boolean isSnapToTickMarks;
	public static int autoWidth = 99;
	public static int autoHeight = 9;
	public static float[] autoSpacing = new float[]{ 0 , 5 , 0 };
	public int alignValueLabel = CENTER;
	protected int _myColorTickMark = 0xffffffff;
	private int mode = -1;

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

	/**
	 * 
	 * @param theControlP5 ControlP5
	 * @param theParent ControllerGroup
	 * @param theName String
	 * @param theMin float
	 * @param theMax float
	 * @param theDefaultValue float
	 * @param theX int
	 * @param theY int
	 * @param theWidth int
	 * @param theHeight int
	 */
	@ControlP5.Invisible public Range( ControlP5 theControlP5 , ControllerGroup< ? > theParent , String theName , float theMin , float theMax , float theDefaultMinValue , float theDefaultMaxValue , int theX , int theY , int theWidth , int theHeight ) {
		super( theControlP5 , theParent , theName , theX , theY , theWidth , theHeight );

		_myArrayValue = new float[] { theDefaultMinValue , theDefaultMaxValue };

		_myMin = theMin;
		_myMax = theMax;
		_myValueRange = _myMax - _myMin;

		minHandle = ( int ) PApplet.map( theDefaultMinValue , _myMin , _myMax , handleSize , getWidth( ) - handleSize );
		maxHandle = ( int ) PApplet.map( theDefaultMaxValue , _myMin , _myMax , handleSize , getWidth( ) - handleSize );
		mr = maxHandle - minHandle;

		_myCaptionLabel = new Label( cp5 , theName ).setColor( color.getCaptionLabel( ) ).align( RIGHT_OUTSIDE , CENTER );
		_myValueLabel = new Label( cp5 , "" + adjustValue( _myMin ) ).setColor( color.getValueLabel( ) ).set( "" + adjustValue( theDefaultMinValue ) ).align( LEFT , CENTER );
		_myHighValueLabel = new Label( cp5 , adjustValue( _myMax ) ).setColor( color.getValueLabel( ) ).set( "" + adjustValue( theDefaultMaxValue ) ).align( RIGHT , CENTER );

		_myValue = theDefaultMinValue;

		_myDirection = HORIZONTAL;

		update( );

	}

	@Override public Range setColorValueLabel( int theColor ) {
		_myValueLabel.setColor( theColor );
		_myHighValueLabel.setColor( theColor );
		return this;
	}

	@Override public Range setColorCaptionLabel( int theColor ) {
		_myCaptionLabel.setColor( theColor );
		return this;
	}

	public Range setHighValueLabel( final String theLabel ) {
		_myHighValueLabel.set( theLabel );
		return this;
	}

	public Range setLowValueLabel( final String theLabel ) {
		_myValueLabel.set( theLabel );
		return this;
	}

	@ControlP5.Invisible public Range setSliderMode( int theMode ) {
		return this;
	}

	public Range setHandleSize( int theSize ) {
		handleSize = theSize;
		setLowValue( _myArrayValue[ 0 ] , false );
		setHighValue( _myArrayValue[ 1 ] , false );
		mr = maxHandle - minHandle;
		return this;
	}

	@ControlP5.Invisible public Range updateInternalEvents( PApplet theApplet ) {
		if ( isVisible ) {
			int c = _myControlWindow.mouseX - _myControlWindow.pmouseX;
			if ( c == 0 ) {
				return this;
			}
			if ( isMousePressed && !cp5.isAltDown( ) ) {
				switch ( mode ) {
				case ( LEFT ):
					minHandle = PApplet.max( handleSize , PApplet.min( maxHandle , minHandle + c ) );
					break;
				case ( RIGHT ):
					maxHandle = PApplet.max( minHandle , PApplet.min( getWidth( ) - handleSize , maxHandle + c ) );
					break;
				case ( CENTER ):
					minHandle = PApplet.max( handleSize , PApplet.min( getWidth( ) - mr - handleSize , minHandle + c ) );
					maxHandle = PApplet.max( minHandle , PApplet.min( getWidth( ) - handleSize , minHandle + mr ) );
					break;
				}
				update( );
			}
		}
		return this;
	}

	@Override @Invisible public void mousePressed( ) {

		final float posX = x( _myParent.getAbsolutePosition( ) ) + x( position );
		final float posY = y( _myParent.getAbsolutePosition( ) ) + y( position );

		if ( _myControlWindow.mouseY < posY || _myControlWindow.mouseY > posY + getHeight( ) ) {
			mode = -1;
			isMinHandle = isMaxHandle = false;
			return;
		}

		int x0 = ( int ) ( posX + minHandle );
		int x1 = ( int ) ( posX + maxHandle );

		if ( _myControlWindow.mouseX >= x0 - handleSize && _myControlWindow.mouseX < x0 ) {
			mode = LEFT;
			isMinHandle = true;
		} else if ( _myControlWindow.mouseX >= x1 && _myControlWindow.mouseX < x1 + handleSize ) {
			mode = RIGHT;
			isMaxHandle = true;
		} else if ( _myControlWindow.mouseX > x0 && _myControlWindow.mouseX < x1 && isDraggable ) {
			mode = CENTER;
		}
	}

	/**
	 * set the value of the range-slider. to set the low and high value, use setLowValue and
	 * setHighValue or setRangeValues
	 * 
	 * @see #setLowValue(float)
	 * @see #setHighValue(float)
	 * @see #setRangeValues(float, float)
	 * 
	 * @param theValue float
	 * @return Range
	 */
	@Override @ControlP5.Invisible public Range setValue( float theValue ) {
		_myValue = theValue;
		broadcast( ARRAY );
		return this;
	}

	/**
	 * @exclude
	 */
	@Override @ControlP5.Invisible public Range update( ) {
		_myArrayValue[ 0 ] = PApplet.map( minHandle , handleSize , getWidth( ) - handleSize , _myMin , _myMax );
		_myArrayValue[ 1 ] = PApplet.map( maxHandle , handleSize , getWidth( ) - handleSize , _myMin , _myMax );
		mr = maxHandle - minHandle;
		_myHighValueLabel.set( adjustValue( _myArrayValue[ 1 ] ) );
		_myValueLabel.set( adjustValue( _myArrayValue[ 0 ] ) );
		return setValue( _myValue );
	}

	@ControlP5.Invisible public Range setDraggable( boolean theFlag ) {
		isDraggable = theFlag;
		isDragging = ( theFlag == false ) ? false : isDragging;
		return this;
	}

	public float[] getArrayValue( ) {
		return _myArrayValue;
	}

	@Override public Range setArrayValue( float[] theArray ) {
		setLowValue( theArray[ 0 ] , false );
		setHighValue( theArray[ 1 ] , false );
		return update( );
	}

	@Override public Range setMin( float theValue ) {
		_myMin = theValue;
		_myValueRange = _myMax - _myMin;
		return setLowValue( _myArrayValue[ 0 ] );
	}

	@Override public Range setMax( float theValue ) {
		_myMax = theValue;
		_myValueRange = _myMax - _myMin;
		return setHighValue( _myArrayValue[ 1 ] );
	}

	public float getLowValue( ) {
		return _myArrayValue[ 0 ];
	}

	public float getHighValue( ) {
		return _myArrayValue[ 1 ];
	}

	@Override public Range setWidth( int theValue ) {
		super.setWidth( theValue );
		return this;
	}

	@Override public Range setHeight( int theValue ) {
		super.setHeight( theValue );
		return this;
	}

	@Override @ControlP5.Invisible public void mouseReleased( ) {
		isDragging = isMinHandle = isMaxHandle = isMoveHandle = false;
		mode = -1;
	}

	@Override @ControlP5.Invisible public void mouseReleasedOutside( ) {
		mouseReleased( );
	}

	@Override @ControlP5.Invisible public void onLeave( ) {
		isMinHandle = false;
		isMaxHandle = false;
	}

	protected void setTickMarks( ) {
		System.out.println( "Range Tickmarks not yet supported" );
	}

	public Range setColorTickMark( int theColor ) {
		_myColorTickMark = theColor;
		return this;
	}

	public Range showTickMarks( boolean theFlag ) {
		isShowTickMarks = theFlag;
		return this;
	}

	public Range snapToTickMarks( boolean theFlag ) {
		isSnapToTickMarks = theFlag;
		System.out.println( "Range Tickmarks not yet supported" );
		return this;
	}

	@ControlP5.Invisible public TickMark getTickMark( ) {
		System.out.println( "Range Tickmarks not yet supported" );
		return null;
	}

	public ArrayList< TickMark > getTickMarks( ) {
		return _myTickMarks;
	}

	public Range setNumberOfTickMarks( int theNumber ) {
		System.out.println( "Range Tickmarks not yet supported" );
		_myTickMarks.clear( );
		if ( theNumber > 0 ) {
			for ( int i = 0 ; i < theNumber ; i++ ) {
				_myTickMarks.add( new TickMark( this ) );
			}
			showTickMarks( true );
			snapToTickMarks( true );
		} else {
			showTickMarks( false );
			snapToTickMarks( false );
		}
		_myUnit = ( _myMax - _myMin ) / ( ( getWidth( ) > getHeight( ) ) ? getWidth( ) - 1 : getHeight( ) - 1 );
		setLowValue( _myArrayValue[ 0 ] , false );
		setHighValue( _myArrayValue[ 1 ] , false );
		return update( );
	}

	public Range setRange( float theMinValue , float theMaxValue ) {
		setMin( theMinValue );
		setMax( theMaxValue );
		return this;
	}

	public Range setRangeValues( float theLowValue , float theHighValue ) {
		return setArrayValue( new float[] { theLowValue , theHighValue } );
	}

	private Range setLowValue( float theValue , boolean isUpdate ) {
		_myArrayValue[ 0 ] = PApplet.max( _myMin , snapValue( theValue ) );
		minHandle = ( int ) PApplet.map( _myArrayValue[ 0 ] , _myMin , _myMax , handleSize , getWidth( ) - handleSize );
		return ( isUpdate ) ? update( ) : this;
	}

	public Range setLowValue( float theValue ) {
		return setLowValue( theValue , true );
	}

	private Range setHighValue( float theValue , boolean isUpdate ) {
		_myArrayValue[ 1 ] = PApplet.min( _myMax , snapValue( theValue ) );
		maxHandle = ( int ) PApplet.map( _myArrayValue[ 1 ] , _myMin , _myMax , handleSize , getWidth( ) - handleSize );
		return ( isUpdate ) ? update( ) : this;
	}

	public Range setHighValue( float theValue ) {
		return setHighValue( theValue , true );
	}

	protected float snapValue( float theValue ) {
		if ( isMousePressed ) {
			return theValue;
		}
		if ( isSnapToTickMarks ) {
			_myValuePosition = ( ( theValue - _myMin ) / _myUnit );
			float n = PApplet.round( PApplet.map( _myValuePosition , 0 , ( _myDirection == HORIZONTAL ) ? getWidth( ) : getHeight( ) , 0 , _myTickMarks.size( ) - 1 ) );
			theValue = PApplet.map( n , 0 , _myTickMarks.size( ) - 1 , _myMin , _myMax );
		}
		return theValue;
	}

	@Override @ControlP5.Invisible public Range updateDisplayMode( int theMode ) {
		_myDisplayMode = theMode;
		switch ( theMode ) {
		case ( DEFAULT ):
			_myControllerView = new RangeView( );
			break;
		case ( SPRITE ):
			_myControllerView = new RangeSpriteView( );
			break;
		case ( IMAGE ):
			_myControllerView = new RangeImageView( );
			break;
		case ( CUSTOM ):
		default:
			break;
		}
		return this;
	}

	class RangeSpriteView implements ControllerView< Range > {

		public void display( PGraphics theGraphics , Range theController ) {

			ControlP5.logger( ).log( Level.INFO , "RangeSpriteDisplay not available." );

		}

	}

	class RangeView implements ControllerView< Range > {

		public void display( PGraphics theGraphics , Range theController ) {

			int high = mode;

			final float posX = x( _myParent.getAbsolutePosition( ) ) + x( position );
			int x0 = ( int ) ( posX + minHandle );
			int x1 = ( int ) ( posX + maxHandle );

			if ( isInside( ) && high < 0 ) {
				if ( _myControlWindow.mouseX >= x0 - handleSize && _myControlWindow.mouseX < x0 ) {
					high = LEFT;
				} else if ( _myControlWindow.mouseX >= x1 && _myControlWindow.mouseX < x1 + handleSize ) {
					high = RIGHT;
				} else if ( _myControlWindow.mouseX > x0 && _myControlWindow.mouseX < x1 && isDraggable ) {
					high = CENTER;
				}
			}

			theGraphics.pushMatrix( );

			theGraphics.fill( color.getBackground( ) );

			theGraphics.noStroke( );

			theGraphics.rect( 0 , 0 , getWidth( ) , getHeight( ) );

			theGraphics.fill( high == CENTER ? color.getActive( ) : color.getForeground( ) );

			if ( isShowTickMarks ) {
				int n = handleSize / 2;
				theGraphics.rect( minHandle - n , 0 , mr + handleSize , getHeight( ) );
				theGraphics.fill( ( isMinHandle || high == LEFT ) ? color.getActive( ) : color.getForeground( ) );
				theGraphics.triangle( minHandle - handleSize , 0 , minHandle , 0 , minHandle - n , getHeight( ) );
				theGraphics.fill( ( isMaxHandle || high == RIGHT ) ? color.getActive( ) : color.getForeground( ) );
				theGraphics.triangle( maxHandle , 0 , maxHandle + handleSize , 0 , maxHandle + n , getHeight( ) );
			} else {
				theGraphics.rect( minHandle , 0 , mr , getHeight( ) );
				theGraphics.fill( ( isMinHandle || high == LEFT ) ? color.getActive( ) : color.getForeground( ) );
				theGraphics.rect( ( minHandle - handleSize ) , 0 , handleSize , getHeight( ) );
				theGraphics.fill( ( isMaxHandle || high == RIGHT ) ? color.getActive( ) : color.getForeground( ) );
				theGraphics.rect( maxHandle , 0 , handleSize , getHeight( ) );

			}

			if ( isLabelVisible ) {
				_myCaptionLabel.draw( theGraphics , 0 , 0 , theController );
				_myValueLabel.draw( theGraphics , 0 , 0 , theController );
				_myHighValueLabel.draw( theGraphics , 0 , 0 , theController );
			}

			theGraphics.popMatrix( );

			if ( isShowTickMarks ) {
				theGraphics.pushMatrix( );
				float x = ( getWidth( ) - handleSize ) / ( getTickMarks( ).size( ) - 1 );
				theGraphics.translate( handleSize / 2 , getHeight( ) );
				theGraphics.fill( _myColorTickMark );
				for ( TickMark tm : getTickMarks( ) ) {
					tm.draw( theGraphics );
					theGraphics.translate( x , 0 );
				}
				theGraphics.popMatrix( );
			}
		}
	}

	class RangeImageView implements ControllerView< Range > {

		public void display( PGraphics theGraphics , Range theController ) {

			ControlP5.logger( ).log( Level.INFO , "RangeImageDisplay not implemented." );

		}

	}
	
	@Override public Range setFont( PFont thePFont ) {
		_myHighValueLabel.setFont( thePFont );
		return super.setFont( thePFont );
	}

	@Override public Range setFont( ControlFont theFont ) {
		_myHighValueLabel.setFont( theFont );
		return super.setFont( theFont );
	}
	
	@Override @ControlP5.Invisible public String toString( ) {
		return "type:\tRange\n" + super.toString( );
	}

	@Deprecated public float lowValue( ) {
		return getLowValue( );
	}

	@Deprecated public float highValue( ) {
		return getHighValue( );
	}

	@Deprecated public float[] arrayValue( ) {
		return _myArrayValue;
	}

}
