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.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PGraphics;
import processing.event.Event;
import processing.event.KeyEvent;


/**
 * A singleline input textfield, use arrow keys to go back and forth, use backspace to delete
 * characters. Using the up and down arrows lets you cycle through the history of the textfield.
 * 
 * This is the best you can get. Font handling, font switching, measuring, left align, right align,
 * etc. was giving me a big headache. not perfect, i think this is a good compromise.
 * 
 * @example controllers/ControlP5textfield
 * @nosuperclasses Controller Controller
 */
public class Textfield extends Controller< Textfield > {

	/* TODO textspacing does not work properly for bitfonts sometimes first row of pixels in a
	 * bitfont texture gets cut off */

	protected boolean isTexfieldActive;
	protected boolean isKeepFocus;
	protected StringBuffer _myTextBuffer = new StringBuffer( );
	protected int _myTextBufferIndex = 0;
	protected int _myTextBufferOverflow = 0;
	protected int _myTextBufferIndexPosition = 0;
	public static int cursorWidth = 1;
	protected Map< Integer , TextfieldCommand > keyMapping;
	protected InputFilter _myInputFilter = InputFilter.BITFONT;
	protected List< Integer > ignorelist;
	protected LinkedList< String > _myHistory;
	protected int _myHistoryIndex;
	protected int len = 0;
	protected int offset = 2;
	protected int margin = 2;
	protected boolean isPasswordMode;
	protected boolean autoclear = true;
	protected int _myColorCursor = 0x88ffffff;
	private PGraphics buffer;

	public enum InputFilter {
		INTEGER(Arrays.asList( '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' )), FLOAT(Arrays.asList( '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '.' )), BITFONT(Arrays.asList( '\n' , '\r' , ' ' , '!' , '"' , '#' , '$' , '%' ,
		    '&' , '\'' , '(' , ')' , '*' , '+' , ',' , '-' , '.' , '/' , '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , ':' , ';' , '<' , '=' , '>' , '?' , '@' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' ,
		    'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' , '[' , '\\' , ']' , '^' , '_' , '`' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' ,
		    'v' , 'w' , 'x' , 'y' , 'z' , '{' , '|' , '}' , '~' )), DEFAULT(new LinkedList< Character >( ));

		final List< Character > allowed;

		InputFilter( List< Character > theList ) {
			allowed = theList;
		}

		protected boolean apply( char theCharater ) {
			if ( allowed.isEmpty( ) ) {
				return true;
			} else {
				return allowed.contains( theCharater );
			}
		}

	}

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

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

		_myCaptionLabel = new Label( cp5 , theName , 0 , 0 , color.getCaptionLabel( ) );
		_myValueLabel.setFont( cp5.controlFont == cp5.defaultFont ? cp5.defaultFontForText : cp5.controlFont );
		_myCaptionLabel.align( ControlP5.LEFT , ControlP5.BOTTOM_OUTSIDE );
		_myCaptionLabel.setPaddingX( 0 );

		_myBroadcastType = STRING;
		_myValueLabel.setFixedSize( true );
		_myValueLabel.set( "" );
		_myValueLabel.setWidth( getWidth( ) - margin * 2 );
		_myValueLabel.setPadding( 0 , 0 );
		_myValueLabel.align( LEFT , CENTER );
		_myValueLabel.setColor( color.getValueLabel( ) );
		_myValueLabel.toUpperCase( false );

		_myValueLabel.setLabeltype( _myValueLabel.new SinglelineTextfield( ) );

		_myHistory = new LinkedList< String >( );
		_myHistory.addFirst( "" );

		setSize( getWidth( ) , getHeight( ) );

		keyMapping = new HashMap< Integer , TextfieldCommand >( );
		keyMapping.put( ENTER , new Enter( ) );
		keyMapping.put( DEFAULT , new InsertCharacter( ) );
		keyMapping.put( DELETE , new DeleteCharacter( ) );
		keyMapping.put( BACKSPACE , new DeleteCharacter( ) );
		keyMapping.put( LEFT , new MoveLeft( ) );
		keyMapping.put( RIGHT , new MoveRight( ) );
		keyMapping.put( UP , new MoveUp( ) );
		keyMapping.put( DOWN , new MoveDown( ) );

		ignorelist = new LinkedList< Integer >( );
		ignorelist.add( SHIFT );
		ignorelist.add( ALT );
		ignorelist.add( CONTROL );
		ignorelist.add( TAB );
		ignorelist.add( COMMANDKEY );

		setInputFilter( DEFAULT );

	}

	@Override public Textfield setWidth( int theWidth ) {
		_myValueLabel.setWidth( theWidth );
		return super.setWidth( theWidth );
	}

	@Override public Textfield setHeight( int theHeight ) {
		_myValueLabel.setHeight( theHeight );
		return super.setHeight( theHeight );
	}

	public Textfield setFocus( boolean theValue ) {
		isTexfieldActive = isActive = theValue;
		return this;
	}

	/**
	 * check if the textfield is active and in focus.
	 * 
	 * @return boolean
	 */
	public boolean isFocus( ) {
		return isTexfieldActive;
	}

	public Textfield keepFocus( boolean theValue ) {
		isKeepFocus = theValue;
		if ( isKeepFocus ) {
			setFocus( true );
		}
		return this;
	}

//	public Textfield setFont( PFont thePFont ) {
//		getValueLabel( ).setFont( thePFont );
//		return this;
//	}
//
//	public Textfield setFont( ControlFont theFont ) {
//		getValueLabel( ).setFont( theFont );
//		return this;
//	}

	public Textfield setFont( int theFont ) {
		getValueLabel( ).setFont( theFont );
		return this;
	}

	public Textfield setPasswordMode( boolean theFlag ) {
		isPasswordMode = theFlag;
		return this;
	}

	public Textfield setInputFilter( int theInputType ) {
		switch ( theInputType ) {
		case ( INTEGER ):
			_myInputFilter = InputFilter.INTEGER;
			break;
		case ( FLOAT ):
			_myInputFilter = InputFilter.FLOAT;
			break;
		case ( BITFONT ):
			_myInputFilter = InputFilter.BITFONT;
			break;
		default:
			_myInputFilter = InputFilter.DEFAULT;
			break;
		}
		return this;
	}

	@Override public Textfield setValue( float theValue ) {
		// use setText(String) instead
		return this;
	}

	@Override protected void updateFont( ControlFont theControlFont ) {
		super.updateFont( theControlFont );
	}

	public Textfield setValue( String theText ) {
		_myTextBuffer = new StringBuffer( theText );
		setIndex( _myTextBuffer.length( ) );
		return this;
	}

	public Textfield setText( String theText ) {
		return setValue( theText );
	}

	public Textfield clear( ) {
		// create a new text buffer
		_myTextBuffer = new StringBuffer( );
		// reset the buffer index
		setIndex( 0 );
		return this;
	}

	public Textfield setAutoClear( boolean theValue ) {
		autoclear = theValue;
		return this;
	}

	public boolean isAutoClear( ) {
		return autoclear;
	}

	@Override protected void mousePressed( ) {
		if ( isActive ) {
			// TODO System.out.println("adjust cursor");
		}
		int x = ( int ) ( getControlWindow( ).mouseX - x( getAbsolutePosition( ) ) );
		int y = ( int ) ( getControlWindow( ).mouseY - y( getAbsolutePosition( ) ) );

		// TODO System.out.println(x + ":" + y);
		setFocus( true );
	}

	@Override protected void mouseReleasedOutside( ) {
		if ( isKeepFocus == false ) {
			isTexfieldActive = isActive = false;
		}
	}

	public int getIndex( ) {
		return _myTextBufferIndex;
	}

	public String getText( ) {
		return _myTextBuffer.toString( );
	}

	public Textfield setColor( int theColor ) {
		getValueLabel( ).setColor( theColor );
		return this;
	}

	public Textfield setColorCursor( int theColor ) {
		_myColorCursor = theColor;
		return this;
	}

	@Override public Textfield setSize( int theWidth , int theHeight ) {
		super.setSize( theWidth , theHeight );
		buffer = cp5.papplet.createGraphics( getWidth( ) , getHeight( ) );
		return this;
	}

	@Override public void draw( PGraphics theGraphics ) {

		theGraphics.pushStyle( );
		theGraphics.fill( color.getBackground( ) );
		theGraphics.pushMatrix( );
		theGraphics.translate( x( position ) , y( position ) );
		theGraphics.rect( 0 , 0 , getWidth( ) , getHeight( ) );
		theGraphics.noStroke( );

		theGraphics.fill( _myColorCursor );
		theGraphics.pushMatrix( );
		theGraphics.pushStyle( );

		buffer.beginDraw( );
		buffer.background( 0 , 0 );
		final String text = passCheck( getText( ) );
		final int textWidth = ControlFont.getWidthFor( text.substring( 0 , _myTextBufferIndex ) , _myValueLabel , buffer );
		final int dif = PApplet.max( textWidth - _myValueLabel.getWidth( ) , 0 );
		final int _myTextBufferIndexPosition = ControlFont.getWidthFor( text.substring( 0 , _myTextBufferIndex ) , _myValueLabel , buffer );
		_myValueLabel.setText( text );
		_myValueLabel.draw( buffer , -dif , 0 , this );
		buffer.noStroke( );
		if ( isTexfieldActive ) {
			if ( !cp5.papplet.keyPressed ) {
				buffer.fill( _myColorCursor , PApplet.abs( PApplet.sin( cp5.papplet.frameCount * 0.05f )) * 255 );
			} else {
				buffer.fill( _myColorCursor );
			}
			buffer.rect( PApplet.max( 1 , PApplet.min( _myTextBufferIndexPosition , _myValueLabel.getWidth( ) - 3 ) ) , 0 , 1 , getHeight( ) );
		}
		buffer.endDraw( );
		theGraphics.image( buffer , 0 , 0 );

		theGraphics.popStyle( );
		theGraphics.popMatrix( );

		theGraphics.fill( isTexfieldActive ? color.getActive( ) : color.getForeground( ) );
		theGraphics.rect( 0 , 0 , getWidth( ) , 1 );
		theGraphics.rect( 0 , getHeight( ) - 1 , getWidth( ) , 1 );
		theGraphics.rect( -1 , 0 , 1 , getHeight( ) );
		theGraphics.rect( getWidth( ) , 0 , 1 , getHeight( ) );
		_myCaptionLabel.draw( theGraphics , 0 , 0 , this );
		theGraphics.popMatrix( );
		theGraphics.popStyle( );
	}

	private String passCheck( String label ) {
		if ( !isPasswordMode ) {
			return label;
		}
		String newlabel = "";
		for ( int i = 0 ; i < label.length( ) ; i++ ) {
			newlabel += "*";
		}
		return newlabel;
	}

	public void keyEvent( KeyEvent theKeyEvent ) {
		if ( isUserInteraction && isTexfieldActive && isActive && theKeyEvent.getAction( ) == KeyEvent.PRESS ) {
			if ( ignorelist.contains( cp5.getKeyCode( ) ) ) {
				return;
			}
			if ( keyMapping.containsKey( cp5.getKeyCode( ) ) ) {
				keyMapping.get( cp5.getKeyCode( ) ).execute( );
			} else {
				keyMapping.get( DEFAULT ).execute( );
			}
		}
	}

	/**
	 * make the controller execute a return event. submit the current content of the texfield.
	 * 
	 */
	public Textfield submit( ) {
		keyMapping.get( ENTER ).execute( );
		return this;
	}

	public String[] getTextList( ) {
		String[] s = new String[ _myHistory.size( ) ];
		_myHistory.toArray( s );
		return s;
	}

	private Textfield setIndex( int theIndex ) {
		_myTextBufferIndex = theIndex;
		return this;
	}

	interface TextfieldCommand {
		void execute( );
	}

	class InsertCharacter implements TextfieldCommand {

		public void execute( ) {
			if ( ( int ) ( cp5.getKey( ) ) == 65535 ) {
				return;
			}

			if ( _myInputFilter.apply( cp5.getKey( ) ) ) {
				_myTextBuffer.insert( _myTextBufferIndex , ( char ) cp5.getKey( ) );
				setIndex( _myTextBufferIndex + 1 );
			}
		}
	}

	class Enter implements TextfieldCommand {

		public void execute( ) {
			setStringValue( _myTextBuffer.toString( ) );
			broadcast( );
			// update current buffer with the last item inside the input history
			_myHistory.set( _myHistory.size( ) - 1 , _myTextBuffer.toString( ) );
			// set the history index to our last item
			_myHistoryIndex = _myHistory.size( );
			// add a new and empty buffer to the history
			_myHistory.add( "" );
			if ( autoclear ) {
				clear( );
			}
		}
	}

	class DeleteCharacter implements TextfieldCommand {

		public void execute( ) {
			if ( _myTextBuffer.length( ) > 0 && _myTextBufferIndex > 0 ) {
				_myTextBuffer.deleteCharAt( _myTextBufferIndex - 1 );
				setIndex( _myTextBufferIndex - 1 );
			}
		}
	}

	class MoveLeft implements TextfieldCommand {

		public void execute( ) {
			setIndex( ( ( cp5.modifiers & Event.META ) > 0 ) ? 0 : PApplet.max( 0 , _myTextBufferIndex - 1 ) );
		}
	}

	class MoveRight implements TextfieldCommand {

		public void execute( ) {
			setIndex( ( ( cp5.modifiers & Event.META ) > 0 ) ? _myTextBuffer.length( ) : PApplet.min( _myTextBuffer.length( ) , _myTextBufferIndex + 1 ) );
		}
	}

	class MoveUp implements TextfieldCommand {

		public void execute( ) {
			if ( _myHistoryIndex == 0 ) {
				return;
			}
			_myHistoryIndex = PApplet.max( 0 , --_myHistoryIndex );
			_myTextBuffer = new StringBuffer( _myHistory.get( _myHistoryIndex ) );
			setIndex( _myTextBuffer.length( ) );
		}
	}

	class MoveDown implements TextfieldCommand {

		public void execute( ) {
			if ( _myHistoryIndex >= _myHistory.size( ) - 1 ) {
				return;
			}

			_myHistoryIndex = PApplet.min( _myHistory.size( ) - 1 , ++_myHistoryIndex );
			_myTextBuffer = new StringBuffer( _myHistory.get( _myHistoryIndex ) );
			setIndex( _myTextBuffer.length( ) );
		}
	}

}