/*
 * ReadlinePromptCompletion.cpp
 *
 *  Created on: 22. 1. 2019
 *	  Author: Tomas Pecka
 */

#include <alib/registration>

#include "ReadlinePromptCompletion.h"

static ext::Register < void > instance ( [ ] ( ) {
		// register readline completion function, pass environment
		rl_attempted_completion_function = ReadlinePromptCompletion::readline_completion;
	} );

/* ========================================================================= */

std::set < std::string > ReadlinePromptCompletion::addPrefix ( const std::set < std::string > & collection, const std::string & prefix ) {
	std::set < std::string > res;
	for ( const std::string & s : collection )
		res.insert ( prefix + s );

	return res;
}

std::set < std::string > ReadlinePromptCompletion::getGroups ( const std::string & qualified_name ) {
	std::set < std::string > res;

	unsigned template_level = 0;
	for ( size_t i = 0; i < qualified_name.size ( ); i++ ) {
		if ( qualified_name [ i ] == '<' )
			template_level ++;
		else if ( qualified_name [ i ] == '>' )
			template_level --;

		else if ( template_level == 0 && i > 0 && qualified_name [ i - 1 ] == ':' && qualified_name [ i ] == ':' )
			res.insert ( qualified_name.substr ( 0, i + 1 ) );
	}

	return res;
}

std::set < std::string > ReadlinePromptCompletion::filter_completions ( const std::set < std::string > & choices, const char *text ) {
	std::pair < std::set < std::string > :: const_iterator, std::set < std::string > :: const_iterator > range;
	const std::string prefix = text;

	range = std::equal_range ( choices.begin ( ), choices.end ( ), prefix,
			[ &prefix ] ( const std::string & a, const std::string & b ) { return strncmp ( a.c_str ( ), b.c_str ( ), prefix.size ( )) < 0; } );

	return std::set < std::string > ( range.first, range.second );
}

/* ========================================================================= */

bool ReadlinePromptCompletion::masterCommandCompletionTest ( const std::string & line, const int start, const std::string & expectation ) {
	return line.starts_with ( expectation ) && ( unsigned ) start == expectation.length ( ) + 1;
}

ReadlinePromptCompletion::CompletionContext ReadlinePromptCompletion::context ( const char *text, const int start, const int end ) {
	std::string line ( rl_line_buffer );

	if ( start == 0 )
		return CompletionContext::COMMAND;

	if ( masterCommandCompletionTest ( line, start, "introspect" ) )
		return CompletionContext::COMMAND_INTROSPECT;

	else if ( masterCommandCompletionTest ( line, start, "set" ) )
		return CompletionContext::SET;

	else if ( masterCommandCompletionTest ( line, start, "introspect overloads" ) )
		return CompletionContext::ALGORITHM;

	else if ( masterCommandCompletionTest ( line, start, "introspect variables" ) )
		return CompletionContext::VARIABLE;

	else if ( masterCommandCompletionTest ( line, start, "introspect bindings" ) )
		return CompletionContext::BINDING;

	else if ( masterCommandCompletionTest ( line, start, "introspect algorithms" ) )
		return CompletionContext::ALGORITHM_GROUP;

	else if ( masterCommandCompletionTest ( line, start, "introspect datatypes" ) )
		return CompletionContext::DATATYPE_GROUP;

	/* TODO
	else if ( masterCommandCompletionTest ( line, start, "introspect casts" ) )
	*/

	if ( end - start > 0 && text [ 0 ] == '$' )
		return CompletionContext::VARIABLE;
	else if ( end - start > 0 && text [ 0 ] == '#' )
		return CompletionContext::BINDING;

	/* scan the context backwards. If first non-whitespace character is < or >, then complete a filename */
	char *p = rl_line_buffer + start - 1;
	while ( p >= rl_line_buffer && ext::isspace ( *p ) ) p --;
	if ( p >= rl_line_buffer && ( *p == '<' || *p == '>' ) )
		return CompletionContext::FILEPATH_OR_VARIABLE;

	if ( p >= rl_line_buffer && ( *p == '|' || *p == '(' ) )
		return CompletionContext::ALGORITHM;

	if ( masterCommandCompletionTest ( line, start, "execute" ) || masterCommandCompletionTest ( line, start, "print" ) )
		return CompletionContext::ALGORITHM;

	/* undecided, fallback to filepath */
	return CompletionContext::FILEPATH;
}

/* ========================================================================= */

char ** ReadlinePromptCompletion::readline_completion ( const char *text, int start, int end ) {
	// for variables and bindings
	rl_special_prefixes = "$#@";
	rl_attempted_completion_over = 1;

	// std::cerr << ">" << text << "< " << start << " " << end << std::endl;

	switch ( context ( text, start, end ) ) {
		case CompletionContext::ALGORITHM:
			return rl_completion_matches ( text, complete_algorithm );
		case CompletionContext::ALGORITHM_GROUP:
			return rl_completion_matches ( text, complete_algorithm_group );
		case CompletionContext::DATATYPE_GROUP:
			return rl_completion_matches ( text, complete_datatype_group );
		case CompletionContext::COMMAND:
			return rl_completion_matches ( text, complete_command );
		case CompletionContext::COMMAND_INTROSPECT:
			return rl_completion_matches ( text, complete_command_introspect );
		case CompletionContext::VARIABLE:
			return rl_completion_matches ( text, complete_variable );
		case CompletionContext::BINDING:
			return rl_completion_matches ( text, complete_binding );
		case CompletionContext::FILEPATH:
			return rl_completion_matches ( text, complete_filepath );
		case CompletionContext::FILEPATH_OR_VARIABLE:
			return rl_completion_matches ( text, complete_filepath_or_variable );
		case CompletionContext::SET:
			return rl_completion_matches ( text, complete_set );
		default:
			return nullptr;
	}
}
