/*******************************************************************************
*									       
* TextEdit.cxx - Edit text from a text buffer				       
*
* This code is distributed under the LGPL
*									       
* Maarten de Boer 1999
*
*******************************************************************************/

#include "TextEdit.H"
#include <ctype.h>
#include <FL/Fl.H>
#include <string.h>

int TextEdit::StartOfWord( int pos )
{
	while ( pos && ( isalnum( GetBuffer()->GetCharacter( pos ) ) || GetBuffer()->GetCharacter( pos ) == '_' ) )
	{
		pos--;
	}
	if ( !( isalnum( GetBuffer()->GetCharacter( pos ) ) || GetBuffer()->GetCharacter( pos ) == '_' ) ) pos++;
	return pos;
}

int TextEdit::EndOfWord( int pos )
{
	while ( pos < GetBuffer()->Length() && (
	            isalnum( GetBuffer()->GetCharacter( pos ) ) || GetBuffer()->GetCharacter( pos ) == '_' ) )
	{
		pos++;
	}
	return pos;
}

void TextEdit::WordForward()
{
	int pos = GetInsertPosition();
	while ( pos < GetBuffer()->Length() && (
	            isalnum( GetBuffer()->GetCharacter( pos ) ) || GetBuffer()->GetCharacter( pos ) == '_' ) )
	{
		pos++;
	}
	while ( pos < GetBuffer()->Length() && !( isalnum( GetBuffer()->GetCharacter( pos ) ) || GetBuffer()->GetCharacter( pos ) == '_' ) )
	{
		pos++;
	}
SetInsertPosition( pos );
	return;
}

void TextEdit::WordBackward()
{
	int pos = GetInsertPosition();
	pos--;
	while ( pos && !( isalnum( GetBuffer()->GetCharacter( pos ) ) || GetBuffer()->GetCharacter( pos ) == '_' ) )
	{
		pos--;
	}
	while ( pos && ( isalnum( GetBuffer()->GetCharacter( pos ) ) || GetBuffer()->GetCharacter( pos ) == '_' ) )
	{
		pos--;
	}
	if ( !( isalnum( GetBuffer()->GetCharacter( pos ) ) || GetBuffer()->GetCharacter( pos ) == '_' ) ) pos++;
SetInsertPosition( pos );
	return;
}

TextEdit::TextEdit( Position left, Position top, Position width, Position height,
                    Fl_Scrollbar* hScrollbar, Fl_Scrollbar* vScrollbar,
                    TextBuffer *buffer,
                    int continuousWrap, int wrapMargin )
		: TextDisp( left, top, width, height, hScrollbar, vScrollbar, buffer, continuousWrap,
		            wrapMargin )
{}


static const char* const compose_pairs =
    "  ! % # $ y=| & : c a <<~ - r _ * +-2 3 ' u p . , 1 o >>141234? "
    "A`A'A^A~A:A*AEC,E`E'E^E:I`I'I^I:D-N~O`O'O^O~O:x O/U`U'U^U:Y'DDss"
    "a`a'a^a~a:a*aec,e`e'e^e:i`i'i^i:d-n~o`o'o^o~o:-:o/u`u'u^u:y'ddy:";

int fl_compose2( int state, char c, int& del, char* buffer, int& ins )
{
	del = 0;
	ins = 1;
	buffer[ 0 ] = c;

	if ( c == '"' ) c = ':';

	if ( !state )
	{	// first character
		if ( c == ' ' )
		{
			buffer[ 0 ] = char( 0xA0 );
			return 0x100;
		} // space turns into nbsp
		// see if it is either character of any pair:
		state = 0;
		for ( const char * p = compose_pairs; *p; p += 2 )
			if ( p[ 0 ] == c || p[ 1 ] == c )
			{
				if ( p[ 1 ] == ' ' ) buffer[ 0 ] = ( p - compose_pairs ) / 2 + 0xA0;
				state = c;
			}
		return state;

	}
	else if ( state == 0x100 )
	{ // third character
		return 0;

	}
	else
	{ // second character
		char c1 = char( state );    // first character
		// now search for the pair in either order:
		for ( const char * p = compose_pairs; *p; p += 2 )
		{
			if ( p[ 0 ] == c && p[ 1 ] == c1 || p[ 1 ] == c && p[ 0 ] == c1 )
			{
				buffer[ 0 ] = ( p - compose_pairs ) / 2 + 0xA0;
				ins = del = 1;
				return 0x100;
			}
		}
		return 0;
	}
}

////////////////////////////////////////////////////////////////

static int compose;    // compose state (# of characters so far + 1)

#define NORMAL_INPUT_MOVE 0

#define ctrl(x) (x^0x40)

int TextEdit::handle_key()
{
	int sel_start = 0, sel_end = 0;
	GetBuffer()->GetSelectionPos( &sel_start, &sel_end );

	int pcompose = compose; compose = 0;
	char key = Fl::event_text()[ 0 ];

	if ( pcompose && Fl::event_length() )
	{
		char buf[ 20 ];
		int ins;
		int del;
		compose = fl_compose2( pcompose - 1, key, del, buf, ins );
		if ( compose )
		{
			if ( del && sel_start == sel_end )
			{
				int end = GetInsertPosition();
				MoveLeft();
				GetBuffer()->Select( GetInsertPosition(), end );
			}
			GetBuffer()->RemoveSelected();

			Insert( buf );
			MakeInsertPosVisible();


			//      replace(position(), del ? position()-del : mark(), buf, ins);
			compose++;    // store value+1 so 1 can initialize compose state
			return 1;
		}
		else
		{
			if ( pcompose == 1 )
			{// compose also acts as quote-next:
				//	return replace(position(),mark(),Fl::event_text(),Fl::event_length());
			}

		}
	}

	if ( Fl::event_state( FL_ALT | FL_META ) )
	{ // reserved for shortcuts
		compose = pcompose;
		return 0;
	}

	switch ( Fl::event_key() )
	{
	case FL_Left:
		key = ctrl( 'B' );
		break;
	case FL_Right:
		key = ctrl( 'F' );
		break;
	case FL_Up:
		key = ctrl( 'P' );
		break;
	case FL_Down:
		key = ctrl( 'N' );
		break;
	case FL_Delete:
		key = ctrl( 'D' );
		break;
	case FL_Home:
		key = ctrl( 'A' );
		break;
	case FL_End:
		key = ctrl( 'E' );
		break;
	case FL_BackSpace:
		if ( sel_start == sel_end )
		{
			int end = GetInsertPosition();
			MoveLeft();
			GetBuffer()->Select( GetInsertPosition(), end );
		}
		GetBuffer()->RemoveSelected();
		return 1;
	case FL_Enter:
	case FL_KP_Enter:
		Insert( "\n" );
		MakeInsertPosVisible();
		return 1;
		//    if (when() & FL_WHEN_ENTER_KEY) {
		//      position(size(), 0);
		//      maybe_do_callback();
		//      return 1;
		//    } else
		//      return replace(position(), mark(), "\n", 1);
	case FL_Tab:
		if ( Fl::event_state( FL_CTRL ) ) return 0;
		break;
	case FL_Escape:
		return 0;   	// reserved for shortcuts (Forms cleared field)
	case FL_Control_R:
	case 0xff20:    // Multi-Key
		compose = 1;
		return 1;
	}

	switch ( key )
	{
	case 0:   	// key did not translate to any text
		compose = pcompose;    // allow user to hit shift keys after ^Q
		return 0;
	case ctrl( 'A' ) :
					if ( !( Fl::event_state() & FL_SHIFT ) )
			{
				if ( sel_start != sel_end )
				{
					GetBuffer()->Unselect();
				}
				SetInsertPosition( StartOfLine( GetInsertPosition() ) );
			}
			else if ( GetInsertPosition() == sel_start )
			{
				SetInsertPosition( StartOfLine( GetInsertPosition() ) );
				GetBuffer()->Select( GetInsertPosition(), sel_end );
			}
			else if ( GetInsertPosition() == sel_end )
			{
				SetInsertPosition( StartOfLine( GetInsertPosition() ) );
				GetBuffer()->Select( sel_start, GetInsertPosition() );
			}
			else
			{
				int pos = GetInsertPosition();
				SetInsertPosition( StartOfLine( GetInsertPosition() ) );
				GetBuffer()->Select( GetInsertPosition(), pos );
			}
		MakeInsertPosVisible();


		return 1;
	case ctrl( 'B' ) :
					if ( ( Fl::event_state() & FL_CTRL ) )
			{
				if ( !( Fl::event_state() & FL_SHIFT ) )
				{
					if ( sel_start != sel_end )
					{
						GetBuffer()->Unselect();
					}
					WordBackward();
				}
				else if ( GetInsertPosition() == sel_start )
				{
					WordBackward();
					GetBuffer()->Select( GetInsertPosition(), sel_end );
				}
				else if ( GetInsertPosition() == sel_end )
				{
					WordBackward();
					GetBuffer()->Select( sel_start, GetInsertPosition() );
				}
				else
				{
					int pos = GetInsertPosition();
					WordBackward();
					GetBuffer()->Select( GetInsertPosition(), pos );
				}
			}
			else
				if ( !( Fl::event_state() & FL_SHIFT ) )
				{
					if ( sel_start != sel_end )
					{
						GetBuffer()->Unselect();
					}
					MoveLeft();
				}
				else if ( GetInsertPosition() == sel_start )
				{
					MoveLeft();
					GetBuffer()->Select( GetInsertPosition(), sel_end );
				}
				else if ( GetInsertPosition() == sel_end )
				{
					MoveLeft();
					GetBuffer()->Select( sel_start, GetInsertPosition() );
				}
				else
				{
					int pos = GetInsertPosition();
					MoveLeft();
					GetBuffer()->Select( GetInsertPosition(), pos );
				}
		MakeInsertPosVisible();
		return 1;
	case ctrl( 'D' ) :
					GetBuffer()->RemoveSelected();
		return 1;
		//    if (mark() != position()) return cut();
		//    else return cut(1);
	case ctrl( 'E' ) :

					if ( !( Fl::event_state() & FL_SHIFT ) )
			{
				if ( sel_start != sel_end )
				{
					GetBuffer()->Unselect();
				}
				SetInsertPosition( EndOfLine( GetInsertPosition(), 0 ) );
			}
			else if ( GetInsertPosition() == sel_end )
			{
				SetInsertPosition( EndOfLine( GetInsertPosition(), 0 ) );
				GetBuffer()->Select( sel_start, GetInsertPosition() );
			}
			else if ( GetInsertPosition() == sel_start )
			{
				SetInsertPosition( EndOfLine( GetInsertPosition(), 0 ) );
				GetBuffer()->Select( GetInsertPosition(),
				                     sel_end );
			}
			else
			{
				int pos = GetInsertPosition();
				SetInsertPosition( EndOfLine( GetInsertPosition(), 0 ) );
				GetBuffer()->Select( pos, GetInsertPosition() );
			}
		MakeInsertPosVisible();
		return 1;
	case ctrl( 'F' ) :
					if ( ( Fl::event_state() & FL_CTRL ) )
			{
				if ( !( Fl::event_state() & FL_SHIFT ) )
				{
					if ( sel_start != sel_end )
					{
						GetBuffer()->Unselect();
					}
					WordForward();
				}
				else if ( GetInsertPosition() == sel_end )
				{
					WordForward();
					GetBuffer()->Select( sel_start, GetInsertPosition() );
				}
				else if ( GetInsertPosition() == sel_start )
				{
					WordForward();
					GetBuffer()->Select( GetInsertPosition(),
					                     sel_end );
				}
				else
				{
					int pos = GetInsertPosition();
					WordForward();
					GetBuffer()->Select( pos, GetInsertPosition() );
				}
			}
			else

				if ( !( Fl::event_state() & FL_SHIFT ) )
				{
					if ( sel_start != sel_end )
					{
						GetBuffer()->Unselect();
					}
					MoveRight();
				}
				else if ( GetInsertPosition() == sel_end )
				{
					MoveRight();
					GetBuffer()->Select( sel_start, GetInsertPosition() );
				}
				else if ( GetInsertPosition() == sel_start )
				{
					MoveRight();
					GetBuffer()->Select( GetInsertPosition(),
					                     sel_end );
				}
				else
				{
					int pos = GetInsertPosition();
					MoveRight();
					GetBuffer()->Select( pos, GetInsertPosition() );
				}
		MakeInsertPosVisible();
		return 1;
	case ctrl( 'K' ) :
					/*    if (position()>=size()) return 0;
					{
					if (index(position()) == '\n')
					i = position() + 1;
					else 
					for (i=position()+1; index(i) && index(i) != '\n'; i++);
					cut(position(), i);
					return copy_cuts();
					*/ 
					return 1;
	case ctrl( 'N' ) :
					if ( !( Fl::event_state() & FL_SHIFT ) )
			{
				if ( sel_start != sel_end )
				{
					GetBuffer()->Unselect();
					redraw();
				}
				MoveDown();
			}
			else if ( GetInsertPosition() == sel_end )
			{
				MoveDown();
				GetBuffer()->Select( sel_start, GetInsertPosition() );
			}
			else if ( GetInsertPosition() == sel_start )
			{
				MoveDown();
				GetBuffer()->Select( GetInsertPosition(),
				                     sel_end );
			}
			else
			{
				int pos = GetInsertPosition();
				MoveDown();
				GetBuffer()->Select( pos, GetInsertPosition() );
			}
		MakeInsertPosVisible();
		return 1;
	case ctrl( 'P' ) :
					if ( !( Fl::event_state() & FL_SHIFT ) )
			{
				if ( sel_start != sel_end )
				{
					GetBuffer()->Unselect();
					redraw();
				}
				MoveUp();
			}
			else if ( GetInsertPosition() == sel_start )
			{
				MoveUp();
				GetBuffer()->Select( GetInsertPosition(), sel_end );
			}
			else if ( GetInsertPosition() == sel_end )
			{
				MoveUp();
				GetBuffer()->Select( sel_start,
				                     GetInsertPosition() );
			}
			else
			{
				int pos = GetInsertPosition();
				MoveUp();
				GetBuffer()->Select( GetInsertPosition(), pos );
			}
		MakeInsertPosVisible();
		return 1;
	case ctrl( 'Q' ) :
					compose = 1;
		return 1;
		//  case ctrl('U'):
		//    return cut(0, size());
	case ctrl( 'V' ) :
				case ctrl( 'Y' ) :
						GetBuffer()->RemoveSelected();
		Fl::paste( *this );
		return 1;
	case ctrl( 'X' ) :
				case ctrl( 'W' ) :
						mCopy = GetBuffer()->GetSelectionText();
		Fl::selection( *this, mCopy, strlen( mCopy ) );
		GetBuffer()->RemoveSelected();
		return 1;
	case ctrl( 'C' ) :    // copy
					mCopy = GetBuffer()->GetSelectionText();
		Fl::selection( *this, mCopy, strlen( mCopy ) );
		return 1;
		//    return copy();
	case ctrl( 'Z' ) :
				case ctrl( '_' ) :
						//    return undo();
						return 1;
	}
	/*  // skip all illegal characters
	// this could be improved to make sure characters are inserted at
	// legal positions...
	if (type() == FL_FLOAT_INPUT) {
	if (!strchr("0123456789.eE+-", key)) return 0;
} else if (type() == FL_INT_INPUT) {
	if (!position() && (key == '+' || key == '-'));
	else if (key >= '0' && key <= '9');
	// we allow 0xabc style hex numbers to be typed:
	else if (position()==1 && index(0)=='0' && (key == 'x' || key == 'X'));
	else if (position()>1 && index(0)=='0' && (index(1)=='x'||index(1)=='X')
	&& (key>='A'&& key<='F' || key>='a'&& key<='f'));
	else return 0;
}
	*/
	//  return replace(position(), mark(), Fl::event_text(), Fl::event_length());

	char tmp[ 16 ];
	strncpy( tmp, Fl::event_text(), Fl::event_length() );
	tmp[ Fl::event_length() ] = 0;

	GetBuffer()->RemoveSelected();
	Insert( tmp );
	MakeInsertPosVisible();

	return 1;
}

static int dragstartpos;
static bool timeout = 0;

int TextEdit::handle( int event )
{
	int sel_start = 0, sel_end = 0;
	GetBuffer()->GetSelectionPos( &sel_start, &sel_end );
	static int word_start, word_end;

	switch ( event )
	{


	case FL_FOCUS:
		return 1;
		switch ( Fl::event_key() )
		{
		case FL_Right:
			MoveRight();
			break;
		case FL_Left:
			MoveLeft();
			break;
		case FL_Down:
			MoveDown();
			break;
		case FL_Up:
			MoveUp();
			break;
		case FL_Tab:
		case 0xfe20:    // XK_ISO_Left_Tab
			//      position(size(),0);
			break;
		}
		break;

	case FL_UNFOCUS:
		compose = 0;
		break;

	case FL_KEYBOARD:
		{
			int ret = handle_key();
			return ret;
		}
	case FL_PASTE:
		GetBuffer()->RemoveSelected();
		Insert( Fl::event_text() );
		return 1;
	case FL_PUSH:
		compose = 0;
		if ( Fl::event_button() == 2 )
		{
			Fl::paste( *this );
			if ( Fl::focus() == this ) return 1;    // remove line for Motif behavior
		}

		if ( Fl::focus() != this )
		{
			Fl::focus( this );
			handle( FL_FOCUS );    // cause minimal update
		}


		if ( Fl::event_state() & FL_SHIFT )
		{
			int pos = XYToPosition( Fl::event_x(), Fl::event_y() );
			if ( sel_end != sel_start )
			{

				int sel_mid = ( sel_end + sel_start ) / 2;
				if ( pos <= sel_mid )
				{
					GetBuffer()->Select( pos, sel_end );
					dragstartpos = sel_end;
					SetInsertPosition( pos );
				}
				else
				{
					GetBuffer()->Select( pos, sel_start );
					dragstartpos = sel_end;
					SetInsertPosition( pos );
				}
			}
			else
			{
				GetBuffer()->Select( pos, GetInsertPosition() );
				SetInsertPosition( pos );
			}
		}
		else if ( !Fl::event_clicks() )
		{
			int pos = XYToPosition( Fl::event_x(), Fl::event_y() );
			GetBuffer()->Unselect();
			SetInsertPosition( pos );
			dragstartpos = pos;
		}
		else
		{
			int pos = XYToPosition( Fl::event_x(), Fl::event_y() );
			word_start = StartOfWord( pos ), word_end = EndOfWord( pos );
			GetBuffer()->Select( word_start, word_end );

			SetInsertPosition( word_end );
			dragstartpos = -1;
		}
		return 1;
	case FL_DRAG:
		if ( Fl::event_y() < y() || Fl::event_y() > y() + h() )
		{
			if ( !timeout )
			{
				timeout = 1;
				ScrollTimeout();
			}
			return 1;
		}
		else
			if ( dragstartpos != -1 )
			{
				int pos = XYToPosition( Fl::event_x(), Fl::event_y() );
				if ( GetInsertPosition() > dragstartpos )
				{
					GetBuffer()->Select( dragstartpos, pos );
				}
				else
				{
					GetBuffer()->Select( pos, dragstartpos );
				}
				SetInsertPosition( pos );
			}
			else
			{
				int pos = XYToPosition( Fl::event_x(), Fl::event_y() );
				if ( pos >= word_end ) pos = EndOfWord( pos );
				if ( pos <= word_start ) pos = StartOfWord( pos );
				if ( pos >= word_end )
				{
					GetBuffer()->Select( word_start, pos );
				}
				else if ( pos <= word_start )
				{
					GetBuffer()->Select( pos, word_end );
				}
				SetInsertPosition( pos );
			}
		return 1;
	case FL_RELEASE:
		Fl::remove_timeout( s_ScrollTimeout, this );
		timeout = 0;
		break;
	}


	return 1;
}

void TextEdit::ScrollTimeout( void )
{
	if ( Fl::event_y() < y() )
	{
		Fl::add_timeout( 0.1, s_ScrollTimeout, this );

		int n = ( mMaxsize + y() - Fl::event_y() ) / mMaxsize;
		while ( n-- )
		{
			MoveUp();
		}

		GetBuffer()->Select( dragstartpos, GetInsertPosition() );
		MakeInsertPosVisible();
	}
	else if ( Fl::event_y() > y() + h() )
	{
		Fl::add_timeout( 0.1, s_ScrollTimeout, this );

		int n = ( mMaxsize + Fl::event_y() - ( y() + h() ) ) / mMaxsize;

		while ( n-- )
		{
			MoveDown();
		}
		GetBuffer()->Select( dragstartpos, GetInsertPosition() );
		MakeInsertPosVisible();
	}
	else
	{
		timeout = 0;
	}
}

void TextEdit::s_ScrollTimeout( void* ptr )
{
	( ( TextEdit* ) ptr ) ->ScrollTimeout();
}


