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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import processing.core.PImage;

/**
 * A radioButton is a list of toggles that can be turned on or off. radioButton is of type
 * ControllerGroup, therefore a controllerPlug can't be set. this means that an event from a
 * radioButton can't be forwarded to a method other than controlEvent in a sketch.
 * 
 * a radioButton has 2 sets of values. radioButton.getValue() returns the value of the active
 * radioButton item. radioButton.getArrayValue() returns a float array that represents the active
 * (1) and inactive (0) items of a radioButton.
 * 
 * ControlP5 CheckBox Toggle
 * 
 * @example controllers/ControlP5radioButton
 * 
 * @nosuperclasses Controller Controller
 */
public class RadioButton extends ControlGroup< RadioButton > {

	protected List< Toggle > _myRadioToggles;
	protected int spacingRow = 1;
	protected int spacingColumn = 1;
	protected int itemsPerRow = -1;
	protected boolean isMultipleChoice;
	protected int itemHeight = 9;
	protected int itemWidth = 9;
	protected boolean[] availableImages = new boolean[ 3 ];
	protected PImage[] images = new PImage[ 3 ];
	protected boolean noneSelectedAllowed = true;
	private Object _myPlug;
	private String _myPlugName;
	protected int alignX = RIGHT_OUTSIDE;
	protected int alignY = CENTER;
	protected int _myPaddingX = Label.paddingX;
	protected int _myPaddingY = 0;

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

	/**
	 * @exclude
	 * @param theControlP5
	 * @param theParent
	 * @param theName
	 * @param theX
	 * @param theY
	 */
	public RadioButton( final ControlP5 theControlP5 , final ControllerGroup< ? > theParent , final String theName , final int theX , final int theY ) {
		super( theControlP5 , theParent , theName , theX , theY , 99 , 9 );
		isBarVisible = false;
		isCollapse = false;
		_myRadioToggles = new ArrayList< Toggle >( );
		setItemsPerRow( 1 );
		_myPlug = cp5.papplet;
		_myPlugName = getName( );
		if ( !ControllerPlug.checkPlug( _myPlug , _myPlugName , new Class[] { int.class } ) ) {
			_myPlug = null;
		}
	}

	/**
	 * @param theName
	 * @param theValue
	 * @return
	 */
	public RadioButton addItem( final String theName , final float theValue ) {
		Toggle t = cp5.addToggle( theName , 0 , 0 , itemWidth , itemHeight );
		t.getCaptionLabel( ).align( alignX , alignY ).setPadding( _myPaddingX , _myPaddingY );
		t.setMode( ControlP5.DEFAULT );
		t.setImages( images[ 0 ] , images[ 1 ] , images[ 2 ] );
		t.setSize( images[ 0 ] );
		addItem( t , theValue );
		return this;
	}

	/**
	 * @param theToggle
	 * @param theValue
	 * @return
	 */
	public RadioButton addItem( final Toggle theToggle , final float theValue ) {
		theToggle.setGroup( this );
		theToggle.isMoveable = false;
		theToggle.setInternalValue( theValue );
		theToggle.isBroadcast = false;
		_myRadioToggles.add( theToggle );
		updateLayout( );
		getColor( ).copyTo( theToggle );
		theToggle.addListener( this );
		updateValues( false );
		cp5.removeProperty( theToggle );
		return this;
	}

	/**
	 * @param theName
	 */
	public RadioButton removeItem( final String theName ) {
		int n = _myRadioToggles.size( );
		for ( int i = n-1 ; i >= 0 ; i-- ) {
			if ( ( _myRadioToggles.get( i ) ).getName( ).equals( theName ) ) {
				( _myRadioToggles.get( i ) ).removeListener( this );
				_myRadioToggles.get( i ).remove();
				_myRadioToggles.remove( i );
			}
		}
		updateValues( false );
		updateLayout( );
		return this;
	}

	private void updateAlign( ) {
		for ( Toggle t : _myRadioToggles ) {
			t.getCaptionLabel( ).align( alignX , alignY );
		}
	}

	public RadioButton align( int[] a ) {
		return align( a[ 0 ] , a[ 1 ] );
	}

	public RadioButton align( int theX , int theY ) {
		alignX = theX;
		alignY = theY;
		updateAlign( );
		return this;
	}

	public RadioButton alignX( int theX ) {
		return align( theX , alignY );
	}

	public RadioButton alignY( int theY ) {
		return align( alignX , theY );
	}

	public int[] getAlign( ) {
		return new int[] { alignX , alignY };
	}

	public RadioButton setLabelPadding( int thePaddingX , int thePaddingY ) {
		_myPaddingX = thePaddingX;
		_myPaddingY = thePaddingY;
		for ( Toggle t : _myRadioToggles ) {
			t.getCaptionLabel( ).setPadding( thePaddingX , thePaddingY );
		}
		return this;
	}

	/**
	 * 
	 * @param theDefaultImage
	 * @param theOverImage
	 * @param theActiveImage
	 * @return RadioButton
	 */
	public RadioButton setImages( PImage theDefaultImage , PImage theOverImage , PImage theActiveImage ) {
		setImage( theDefaultImage , DEFAULT );
		setImage( theOverImage , OVER );
		setImage( theActiveImage , ACTIVE );
		return this;
	}

	/**
	 * @param theImage
	 */
	public RadioButton setImage( PImage theImage ) {
		return setImage( theImage , DEFAULT );
	}

	/**
	 * @param theImage
	 * @param theState
	 *            use Controller.DEFAULT (background), or Controller.OVER (foreground), or
	 *            Controller.ACTIVE (active)
	 * @return
	 */
	public RadioButton setImage( PImage theImage , int theState ) {
		if ( theImage != null ) {
			images[ theState ] = theImage;
			availableImages[ theState ] = true;
			for ( int i = 0 ; i < _myRadioToggles.size( ) ; i++ ) {
				_myRadioToggles.get( i ).setImage( theImage , theState );
			}
		}
		return this;
	}

	public RadioButton setSize( PImage theImage ) {
		return setSize( theImage.width , theImage.height );
	}

	public RadioButton setSize( int theWidth , int theHeight ) {
		setItemWidth( theWidth );
		setItemHeight( theHeight );
		return this;
	}

	/**
	 * set the height of a radioButton/checkBox item. by default the height is 11px. in order to
	 * recognize a custom height, the itemHeight has to be set before adding items to a
	 * radioButton/checkBox.
	 * 
	 * @param theItemHeight
	 */
	public RadioButton setItemHeight( int theItemHeight ) {
		itemHeight = theItemHeight;
		for ( Toggle t : _myRadioToggles ) {
			t.setHeight( theItemHeight );
		}
		updateLayout( );
		return this;
	}

	/**
	 * set the width of a radioButton/checkBox item. by default the width is 11px. in order to
	 * recognize a custom width, the itemWidth has to be set before adding items to a
	 * radioButton/checkBox.
	 * 
	 * @param theItemWidth
	 */
	public RadioButton setItemWidth( int theItemWidth ) {
		itemWidth = theItemWidth;
		for ( Toggle t : _myRadioToggles ) {
			t.setWidth( theItemWidth );
		}
		updateLayout( );
		return this;
	}

	/**
	 * Gets a radio button item by index.
	 * 
	 * @param theIndex
	 * @return Toggle
	 */
	public Toggle getItem( int theIndex ) {
		return _myRadioToggles.get( theIndex );
	}

	public Toggle getItem( String theName ) {
		for ( Toggle t : _myRadioToggles ) {
			if ( theName.equals( t.getName( ) ) ) {
				return t;
			}
		}
		return null;
	}

	public List< Toggle > getItems( ) {
		return _myRadioToggles;
	}

	/**
	 * Gets the state of an item - this can be true (for on) or false (for off) - by index.
	 * 
	 * @param theIndex
	 * @return boolean
	 */
	public boolean getState( int theIndex ) {
		if ( theIndex < _myRadioToggles.size( ) && theIndex >= 0 ) {
			return ( ( Toggle ) _myRadioToggles.get( theIndex ) ).getState( );
		}
		return false;
	}

	/**
	 * Gets the state of an item - this can be true (for on) or false (for off) - by name.
	 * 
	 * @param theName
	 * @return
	 */
	public boolean getState( String theName ) {
		int n = _myRadioToggles.size( );
		for ( int i = 0 ; i < n ; i++ ) {
			Toggle t = _myRadioToggles.get( i );
			if ( theName.equals( t.getName( ) ) ) {
				return t.getState( );
			}
		}
		return false;
	}

	/**
	 * @exclude
	 */
	public void updateLayout( ) {
		int nn = 0;
		int xx = 0;
		int yy = 0;
		int n = _myRadioToggles.size( );
		for ( int i = 0 ; i < n ; i++ ) {
			Toggle t = _myRadioToggles.get( i );
			set( t.position , xx , yy );

			xx += t.getWidth( ) + spacingColumn;
			nn++;
			if ( nn == itemsPerRow ) {
				nn = 0;
				_myWidth = xx;
				yy += t.getHeight( ) + spacingRow;
				xx = 0;
			} else {
				_myWidth = xx;
			}
		}
	}

	/**
	 * Items of a radioButton or a checkBox are organized in columns and rows. SetItemsPerRow sets
	 * the limit of items per row. items exceeding the limit will be pushed to the next row.
	 * 
	 * @param theValue
	 */
	public RadioButton setItemsPerRow( final int theValue ) {
		itemsPerRow = theValue;
		updateLayout( );
		return this;
	}

	/**
	 * Sets the spacing in pixels between columns.
	 * 
	 * @param theSpacing
	 */
	public RadioButton setSpacingColumn( final int theSpacing ) {
		spacingColumn = theSpacing;
		updateLayout( );
		return this;
	}

	/**
	 * Sets the spacing in pixels between rows.
	 * 
	 * @param theSpacing
	 */
	public RadioButton setSpacingRow( final int theSpacing ) {
		spacingRow = theSpacing;
		updateLayout( );
		return this;
	}

	public RadioButton deactivateAll( ) {
		if ( !isMultipleChoice && !noneSelectedAllowed ) {
			return this;
		}
		int n = _myRadioToggles.size( );
		for ( int i = 0 ; i < n ; i++ ) {
			( ( Toggle ) _myRadioToggles.get( i ) ).deactivate( );
		}
		_myValue = -1;
		updateValues( true );
		return this;
	}

	/**
	 * Deactivates all active RadioButton items and only activates the item corresponding to
	 * theIndex.
	 * TODO does not trigger function or value when called by code, fix!
	 * 
	 * @param theIndex
	 */
	public RadioButton activate( int theIndex ) {
		int n = _myRadioToggles.size( );
		if ( theIndex < n ) {
			for ( int i = 0 ; i < n ; i++ ) {
				_myRadioToggles.get( i ).deactivate( );
			}
			( ( Toggle ) _myRadioToggles.get( theIndex ) ).activate( );
			_myValue = _myRadioToggles.get( theIndex ).internalValue( );
			updateValues( true );
		}
		return this;
	}

	/**
	 * @param theIndex
	 */
	public RadioButton deactivate( int theIndex ) {
		if ( !isMultipleChoice && !noneSelectedAllowed ) {
			return this;
		}
		if ( theIndex < _myRadioToggles.size( ) ) {
			Toggle t = _myRadioToggles.get( theIndex );
			if ( t.isActive ) {
				t.deactivate( );
				_myValue = -1;
				updateValues( true );
			}
		}
		return this;
	}

	/**
	 * Actives an item of the Radio button by name.
	 * 
	 * @param theName
	 */
	public RadioButton activate( String theName ) {
		int n = _myRadioToggles.size( );
		for ( int i = 0 ; i < n ; i++ ) {
			Toggle t = _myRadioToggles.get( i );
			if ( theName.equals( t.getName( ) ) ) {
				activate( i );
				return this;
			}
		}
		return this;
	}

	/**
	 * Deactivates a RadioButton by name and sets the value of the RadioButton to the default value
	 * -1.
	 * 
	 * @param theName
	 */
	public RadioButton deactivate( String theName ) {
		int n = _myRadioToggles.size( );
		for ( int i = 0 ; i < n ; i++ ) {
			Toggle t = _myRadioToggles.get( i );
			if ( theName.equals( t.getName( ) ) ) {
				t.deactivate( );
				_myValue = -1;
				updateValues( true );
				return this;
			}
		}
		return this;
	}

	/**
	 * @exclude
	 * @param theIndex
	 */
	public RadioButton toggle( int theIndex ) {
		// TODO
		// boolean itemState = ((Toggle)
		// _myRadioToggles.get(theIndex)).getState();
		// if (theIndex < _myRadioToggles.size()) {
		// Toggle t = ((Toggle) _myRadioToggles.get(theIndex));
		// if (t.isActive) {
		// t.deactivate();
		// _myValue = -1;
		// updateValues(true);
		// }
		// }
		ControlP5.logger( ).info( "toggle() not yet implemented, working on it." );
		return this;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @exclude
	 */
	@ControlP5.Invisible @Override public void controlEvent( ControlEvent theEvent ) {
		if ( !isMultipleChoice ) {
			if ( noneSelectedAllowed == false && theEvent.getController( ).getValue( ) < 1 ) {
				if ( theEvent.getController( ) instanceof Toggle ) {
					Toggle t = ( ( Toggle ) theEvent.getController( ) );
					boolean b = t.isBroadcast( );
					t.setBroadcast( false );
					t.setState( true );
					t.setBroadcast( b );
					return;
				}
			}
			_myValue = -1;
			int n = _myRadioToggles.size( );
			for ( int i = 0 ; i < n ; i++ ) {
				Toggle t = _myRadioToggles.get( i );
				if ( !t.equals( theEvent.getController( ) ) ) {
					t.deactivate( );
				} else {
					if ( t.isOn ) {
						_myValue = t.internalValue( );
					}
				}
			}
		}
		if ( _myPlug != null ) {
			try {
				Method method = _myPlug.getClass( ).getMethod( _myPlugName , int.class );
				method.invoke( _myPlug , ( int ) _myValue );
			} catch ( SecurityException ex ) {
				ex.printStackTrace( );
			} catch ( NoSuchMethodException ex ) {
				ex.printStackTrace( );
			} catch ( IllegalArgumentException ex ) {
				ex.printStackTrace( );
			} catch ( IllegalAccessException ex ) {
				ex.printStackTrace( );
			} catch ( InvocationTargetException ex ) {
				ex.printStackTrace( );
			}
		}
		updateValues( true );
	}

	public RadioButton plugTo( Object theObject ) {
		_myPlug = theObject;
		if ( !ControllerPlug.checkPlug( _myPlug , _myPlugName , new Class[] { int.class } ) ) {
			_myPlug = null;
		}
		return this;
	}

	public RadioButton plugTo( Object theObject , String thePlugName ) {
		_myPlug = theObject;
		_myPlugName = thePlugName;
		if ( !ControllerPlug.checkPlug( _myPlug , _myPlugName , new Class[] { int.class } ) ) {
			_myPlug = null;
		}
		return this;
	}

	protected void updateValues( boolean theBroadcastFlag ) {
		int n = _myRadioToggles.size( );
		_myArrayValue = new float[ n ];
		for ( int i = 0 ; i < n ; i++ ) {
			Toggle t = ( ( Toggle ) _myRadioToggles.get( i ) );
			_myArrayValue[ i ] = t.getValue( );
		}
		if ( theBroadcastFlag ) {
			ControlEvent myEvent = new ControlEvent( this );
			cp5.getControlBroadcaster( ).broadcast( myEvent , ControlP5Constants.FLOAT );
		}

	}

	/**
	 * In order to always have 1 item selected, use setNoneSelectedAllowed(false), by default this
	 * is true. setNoneSelectedAllowed does not apply when in multipleChoice mode.
	 * 
	 * @param theValue
	 */
	public RadioButton setNoneSelectedAllowed( boolean theValue ) {
		noneSelectedAllowed = theValue;
		return this;
	}

	/**
	 * Sets the value for all RadioButton items according to the values of the array passed on. 0
	 * will turn off an item, any other value will turn it on.
	 */
	@Override public RadioButton setArrayValue( float[] theArray ) {
		for ( int i = 0 ; i < theArray.length ; i++ ) {
			if ( _myArrayValue[ i ] != theArray[ i ] ) {
				if ( theArray[ i ] == 0 ) {
					deactivate( i );
				} else {
					activate( i );
				}
			}
		}
		super.setArrayValue( theArray );
		return this;
	}

	public RadioButton setColorLabels( int theColor ) {
		for ( Toggle t : _myRadioToggles ) {
			t.getCaptionLabel( ).setColor( theColor );
		}
		return this;
	}

	public RadioButton hideLabels( ) {
		for ( Toggle t : _myRadioToggles ) {
			t.getCaptionLabel( ).setVisible( false );
		}
		return this;
	}

	public RadioButton showLabels( ) {
		for ( Toggle t : _myRadioToggles ) {
			t.getCaptionLabel( ).setVisible( true );
		}
		return this;
	}

	public RadioButton toUpperCase( boolean theValue ) {
		for ( Toggle t : _myRadioToggles ) {
			t.getCaptionLabel( ).toUpperCase( theValue );
		}
		return this;
	}

	/**
	 * @exclude {@inheritDoc}
	 */
	@Override public String getInfo( ) {
		return "type:\tRadioButton\n" + super.getInfo( );
	}

	/**
	 * @deprecated
	 * @exclude
	 */
	@Deprecated public RadioButton add( final String theName , final float theValue ) {
		return addItem( theName , theValue );
	}
}
