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.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PGraphics;
import processing.event.KeyEvent;
import processing.event.MouseEvent;
import controlP5.ControlWindow.Pointer;

/**
 * <p>
 * controlP5 is a processing and java library for creating
 * simple control GUIs. The ControlP5 class, the core of
 * controlP5.
 * </p>
 * <p>
 * All addController-Methods are located inside the
 * ControlP5Base class.
 * </p>
 *
 * @see controlP5.ControlP5Base
 * @example use/ControlP5basics
 */
public class ControlP5 extends ControlP5Base {

	/**
	 * @exclude
	 */
	@ControlP5.Invisible public ControlWindow controlWindow;

	/**
	 * @exclude
	 */
	@ControlP5.Invisible static CColor color = new CColor( THEME_CP52014 );

	/**
	 * @exclude
	 */
	@ControlP5.Invisible public PApplet papplet;

	/**
	 * @exclude
	 */
	@ControlP5.Invisible PGraphics pg;
	int pgx = 0 , pgy = 0 , pgw = 0 , pgh = 0;
	int ox = 0;
	int oy = 0;

	boolean isGraphics = false;

	/**
	 * @exclude
	 */
	@ControlP5.Invisible public static final String VERSION = "2.2.7";// "##version##";

	/**
	 * @exclude
	 */
	@ControlP5.Invisible public static boolean isApplet = false;

	static int renderer = J2D;

	/**
	 * use this static variable to turn DEBUG on or off.
	 */
	public static boolean DEBUG;

	/**
	 * @exclude
	 */
	@ControlP5.Invisible public static final Logger logger = Logger.getLogger( ControlP5.class.getName( ) );

	private Map< String , ControllerInterface< ? >> _myControllerMap;
	protected ControlBroadcaster _myControlBroadcaster;
	protected ControlWindow window;
	protected boolean isMoveable = false;

	/* TODO does not work anymore, deprecate? */
	protected boolean isAutoInitialization = false;
	protected boolean isGlobalControllersAlwaysVisible = true;
	protected boolean isTabEventsActive;
	protected boolean isUpdate;
	protected boolean isControlFont;
	protected ControlFont controlFont;
	static public final PFont BitFontStandard56 = new BitFont( CP.decodeBase64( BitFont.standard56base64 ) );
	static public final PFont BitFontStandard58 = new BitFont( CP.decodeBase64( BitFont.standard58base64 ) );
	protected ControlFont defaultFont = new ControlFont( BitFontStandard58 );
	protected ControlFont defaultFontForText = new ControlFont( BitFontStandard56 );

	/**
	 * from version 0.7.2 onwards shortcuts are disabled by
	 * default. shortcuts can be enabled using
	 * controlP5.enableShortcuts();
	 *
	 * @see #enableShortcuts()
	 */
	protected boolean isShortcuts = false;

	@Deprecated public boolean blockDraw;
	protected Tooltip _myTooltip;
	protected boolean isAnnotation;
	boolean isAndroid = false;

	/**
	 * Create a new instance of controlP5.
	 *
	 * @param theParent PApplet
	 */
	public ControlP5( final PApplet theParent ) {
		papplet = theParent;
		init( );
	}

	public ControlP5( final PApplet theParent , PFont thePFont ) {
		papplet = theParent;
		init( );
		setFont( thePFont );
	}

	public ControlP5( final PApplet theParent , ControlFont theControlFont ) {
		papplet = theParent;
		init( );
		setFont( theControlFont );
	}

	protected void init( ) {
		renderer = ( papplet.g.getClass( ).getCanonicalName( ).indexOf( "Java2D" ) > -1 ) ? J2D : P3D;
		Class< ? > check = papplet.getClass( );
		while ( check != null ) {
			check = check.getSuperclass( );
			if ( check != null ) {
				if ( check.toString( ).toLowerCase( ).indexOf( "android.app." ) > -1 ) {
					isAndroid = true;
					break;
				}
			}
		}

		isTabEventsActive = false;

		_myControlBroadcaster = new ControlBroadcaster( this );

		// } else {
		// defaultFont = new
		// ControlFont(papplet.createFont("", 10));
		//
		// defaultFontForText = new
		// ControlFont(papplet.createFont("", 10));
		// }

		controlFont = defaultFont;
		controlWindow = new ControlWindow( this , papplet );
		papplet.registerMethod( "pre" , this );
		papplet.registerMethod( "dispose" , this );
		setGraphics( papplet , 0 , 0 );
		_myControllerMap = new TreeMap< String , ControllerInterface< ? >>( );
		setFont( controlFont );
		_myTooltip = new Tooltip( this );
		super.init( this );

		if ( welcome++ < 1 ) {
			welcome( );
		}

		mapKeyFor( new ControlKey( ) {

			public void keyEvent( ) {
				saveProperties( );
			}
		} , PApplet.ALT , PApplet.SHIFT , 's' );

		mapKeyFor( new ControlKey( ) {

			public void keyEvent( ) {
				loadProperties( );
			}
		} , PApplet.ALT , PApplet.SHIFT , 'l' );

		mapKeyFor( new ControlKey( ) {

			public void keyEvent( ) {
				if ( controlWindow.isVisible ) {
					hide( );
				} else {
					show( );
				}
			}
		} , PApplet.ALT , PApplet.SHIFT , 'h' );

		disableShortcuts( );

		setFont( controlFont );

	}

	static int welcome = 0;

	private void welcome( ) {
		System.out.println( "ControlP5 " + VERSION + " " + "infos, comments, questions at http://www.sojamo.de/libraries/controlP5" );
	}

	public ControlP5 setGraphics( PApplet theApplet , int theX , int theY ) {
		setGraphics( theApplet.g , theX , theY );
		isGraphics = false;
		return this;
	}

	public ControlP5 setGraphics( PGraphics theGraphics , int theX , int theY ) {
		pg = theGraphics;
		pgx = theX;
		pgy = theY;
		pgw = pg.width;
		pgh = pg.height;
		isGraphics = true;
		return this;
	}

	public ControlP5 setPosition( int theX , int theY ) {
		ox = theX;
		oy = theY;
		return this;
	}

	/**
	 * By default event originating from tabs are disabled,
	 * use setTabEventsActive(true) to receive controlEvents
	 * when tabs are clicked.
	 *
	 * @param theFlag
	 */
	public void setTabEventsActive( boolean theFlag ) {
		isTabEventsActive = theFlag;
	}

	/**
	 * autoInitialization can be very handy when it comes to
	 * initializing values, e.g. you load a set of
	 * controllers, then the values that are attached to the
	 * controllers will be reset to its saved state. to turn
	 * of auto intialization, call
	 * setAutoInitialization(false) right after initializing
	 * controlP5 and before creating any controller.
	 *
	 * @param theFlag boolean
	 */
	public void setAutoInitialization( boolean theFlag ) {
		isAutoInitialization = theFlag;
	}

	/**
	 * by default controlP5 draws any controller on top of
	 * any drawing done in the draw() function (this doesnt
	 * apply to P3D where controlP5.draw() has to be called
	 * manually in the sketch's draw() function ). to turn
	 * off the auto drawing of controlP5, use
	 * controlP5.setAutoDraw(false). now you can call
	 * controlP5.draw() any time whenever controllers should
	 * be drawn into the sketch.
	 *
	 * @param theFlag boolean
	 */
	public void setAutoDraw( boolean theFlag ) {
		if ( isAutoDraw( ) && theFlag == false ) {
			controlWindow.papplet( ).unregisterMethod( "draw" , controlWindow );
		}
		if ( isAutoDraw( ) == false && theFlag == true ) {
			controlWindow.papplet( ).registerMethod( "draw" , controlWindow );
		}
		controlWindow.isAutoDraw = theFlag;
	}

	/**
	 * check if the autoDraw function for the main window is
	 * enabled(true) or disabled(false).
	 *
	 * @return boolean
	 */
	public boolean isAutoDraw( ) {
		return controlWindow.isAutoDraw;
	}

	/**
	 *
	 * @see controlP5.ControlBroadcaster
	 */
	public ControlBroadcaster getControlBroadcaster( ) {
		return _myControlBroadcaster;
	}

	/**
	 * @see controlP5.ControlListener
	 */
	public ControlP5 addListener( ControlListener ... theListeners ) {
		getControlBroadcaster( ).addListener( theListeners );
		return this;
	}

	/**
	 * @see controlP5.ControlListener
	 */
	public ControlP5 removeListener( ControlListener ... theListeners ) {
		getControlBroadcaster( ).removeListener( theListeners );
		return this;
	}

	/**
	 * @see controlP5.ControlListener
	 */
	public ControlP5 removeListener( ControlListener theListener ) {
		getControlBroadcaster( ).removeListener( theListener );
		return this;
	}

	/**
	 * @see controlP5.ControlListener
	 */
	public ControlListener getListener( int theIndex ) {
		return getControlBroadcaster( ).getListener( theIndex );
	}

	/**
	 * @see controlP5.CallbackEvent
	 * @see controlP5.CallbackListener
	 */
	public ControlP5 addCallback( CallbackListener ... theListeners ) {
		getControlBroadcaster( ).addCallback( theListeners );
		return this;
	}

	/**
	 * @see controlP5.CallbackEvent
	 * @see controlP5.CallbackListener
	 */
	public ControlP5 addCallback( CallbackListener theListener ) {
		getControlBroadcaster( ).addCallback( theListener );
		return this;
	}

	/**
	 * @see controlP5.CallbackEvent
	 * @see controlP5.CallbackListener
	 */
	public ControlP5 addCallback( CallbackListener theListener , Controller< ? > ... theControllers ) {
		getControlBroadcaster( ).addCallback( theListener , theControllers );
		return this;
	}

	/**
	 * @see controlP5.CallbackEvent
	 * @see controlP5.CallbackListener
	 */
	public ControlP5 removeCallback( CallbackListener ... theListeners ) {
		getControlBroadcaster( ).removeCallback( theListeners );
		return this;
	}

	/**
	 * @see controlP5.CallbackEvent
	 * @see controlP5.CallbackListener
	 */
	public ControlP5 removeCallback( Controller< ? > ... theControllers ) {
		getControlBroadcaster( ).removeCallback( theControllers );
		return this;
	}

	/**
	 * @see controlP5.CallbackEvent
	 * @see controlP5.CallbackListener
	 */
	public ControlP5 removeCallback( Controller< ? > theController ) {
		getControlBroadcaster( ).removeCallback( theController );
		return this;
	}

	/**
	 * TODO
	 *
	 * @exclude
	 */
	public void addControlsFor( Object theObject ) {

	}

	public Tab getTab( String theName ) {
		for ( int i = 0 ; i < controlWindow.getTabs( ).size( ) ; i++ ) {
			if ( ( ( Tab ) controlWindow.getTabs( ).get( i ) ).getName( ).equals( theName ) ) {
				return ( Tab ) controlWindow.getTabs( ).get( i );
			}
		}
		Tab myTab = addTab( theName );
		return myTab;
	}

	public Tab getTab( ControlWindow theWindow , String theName ) {
		for ( int i = 0 ; i < theWindow.getTabs( ).size( ) ; i++ ) {
			if ( ( ( Tab ) theWindow.getTabs( ).get( i ) ).getName( ).equals( theName ) ) {
				return ( Tab ) theWindow.getTabs( ).get( i );
			}
		}
		Tab myTab = theWindow.add( new Tab( this , theWindow , theName ) );
		return myTab;
	}

	/**
	 * registers a Controller with ControlP5, a Controller
	 * should/must be registered with a unique name. If not,
	 * accessing Controllers by name is not guaranteed. the
	 * rule here is last come last serve, existing
	 * Controllers with the same name will be overridden.
	 *
	 * @param theController ControllerInterface
	 * @return ControlP5
	 */
	public ControlP5 register( Object theObject , String theIndex , ControllerInterface< ? > theController ) {
		String address = "";
		if ( theObject == papplet ) {
			address = ( theController.getName( ).startsWith( "/" ) ) ? "" : "/";
			address += theController.getName( );
		} else {
			address = ( ( ( theIndex.length( ) == 0 ) || theIndex.startsWith( "/" ) ) ? "" : "/" );
			address += theIndex;
			address += ( theController.getName( ).startsWith( "/" ) ? "" : "/" );
			address += theController.getName( );
		}

		theController.setAddress( address );

		if ( checkName( theController.getAddress( ) ) ) {
			/* in case a controller with the same name
			 * already exists, will be deleted */
			remove( theController.getAddress( ) );
		}

		/* add the controller to the controller map */
		_myControllerMap.put( theController.getAddress( ) , theController );

		/* update the properties' controller address */
		List< ControllerProperty > ps = getProperties( ).get( theController );
		if ( ps != null ) {
			for ( ControllerProperty p : ps ) {
				p.setAddress( theController.getAddress( ) );
			}
		}
		/* initialize the controller */

		theController.init( );

		/* handle controller plugs and map controllers to
		 * its reference objects if applicable. */

		if ( theObject == null ) {
			theObject = papplet;
		}

		if ( theController instanceof Controller< ? > ) {

			if ( !theObject.equals( papplet ) ) {
				( ( Controller< ? > ) ( ( Controller< ? > ) theController ).unplugFrom( papplet ) ).plugTo( theObject );
			}

		}

		if ( !_myObjectToControllerMap.containsKey( theObject ) ) {
			_myObjectToControllerMap.put( theObject , new ArrayList< ControllerInterface< ? >>( ) );
		}

		_myObjectToControllerMap.get( theObject ).add( theController );
		return this;
	}

	public ControlP5 register( ControllerInterface< ? > theController ) {
		return register( papplet , "" , theController );
	}

	/**
	 * Returns a List of all controllers currently
	 * registered.
	 *
	 * @return List<ControllerInterface<?>>
	 */
	public List< ControllerInterface< ? >> getAll( ) {
		return new ArrayList< ControllerInterface< ? >>( _myControllerMap.values( ) );
	}

	/**
	 * Returns a list of controllers or groups of a
	 * particular type. The following example will return a
	 * list of registered Bangs only:<br />
	 * <code><pre>
	 * List<Bang> list = controlP5.getAll(Bang.class);
	 * println(list);
	 * for(Bang b:list) {
	 *   b.setColorForeground(color(255,255,0));
	 * }
	 * </pre></code> Here the foreground color of all Bangs
	 * is changed to yellow.
	 *
	 * @param <T>
	 * @param theClass A class that extends
	 *            ControllerInterface, which applies to all
	 *            Controllers and ControllerGroups
	 * @return List<T>
	 */
	@SuppressWarnings( "unchecked" ) public < T > List< T > getAll( Class< T > theClass ) {
		ArrayList< T > l = new ArrayList< T >( );
		for ( ControllerInterface ci : _myControllerMap.values( ) ) {
			if ( ci.getClass( ) == theClass || ci.getClass( ).getSuperclass( ) == theClass ) {
				l.add( ( T ) ci );
			}
		}
		return l;
	}

	protected void deactivateControllers( ) {
		for ( Textfield t : getAll( Textfield.class ) ) {
			t.setFocus( false );
		}
	}

	private String checkAddress( String theName ) {
		if ( !theName.startsWith( "/" ) ) {
			return "/" + theName;
		}
		return theName;
	}

	/**
	 * @excude
	 */
	public void printControllerMap( ) {
		List< String > strs = new ArrayList< String >( );
		System.out.println( "============================================" );
		for ( Iterator it = _myControllerMap.entrySet( ).iterator( ) ; it.hasNext( ) ; ) {
			Map.Entry entry = ( Map.Entry ) it.next( );
			Object key = entry.getKey( );
			Object value = entry.getValue( );
			strs.add( key + " = " + value );
		}
		Collections.sort( strs );
		for ( String s : strs ) {
			System.out.println( s );
		}
		System.out.println( "============================================" );
	}

	/**
	 * removes a controller by instance.
	 *
	 * TODO Fix this. this only removes the reference to a
	 * controller from the controller map but not its
	 * children, fatal for controller groups!
	 *
	 * @param theController ControllerInterface
	 */
	protected void remove( ControllerInterface< ? > theController ) {
		_myControllerMap.remove( theController.getAddress( ) );
	}

	/**
	 * removes a controlP5 element such as a controller,
	 * group, or tab by name.
	 *
	 * @param theString String
	 */
	public void remove( String theName ) {
		String address = checkAddress( theName );

		if ( getController( address ) != null ) {
			getController( address ).remove( );
		}

		if ( getGroup( address ) != null ) {
			getGroup( address ).remove( );
		}

		for ( int i = 0 ; i < controlWindow.getTabs( ).size( ) ; i++ ) {
			if ( controlWindow.getTabs( ).get( i ).getAddress( ).equals( address ) ) {
				controlWindow.getTabs( ).get( i ).remove( );
			}
		}
		_myControllerMap.remove( address );
	}

	public ControllerInterface< ? > get( String theName ) {
		String address = checkAddress( theName );
		if ( _myControllerMap.containsKey( address ) ) {
			return _myControllerMap.get( address );
		}
		return null;
	}

	public ControllerInterface< ? > get( Object theObject , String theName ) {
		return getController( theName , theObject );
	}

	public < C > C get( Class< C > theClass , String theName ) {
		for ( ControllerInterface< ? > ci : _myControllerMap.values( ) ) {
			if ( ci.getClass( ) == theClass || ci.getClass( ).getSuperclass( ) == theClass ) {
				return ( C ) get( theName );
			}
		}
		return null;
	}

	/**
	 * @exclude
	 * @see controlP5.ControlP5#getAll(Class)
	 * @return List<ControllerInterface>
	 */
	@ControlP5.Invisible public List< ControllerInterface< ? >> getList( ) {
		LinkedList< ControllerInterface< ? >> l = new LinkedList< ControllerInterface< ? >>( );
		l.addAll( controlWindow.getTabs( ).get( ) );
		l.addAll( getAll( ) );
		return l;
	}

	public float getValue( String theIndex ) {
		Controller c = getController( theIndex );
		if ( c != null ) {
			return c.getValue( );
		}
		return Float.NaN;
	}

	public Controller< ? > getController( String theName ) {
		String address = checkAddress( theName );
		if ( _myControllerMap.containsKey( address ) ) {
			if ( _myControllerMap.get( address ) instanceof Controller< ? > ) {
				return ( Controller< ? > ) _myControllerMap.get( address );
			}
		}
		return null;
	}

	public ControllerGroup< ? > getGroup( String theGroupName ) {
		String address = checkAddress( theGroupName );
		if ( _myControllerMap.containsKey( address ) ) {
			if ( _myControllerMap.get( address ) instanceof ControllerGroup< ? > ) {
				return ( ControllerGroup< ? > ) _myControllerMap.get( address );
			}
		}
		return null;
	}

	private boolean checkName( String theName ) {
		if ( _myControllerMap.containsKey( checkAddress( theName ) ) ) {
			ControlP5.logger( ).warning( "Controller with name \"" + theName + "\" already exists. overwriting reference of existing controller." );
			return true;
		}
		return false;
	}

	public void moveControllersForObject( Object theObject , ControllerGroup< ? > theGroup ) {
		if ( _myObjectToControllerMap.containsKey( theObject ) ) {
			ArrayList< ControllerInterface< ? >> cs = _myObjectToControllerMap.get( theObject );
			for ( ControllerInterface< ? > c : cs ) {
				( ( Controller< ? > ) c ).moveTo( theGroup );
			}
		}
	}

	public void move( Object theObject , ControllerGroup< ? > theGroup ) {
		moveControllersForObject( theObject , theGroup );
	}

	protected void clear( ) {
		controlWindow.clear( );
		_myControllerMap.clear( );
	}

	/**
	 * @exclude
	 */
	@ControlP5.Invisible public void pre( ) {
		Iterator< FieldChangedListener > itr = _myFieldChangedListenerMap.values( ).iterator( );
		while ( itr.hasNext( ) ) {
			itr.next( ).update( );
		}
	}

	/**
	 * call draw() from your program when autoDraw is
	 * disabled.
	 *
	 * @exclude
	 */
	@ControlP5.Invisible public void draw( ) {
		if ( !isAutoDraw( ) ) {
			controlWindow.draw( );
		}
	}

	/**
	 * convenience method to access the main window
	 * (ControlWindow class).
	 */
	public ControlWindow getWindow( ) {
		return getWindow( papplet );
	}

	public void mouseEvent( MouseEvent theMouseEvent ) {
		getWindow( ).mouseEvent( theMouseEvent );
	}

	public void keyEvent( KeyEvent theKeyEvent ) {
		getWindow( ).keyEvent( theKeyEvent );
	}

	/**
	 * convenience method to access the pointer of the main
	 * control window.
	 */
	public Pointer getPointer( ) {
		return getWindow( papplet ).getPointer( );
	}

	/**
	 * convenience method to check if the mouse (or pointer)
	 * is hovering over any controller. only applies to the
	 * main window. To receive the mouseover information for
	 * a ControlWindow use
	 * getWindow(nameOfWindow).isMouseOver();
	 */
	public boolean isMouseOver( ) {
		return getWindow( papplet ).isMouseOver( );
	}

	/**
	 * convenience method to check if the mouse (or pointer)
	 * is hovering over a specific controller. only applies
	 * to the main window. To receive the mouseover
	 * information for a ControlWindow use
	 * getWindow(nameOfWindow
	 * ).isMouseOver(ControllerInterface<?>);
	 */
	public boolean isMouseOver( ControllerInterface< ? > theController ) {
		return getWindow( papplet ).isMouseOver( theController );
	}

	/**
	 * convenience method to check if the mouse (or pointer)
	 * is hovering over a specific controller. only applies
	 * to the main window. To receive the mouseover
	 * information for a ControlWindow use
	 * getWindow(nameOfWindow).getMouseOverList();
	 */
	public List< ControllerInterface< ? >> getMouseOverList( ) {
		return getWindow( papplet ).getMouseOverList( );
	}

	public ControlWindow getWindow( PApplet theApplet ) {
		if ( theApplet.equals( papplet ) ) {
			return controlWindow;
		}
		// TODO !!! check for another window in case
		// theApplet is of type
		// PAppletWindow.
		return controlWindow;
	}

	/**
	 * adds a Canvas to the default sketch window.
	 *
	 * @see controlP5.Canvas
	 */
	public ControlP5 addCanvas( Canvas theCanvas ) {
		getWindow( ).addCanvas( theCanvas );
		return this;
	}

	public ControlP5 removeCanvas( Canvas theCanvas ) {
		getWindow( ).removeCanvas( theCanvas );
		return this;
	}

	public ControlP5 setColor( CColor theColor ) {
		setColorBackground( theColor.getBackground( ) );
		setColorForeground( theColor.getForeground( ) );
		setColorActive( theColor.getActive( ) );
		setColorCaptionLabel( theColor.getCaptionLabel( ) );
		setColorValueLabel( theColor.getValueLabel( ) );
		return this;
	}

	public static CColor getColor( ) {
		return color;
	}

	/**
	 * sets the active state color of tabs and controllers,
	 * this cascades down to all known controllers.
	 */
	public ControlP5 setColorActive( int theColor ) {
		color.setActive( theColor );
		controlWindow.setColorActive( theColor );
		return this;
	}

	/**
	 * sets the foreground color of tabs and controllers,
	 * this cascades down to all known controllers.
	 */
	public ControlP5 setColorForeground( int theColor ) {
		color.setForeground( theColor );
		controlWindow.setColorForeground( theColor );
		return this;
	}

	/**
	 * sets the background color of tabs and controllers,
	 * this cascades down to all known controllers.
	 */
	public ControlP5 setColorBackground( int theColor ) {
		color.setBackground( theColor );
		controlWindow.setColorBackground( theColor );
		return this;
	}

	/**
	 * sets the label color of tabs and controllers, this
	 * cascades down to all known controllers.
	 */
	public ControlP5 setColorCaptionLabel( int theColor ) {
		color.setCaptionLabel( theColor );
		controlWindow.setColorLabel( theColor );
		return this;
	}

	/**
	 * sets the value color of controllers, this cascades
	 * down to all known controllers.
	 */
	public ControlP5 setColorValueLabel( int theColor ) {
		color.setValueLabel( theColor );
		controlWindow.setColorValue( theColor );
		return this;
	}

	public ControlP5 setBackground( int theColor ) {
		controlWindow.setBackground( theColor );
		return this;
	}

	/**
	 * Enables/disables Controllers to be moved around when
	 * ALT-key is down and mouse is dragged. Other key
	 * events are still available like ALT-h to hide and
	 * show the controllers To disable all key events, use
	 * disableKeys()
	 */
	public ControlP5 setMoveable( boolean theFlag ) {
		isMoveable = theFlag;
		return this;
	}

	/**
	 * Checks if controllers are generally moveable
	 *
	 */
	public boolean isMoveable( ) {
		return isMoveable;
	}

	/**
	 * Saves the current values of controllers into a
	 * default properties file
	 *
	 * @see controlP5.ControllerProperties
	 */
	public boolean saveProperties( ) {
		return _myProperties.save( );
	}

	/**
	 * Saves the current values of controllers into a file,
	 * the filepath is given by parameter theFilePath.
	 *
	 * @see controlP5.ControllerProperties
	 */
	public boolean saveProperties( String theFilePath ) {
		return _myProperties.saveAs( theFilePath );
	}

	public boolean saveProperties( String theFilePath , String ... theSets ) {
		return _myProperties.saveAs( theFilePath , theSets );
	}

	/**
	 * Loads properties from a default properties file and
	 * changes values of controllers accordingly.
	 *
	 * @see controlP5.ControllerProperties
	 * @return
	 */
	public boolean loadProperties( ) {
		return _myProperties.load( );
	}

	/**
	 * Loads properties from a properties file and changes
	 * the values of controllers accordingly, the filepath
	 * is given by parameter theFilePath.
	 *
	 * @param theFilePath
	 * @return
	 */
	public boolean loadProperties( final String theFilePath ) {
		String path = theFilePath.endsWith( _myProperties.format.getExtension( ) ) ? theFilePath : theFilePath + "." + _myProperties.format.getExtension( );
		path = checkPropertiesPath( path );
		File f = new File( path);

		if ( f.exists( ) ) {
			return _myProperties.load( path );
		}
		logger.info( "Properties File " + path + " does not exist." );
		return false;
	}

	String checkPropertiesPath( String theFilePath ) {
		theFilePath = ( theFilePath.startsWith( "/" ) || theFilePath.startsWith( "." ) ) ? theFilePath : papplet.sketchPath( theFilePath );
		return theFilePath;
	}

	/**
	 * @exclude
	 * @param theFilePath
	 * @return
	 */
	@ControlP5.Invisible public boolean loadLayout( String theFilePath ) {
		theFilePath = checkPropertiesPath( theFilePath );
		File f = new File( theFilePath );
		if ( f.exists( ) ) {
			getLayout( ).load( theFilePath );
			return true;
		}
		logger.info( "Layout File " + theFilePath + " does not exist." );
		return false;
	}

	/**
	 * @exclude
	 * @param theFilePath
	 */
	public void saveLayout( String theFilePath ) {
		getLayout( ).save( theFilePath );
	}

	/**
	 * Returns the current version of controlP5
	 *
	 * @return String
	 */
	public String version( ) {
		return VERSION;
	}

	/**
	 * shows all controllers and tabs in your sketch.
	 *
	 * @see controlP5.ControlP5#isVisible()
	 * @see controlP5.ControlP5#hide()
	 */

	public void show( ) {
		controlWindow.show( );
	}

	public ControlP5 setBroadcast( boolean theValue ) {
		_myControlBroadcaster.broadcast = theValue;
		return this;
	}

	/**
	 * returns true or false according to the current
	 * visibility flag.
	 *
	 * @see controlP5.ControlP5#show()
	 * @see controlP5.ControlP5#hide()
	 */
	public boolean isVisible( ) {
		return controlWindow.isVisible( );
	}

	public ControlP5 setVisible( boolean b ) {
		if ( b ) {
			show( );
		} else {
			hide( );
		}
		return this;
	}

	/**
	 * hide all controllers and tabs inside your sketch
	 * window.
	 *
	 * @see controlP5.ControlP5#show()
	 * @see controlP5.ControlP5#isVisible()
	 */
	public void hide( ) {
		controlWindow.hide( );
	}

	/**
	 * forces all controllers to update.
	 *
	 * @see controlP5.ControlP5#isUpdate()
	 * @see controlP5.ControlP5#setUpdate()
	 */
	public void update( ) {
		controlWindow.update( );
	}

	/**
	 * checks if automatic updates are enabled. By default
	 * this is true.
	 *
	 * @see controlP5.ControlP5#update()
	 * @see controlP5.ControlP5#setUpdate(boolean)
	 * @return
	 */
	public boolean isUpdate( ) {
		return isUpdate;
	}

	/**
	 * changes the update behavior according to parameter
	 * theFlag
	 *
	 * @see controlP5.ControlP5#update()
	 * @see controlP5.ControlP5#isUpdate()
	 * @param theFlag
	 */
	public void setUpdate( boolean theFlag ) {
		isUpdate = theFlag;
		controlWindow.setUpdate( theFlag );
	}

	public boolean setFont( ControlFont theControlFont ) {
		controlFont = theControlFont;
		isControlFont = true;
		updateFont( controlFont );
		return isControlFont;
	}

	public boolean setFont( PFont thePFont , int theFontSize ) {
		controlFont = new ControlFont( thePFont , theFontSize );
		isControlFont = true;
		updateFont( controlFont );
		return isControlFont;
	}

	public boolean setFont( PFont thePFont ) {
		controlFont = new ControlFont( thePFont );
		isControlFont = true;
		updateFont( controlFont );
		return isControlFont;
	}

	protected void updateFont( ControlFont theControlFont ) {
		controlWindow.updateFont( theControlFont );
	}

	public ControlFont getFont( ) {
		return controlFont;
	}

	/**
	 * disables shortcuts such as alt-h for hiding/showing
	 * controllers
	 *
	 */
	public void disableShortcuts( ) {
		isShortcuts = false;
	}

	public boolean isShortcuts( ) {
		return isShortcuts;
	}

	/**
	 * enables shortcuts.
	 */
	public void enableShortcuts( ) {
		isShortcuts = true;
	}

	public Tooltip getTooltip( ) {
		return _myTooltip;
	}

	public void setTooltip( Tooltip theTooltip ) {
		_myTooltip = theTooltip;
	}

	public void setMouseWheelRotation( int theRotation ) {
		getWindow( ).setMouseWheelRotation( theRotation );
	}

	/**
	 * cp5.begin() and cp5.end() are mechanisms to
	 * auto-layout controllers, see the ControlP5beginEnd
	 * example.
	 */
	public ControllerGroup< ? > begin( ) {
		// TODO replace controlWindow.tab("default") with
		// controlWindow.tabs().get(1);
		return begin( controlWindow.getTab( "default" ) );
	}

	public ControllerGroup< ? > begin( ControllerGroup< ? > theGroup ) {
		setCurrentPointer( theGroup );
		return theGroup;
	}

	public ControllerGroup< ? > begin( int theX , int theY ) {
		// TODO replace controlWindow.tab("default") with
		// controlWindow.tabs().get(1);
		return begin( controlWindow.getTab( "default" ) , theX , theY );
	}

	public ControllerGroup< ? > begin( ControllerGroup< ? > theGroup , int theX , int theY ) {
		setCurrentPointer( theGroup );
		ControllerGroup.set( theGroup.autoPosition , theX , theY );
		theGroup.autoPositionOffsetX = theX;
		return theGroup;
	}

	public ControllerGroup< ? > begin( ControlWindow theWindow ) {
		return begin( theWindow.getTab( "default" ) );
	}

	public ControllerGroup< ? > begin( ControlWindow theWindow , int theX , int theY ) {
		return begin( theWindow.getTab( "default" ) , theX , theY );
	}

	public ControllerGroup< ? > end( ControllerGroup< ? > theGroup ) {
		releaseCurrentPointer( theGroup );
		return theGroup;
	}

	/**
	 * cp5.begin() and cp5.end() are mechanisms to
	 * auto-layout controllers, see the ControlP5beginEnd
	 * example.
	 */
	public ControllerGroup< ? > end( ) {
		return end( controlWindow.getTab( "default" ) );
	}

	public void addPositionTo( int theX , int theY , List< ControllerInterface< ? >> theControllers ) {
		float[] v = new float[] { theX , theY };
		for ( ControllerInterface< ? > c : theControllers ) {
			float[] v1 = new float[ 2 ];
			Controller.set( v1 , Controller.x( c.getPosition( ) ) , Controller.y( c.getPosition( ) ) );
			c.setPosition( Controller.x( v ) + Controller.x( v1 ) , Controller.y( v ) + Controller.y( v1 ) );
		}
	}

	public void addPositionTo( int theX , int theY , ControllerInterface< ? > ... theControllers ) {
		addPositionTo( theX , theY , Arrays.asList( theControllers ) );
	}

	/**
	 * disposes and clears all controlP5 elements. When
	 * running in applet mode, opening new tabs or switching
	 * to another tab causes the applet to call dispose().
	 * therefore dispose() is disabled when running ing
	 * applet mode. TODO implement better dispose handling
	 * for applets.
	 *
	 * @exclude
	 */
	public void dispose( ) {
		if ( !isApplet ) {
			clear( );
		}
	}

	/* static helper functions including Object-to-Type
	 * conversions, invokes */

	static public Object invoke( final Object theObject , final String theMember , final Object ... theParams ) {
		return invoke( theObject , theObject.getClass( ) , theMember , theParams );
	}

	static public Object invoke( final Object theObject , final Class< ? > theClass , final String theMember , final Object ... theParams ) {
		if ( theClass == null ) {
			return null;
		}
		Class[] cs = new Class[ theParams.length ];

		for ( int i = 0 ; i < theParams.length ; i++ ) {
			Class c = theParams[ i ].getClass( );
			cs[ i ] = classmap.containsKey( c ) ? classmap.get( c ) : c;
		}
		try {
			final Field f = theClass.getDeclaredField( theMember );
			/* TODO check super */
			f.setAccessible( true );
			Object o = theParams[ 0 ];
			Class cf = o.getClass( );
			if ( cf.equals( Integer.class ) ) {
				f.setInt( theObject , i( o ) );
			} else if ( cf.equals( Float.class ) ) {
				f.setFloat( theObject , f( o ) );
			} else if ( cf.equals( Long.class ) ) {
				f.setLong( theObject , l( o ) );
			} else if ( cf.equals( Double.class ) ) {
				f.setDouble( theObject , d( o ) );
			} else if ( cf.equals( Boolean.class ) ) {
				f.setBoolean( theObject , b( o ) );
			} else if ( cf.equals( Character.class ) ) {
				f.setChar( theObject , ( char ) i( o ) );
			} else {
				f.set( theObject , o );
			}
		} catch ( NoSuchFieldException e1 ) {
			try {
				final Method m = theClass.getDeclaredMethod( theMember , cs );
				m.setAccessible( true );
				try {
					return m.invoke( theObject , theParams );
				} catch ( IllegalArgumentException e ) {
					System.err.println( e );
				} catch ( IllegalAccessException e ) {
					System.err.println( e );
				} catch ( InvocationTargetException e ) {
					System.err.println( e );
				}

			} catch ( SecurityException e ) {
				System.err.println( e );
			} catch ( NoSuchMethodException e ) {
				invoke( theObject , theClass.getSuperclass( ) , theMember , theParams );
			}
		} catch ( IllegalArgumentException e ) {
			System.err.println( e );
		} catch ( IllegalAccessException e ) {
			System.err.println( e );
		}
		return null;
	}

	static public boolean b( Object o ) {
		return b( o , false );
	}

	static public boolean b( Object o , boolean theDefault ) {
		return ( o instanceof Boolean ) ? ( ( Boolean ) o ).booleanValue( ) : ( o instanceof Number ) ? ( ( Number ) o ).intValue( ) == 0 ? false : true : theDefault;
	}

	static public boolean b( String o ) {
		return b( o , false );
	}

	static public boolean b( String o , boolean theDefault ) {
		return o.equalsIgnoreCase( "true" ) ? true : theDefault;
	}

	static public int i( Object o ) {
		return i( o , Integer.MIN_VALUE );
	}

	static public int i( Object o , int theDefault ) {
		return ( o instanceof Number ) ? ( ( Number ) o ).intValue( ) : ( o instanceof String ) ? i( s( o ) ) : theDefault;
	}

	static public int i( String o ) {
		return i( o , Integer.MIN_VALUE );
	}

	static public int i( String o , int theDefault ) {
		return isNumeric( o ) ? Integer.parseInt( o ) : theDefault;
	}

	static public float f( Object o ) {
		return f( o , Float.MIN_VALUE );
	}

	static public float f( Object o , float theDefault ) {
		return ( o instanceof Number ) ? ( ( Number ) o ).floatValue( ) : ( o instanceof String ) ? f( s( o ) ) : theDefault;
	}

	static public float f( String o ) {
		return f( o , Float.MIN_VALUE );
	}

	static public float f( String o , float theDefault ) {
		return isNumeric( o ) ? Float.parseFloat( o ) : theDefault;
	}

	static public double d( Object o ) {
		return d( o , Double.MIN_VALUE );
	}

	static public double d( Object o , double theDefault ) {
		return ( o instanceof Number ) ? ( ( Number ) o ).doubleValue( ) : ( o instanceof String ) ? d( s( o ) ) : theDefault;
	}

	static public double d( String o ) {
		return d( o , Double.MIN_VALUE );
	}

	static public double d( String o , double theDefault ) {
		return isNumeric( o ) ? Double.parseDouble( o ) : theDefault;
	}

	static public long l( Object o ) {
		return l( o , Long.MIN_VALUE );
	}

	static public long l( Object o , long theDefault ) {
		return ( o instanceof Number ) ? ( ( Number ) o ).longValue( ) : ( o instanceof String ) ? l( s( o ) ) : theDefault;
	}

	static public String s( Object o ) {
		return ( o != null ) ? o.toString( ) : "";
	}

	static public String s( Object o , String theDefault ) {
		return ( o != null ) ? o.toString( ) : theDefault;
	}

	static public boolean isNumeric( Object o ) {
		return isNumeric( o.toString( ) );
	}

	static public boolean isNumeric( String str ) {
		return str.matches( "(-|\\+)?\\d+(\\.\\d+)?" );
	}

	static public List toList( final Object ... args ) {
		List l = new ArrayList( );
		Collections.addAll( l , args );
		return l;
	}

	static public List toList( Object o ) {
		return o != null ? ( o instanceof List ) ? ( List ) o : ( o instanceof String ) ? toList( o.toString( ) ) : Collections.EMPTY_LIST : Collections.EMPTY_LIST;
	}

	static public Map toMap( final String s ) {
		/* similar to mapFrom(Object ... args) but with type
		 * (Number,String) sensitivity */
		String[] arr = s.trim( ).split( delimiter );
		Map m = new LinkedHashMap( );
		if ( arr.length % 2 == 0 ) {
			for ( int i = 0 ; i < arr.length ; i += 2 ) {
				String s1 = arr[ i + 1 ];
				m.put( arr[ i ] , isNumeric( s1 ) ? s1.indexOf( "." ) == -1 ? i( s1 ) : f( s1 ) : s1 );
			}
		}
		return m;
	}

	static public Map toMap( Object o ) {
		return o != null ? ( o instanceof Map ) ? ( Map ) o : Collections.EMPTY_MAP : Collections.EMPTY_MAP;
	}

	static public Map toMap( final Object ... args ) {
		Map m = new LinkedHashMap( );
		if ( args.length % 2 == 0 ) {
			for ( int i = 0 ; i < args.length ; i += 2 ) {
				m.put( args[ i ] , args[ i + 1 ] );
			}
		}
		return m;
	}

	static public String s( String o ) {
		return ( o != null ) ? o : "";
	}

	static Map< Class< ? > , Class< ? > > classmap = new HashMap< Class< ? > , Class< ? > >( ) {
		{
			put( Integer.class , int.class );
			put( Float.class , float.class );
			put( Double.class , double.class );
			put( Boolean.class , boolean.class );
			put( Character.class , char.class );
			put( Long.class , long.class );
		}
	};

	static public void sleep( long theMillis ) {
		try {
			Thread.sleep( theMillis );
		} catch ( Exception e ) {

		}
	}

	static public String timestamp( ) {
		return new SimpleDateFormat( "yyyyMMdd-HHmmss" ).format( new Date( ) );
	}

	/* add Objects with Annotation */

	public static Logger logger( ) {
		return logger;
	}

	@Retention( RetentionPolicy.RUNTIME ) @interface Invisible {
	}

	@Retention( RetentionPolicy.RUNTIME ) @interface Layout {
	}

}
