/** \file builtin_commandline.c Functions defining the commandline builtin

Functions used for implementing the commandline builtin. 

*/
#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <wctype.h>
#include <sys/types.h>
#include <termios.h>
#include <signal.h>

#include "fallback.h"
#include "util.h"

#include "wutil.h"
#include "builtin.h"
#include "common.h"
#include "wgetopt.h"
#include "reader.h"
#include "proc.h"
#include "parser.h"
#include "tokenizer.h"
#include "input_common.h"
#include "input.h"

#include "parse_util.h"

/**
   Which part of the comandbuffer are we operating on
*/
enum
{
	STRING_MODE=1, /**< Operate on entire buffer */
	JOB_MODE, /**< Operate on job under cursor */
	PROCESS_MODE, /**< Operate on process under cursor */
	TOKEN_MODE /**< Operate on token under cursor */
}
	;

/**
   For text insertion, how should it be done
*/
enum
{
	REPLACE_MODE=1, /**< Replace current text */
	INSERT_MODE, /**< Insert at cursor position */
	APPEND_MODE /**< Insert at end of current token/command/buffer */
}
	;

/**
   Pointer to what the commandline builtin considers to be the current
   contents of the command line buffer.
 */
static wchar_t *current_buffer=0;
/**
   What the commandline builtin considers to be the current cursor
   position.
 */
static int current_cursor_pos = -1;

/**
   Returns the current commandline buffer.
*/
static wchar_t *get_buffer()
{
	return current_buffer;
}

/**
   Returns the position of the cursor
*/
static int get_cursor_pos()
{
	return current_cursor_pos;
}


/**
   Replace/append/insert the selection with/at/after the specified string.

   \param begin beginning of selection
   \param end end of selection
   \param insert the string to insert
   \param append_mode can be one of REPLACE_MODE, INSERT_MODE or APPEND_MODE, affects the way the test update is performed
*/
static void replace_part( const wchar_t *begin,
						  const wchar_t *end,
						  wchar_t *insert,
						  int append_mode )
{
	const wchar_t *buff = get_buffer();
	string_buffer_t out;
	int out_pos=get_cursor_pos();
					
	sb_init( &out );

	sb_append_substring( &out, buff, begin-buff );
					
	switch( append_mode)
	{
		case REPLACE_MODE:
		{
							
			sb_append( &out, insert );
			out_pos = wcslen( insert ) + (begin-buff);							
			break;
							
		}
		case APPEND_MODE:
		{
			sb_append_substring( &out, begin, end-begin );
			sb_append( &out, insert );
			break;
		}
		case INSERT_MODE:
		{
			int cursor = get_cursor_pos() -(begin-buff);
			sb_append_substring( &out, begin, cursor );
			sb_append( &out, insert );
			sb_append_substring( &out, begin+cursor, end-begin-cursor );
			out_pos +=  wcslen( insert );							
			break;
		}
	}
	sb_append( &out, end );
	reader_set_buffer( (wchar_t *)out.buff, out_pos );
	sb_destroy( &out );
}
	
/**
   Output the specified selection.

   \param begin start of selection
   \param end  end of selection
   \param cut_at_cursor whether printing should stop at the surrent cursor position
   \param tokenize whether the string should be tokenized, printing one string token on every line and skipping non-string tokens
*/
static void write_part( const wchar_t *begin, 
						const wchar_t *end, 
						int cut_at_cursor, 
						int tokenize )
{	
	tokenizer tok;
	string_buffer_t out;
	wchar_t *buff;
	size_t pos;

	pos = get_cursor_pos()-(begin-get_buffer());

	if( tokenize )
	{
		buff = wcsndup( begin, end-begin );
//		fwprintf( stderr, L"Subshell: %ls, end char %lc\n", buff, *end );
		sb_init( &out );
					
		for( tok_init( &tok, buff, TOK_ACCEPT_UNFINISHED );
			 tok_has_next( &tok );
			 tok_next( &tok ) )
		{
			if( (cut_at_cursor) &&
				(tok_get_pos( &tok)+wcslen(tok_last( &tok)) >= pos) )
				break;
		
			switch( tok_last_type( &tok ) )
			{
				case TOK_STRING:
				{
					wchar_t *tmp = unescape( tok_last( &tok ), UNESCAPE_INCOMPLETE );
					sb_append( &out, tmp, L"\n", NULL );
					free( tmp );
					break;
				}
				
			}						
		}

		sb_append( sb_out,
				   (wchar_t *)out.buff );
					
		free( buff );
		tok_destroy( &tok );
		sb_destroy( &out );
	}
	else
	{
		wchar_t *buff, *esc;
		
		if( cut_at_cursor )
		{
			end = begin+pos;			
		}

		buff = wcsndup( begin, end-begin );
		esc = unescape( buff, UNESCAPE_INCOMPLETE );

//		debug( 0, L"woot2 %ls -> %ls", buff, esc );

		sb_append( sb_out, esc );
		sb_append( sb_out, L"\n" );
		
		free( esc );
		free( buff );
		
	}
}


/**
   The commandline builtin. It is used for specifying a new value for
   the commandline.
*/
static int builtin_commandline( parser_t &parser, wchar_t **argv )
{

	int buffer_part=0;
	int cut_at_cursor=0;
	
	int argc = builtin_count_args( argv );
	int append_mode=0;

	int function_mode = 0;
	
	int tokenize = 0;	
	
	int cursor_mode = 0;
	int line_mode = 0;
	int search_mode = 0;
	wchar_t *begin, *end;

	current_buffer = (wchar_t *)builtin_complete_get_temporary_buffer();
	if( current_buffer )
	{
		current_cursor_pos = wcslen( current_buffer );
	}
	else
	{
		current_buffer = reader_get_buffer();
		current_cursor_pos = reader_get_cursor_pos();
	}

	if( !get_buffer() )
	{
		if (is_interactive_session)
		{
			/*
			  Prompt change requested while we don't have
			  a prompt, most probably while reading the
			  init files. Just ignore it.
			*/
			return 1;
		}
		
		sb_append( sb_err,
			    argv[0],
			    L": Can not set commandline in non-interactive mode\n",
			    NULL );
		builtin_print_help( parser, argv[0], sb_err );
		return 1;
	}

	woptind=0;

	while( 1 )
	{
		static const struct woption
			long_options[] =
			{
				{
					L"append", no_argument, 0, 'a'
				}
				,
				{
					L"insert", no_argument, 0, 'i'
				}
				,
				{
					L"replace", no_argument, 0, 'r'
				}
				,
				{
					L"current-job", no_argument, 0, 'j'
				}
				,
				{
					L"current-process", no_argument, 0, 'p'
				}
				,
				{
					L"current-token", no_argument, 0, 't'
				}
				,		
				{
					L"current-buffer", no_argument, 0, 'b'
				}
				,		
				{
					L"cut-at-cursor", no_argument, 0, 'c'
				}
				,
				{
					L"function", no_argument, 0, 'f'
				}
				,
				{
					L"tokenize", no_argument, 0, 'o'
				}
				,
				{
					L"help", no_argument, 0, 'h'
				}
				,
				{					
					L"input", required_argument, 0, 'I'
				}
				,
				{
					L"cursor", no_argument, 0, 'C'
				}
				,
				{
					L"line", no_argument, 0, 'L'
				}
				,
				{
					L"search-mode", no_argument, 0, 'S'
				}
				,
				{
					0, 0, 0, 0 
				}
			}
		;		
		
		int opt_index = 0;
		
		int opt = wgetopt_long( argc,
								argv, 
								L"abijpctwforhI:CLS", 
								long_options, 
								&opt_index );
		if( opt == -1 )
			break;
			
		switch( opt )
		{
			case 0:
				if(long_options[opt_index].flag != 0)
					break;
                sb_printf( sb_err,
                           BUILTIN_ERR_UNKNOWN,
                           argv[0],
                           long_options[opt_index].name );
				builtin_print_help( parser, argv[0], sb_err );

				return 1;
				
			case L'a':
				append_mode = APPEND_MODE;
				break;

			case L'b':
				buffer_part = STRING_MODE;
				break;
				
				
			case L'i':
				append_mode = INSERT_MODE;
				break;

			case L'r':
				append_mode = REPLACE_MODE;
				break;
								
			case 'c':
				cut_at_cursor=1;
				break;
				
			case 't':
				buffer_part = TOKEN_MODE;
				break;

			case 'j':
				buffer_part = JOB_MODE;
				break;

			case 'p':
				buffer_part = PROCESS_MODE;
				break;

			case 'f':
				function_mode=1;
				break;

			case 'o':
				tokenize=1;
				break;

			case 'I':
				current_buffer = woptarg;
				current_cursor_pos = wcslen( woptarg );
				break;
				
			case 'C':
				cursor_mode = 1;
				break;
				
			case 'L':
				line_mode = 1;
				break;
				
			case 'S':
				search_mode = 1;
				break;
				
			case 'h':
				builtin_print_help( parser, argv[0], sb_out );
				return 0;

			case L'?':
				builtin_unknown_option( parser, argv[0], argv[woptind-1] );
				return 1;	
		}
	}		

	if( function_mode )
	{
		int i;
		
		/*
		  Check for invalid switch combinations
		*/
		if( buffer_part || cut_at_cursor || append_mode || tokenize || cursor_mode || line_mode || search_mode )
		{
			sb_printf(sb_err,
					  BUILTIN_ERR_COMBO,
					  argv[0] );
			
			builtin_print_help( parser, argv[0], sb_err );
			return 1;
		}
		

		if( argc == woptind )
		{
			sb_printf( sb_err,
					   BUILTIN_ERR_MISSING,
					   argv[0] );
			
			builtin_print_help( parser, argv[0], sb_err );
			return 1;
 		}
		for( i=woptind; i<argc; i++ )
		{
			wint_t c = input_function_get_code( argv[i] );
			if( c != -1 )
			{
				/*
				  input_unreadch inserts the specified keypress or
				  readline function at the top of the stack of unused
				  keypresses
				*/
				input_unreadch(c);
			}
			else
			{
				sb_printf( sb_err,
					   _(L"%ls: Unknown input function '%ls'\n"),
					   argv[0],
					   argv[i] );
				builtin_print_help( parser, argv[0], sb_err );
				return 1;
			}
		}
		
		return 0;		
	}
	
	/*
	  Check for invalid switch combinations
	*/
	if( (search_mode || line_mode || cursor_mode) && (argc-woptind > 1) )
	{
		
		sb_append( sb_err,
					argv[0],
					L": Too many arguments\n",
					NULL );
		builtin_print_help( parser, argv[0], sb_err );
		return 1;
	}

	if( (buffer_part || tokenize || cut_at_cursor) && (cursor_mode || line_mode || search_mode) )
	{		
		sb_printf( sb_err,
				   BUILTIN_ERR_COMBO,
				   argv[0] );

		builtin_print_help( parser, argv[0], sb_err );
		return 1;
	}		
	

	if( (tokenize || cut_at_cursor) && (argc-woptind) )
	{
		sb_printf( sb_err,
				   BUILTIN_ERR_COMBO2,
				   argv[0],
				   L"--cut-at-cursor and --tokenize can not be used when setting the commandline" );
		

		builtin_print_help( parser, argv[0], sb_err );
		return 1;
	}

	if( append_mode && !(argc-woptind) )
	{
		sb_printf( sb_err,
                    BUILTIN_ERR_COMBO2,
                    argv[0],
				   L"insertion mode switches can not be used when not in insertion mode" );

        builtin_print_help( parser, argv[0], sb_err );		
        return 1;
	}

	/*
	  Set default modes
	*/
	if( !append_mode )
	{
		append_mode = REPLACE_MODE;
	}
	
	if( !buffer_part )
	{
		buffer_part = STRING_MODE;
	}

	if( cursor_mode )
	{
		if( argc-woptind )
		{
			wchar_t *endptr;
			int new_pos;
			errno = 0;
			
			new_pos = wcstol( argv[woptind], &endptr, 10 );
			if( *endptr || errno )
			{
				sb_printf( sb_err,
					   BUILTIN_ERR_NOT_NUMBER,
					   argv[0],
					   argv[woptind] );
				builtin_print_help( parser, argv[0], sb_err );
			}
			
			current_buffer = reader_get_buffer();
			new_pos = maxi( 0, mini( new_pos, wcslen( current_buffer ) ) );
			reader_set_buffer( current_buffer, new_pos );
			return 0;
		}
		else
		{
			sb_printf( sb_out, L"%d\n", reader_get_cursor_pos() );
			return 0;
		}
		
	}
	
	if( line_mode )
	{
		int pos = reader_get_cursor_pos();
		wchar_t *buff = reader_get_buffer();
		sb_printf( sb_out, L"%d\n", parse_util_lineno( buff, pos ) );
		return 0;
			
	}
	
	if( search_mode )
	{
		return !reader_search_mode();
	}
	
		
	switch( buffer_part )
	{
		case STRING_MODE:
		{			
			begin = get_buffer();
			end = begin+wcslen(begin);
			break;			
		}

		case PROCESS_MODE:
		{
			parse_util_process_extent( get_buffer(),
									   get_cursor_pos(),
									   &begin, 
									   &end );
			break;
		}
		
		case JOB_MODE:
		{
			parse_util_job_extent( get_buffer(),
								   get_cursor_pos(),
								   &begin,
								   &end );					
			break;			
		}
		
		case TOKEN_MODE:
		{
			parse_util_token_extent( get_buffer(),
									 get_cursor_pos(),
									 &begin, 
									 &end, 
									 0, 0 );
			break;			
		}
				
	}

	switch(argc-woptind)
	{
		case 0:
		{					
			write_part( begin, end, cut_at_cursor, tokenize );
			break;
		}
		
		case 1:
		{
			replace_part( begin, end, argv[woptind], append_mode );
			break;
		}		

		default:
		{
			string_buffer_t sb;
			int i;
			
			sb_init( &sb );
			
			sb_append( &sb, argv[woptind] );
			
			for( i=woptind+1; i<argc; i++ )
			{
				sb_append( &sb, L"\n" );
				sb_append( &sb, argv[i] );
			}
			
			replace_part( begin, end, (wchar_t *)sb.buff, append_mode );
			sb_destroy( &sb );
			
			break;
		}		
	}
	
	return 0;
}