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

#pragma once

#include <alib/vector>
#include <set>
#include <map>
#include <alib/string>

#include <registry/Registry.h>
#include <registry/XmlRegistry.h>

#include <environment/Environment.h>
#include <prompt/Prompt.h>

#include <readline/readline.h>

class ReadlinePromptCompletion {
	static std::set < std::string > fetchAlgorithmsFullyQualifiedName ( const char *text ) {
		std::set < std::string > fullyQualifiedNames;

		for ( const ext::pair < std::string, ext::vector < std::string > > & algo : abstraction::Registry::listAlgorithms ( ) ) {
			fullyQualifiedNames.insert ( algo.first );
		}

		return filter_completions ( fullyQualifiedNames, text );
	}

	static std::set < std::string > fetchAlgorithmsLastSegmentName ( const char *text ) {
		std::map < std::string, unsigned > collisions;

		for ( const ext::pair < std::string, ext::vector < std::string > > & algo : abstraction::Registry::listAlgorithms ( ) ) {
			size_t pos = algo.first.find_last_of ( ':' );
			if ( pos != std::string::npos )
				collisions [ algo.first.substr ( pos + 1 ) ] += 1;
		}

		std::set < std::string > res;
		for ( const std::pair < const std::string, unsigned > & kv : collisions )
			if ( kv.second == 1 )
				res.insert ( kv.first );

		return filter_completions ( res, text );
	}

	static std::set < std::string > fetchAlgorithmGroups ( const char *text ) {
		std::set < std::string > res;

		for ( const ext::pair < std::string, ext::vector < std::string > > & algo : abstraction::Registry::listAlgorithms ( ) ) {
			std::set < std::string > groups = getGroups ( algo.first );
			res.insert ( groups.begin ( ), groups.end ( ) );
		}

		return filter_completions ( res, text );
	}

	static std::set < std::string > fetchDatatypeGroups ( const char *text ) {
		std::set < std::string > res;

		for ( const std::string & dtt : abstraction::XmlRegistry::listDataTypes ( ) ) {
			std::set < std::string > groups = getGroups ( dtt );
			res.insert ( groups.begin ( ), groups.end ( ) );
		}

		return filter_completions ( res, text );
	}

	static std::set < std::string > fetchCommands ( const char *text ) {
		return filter_completions ( { "print", "execute", "introspect", "quit", "help", "set" }, text );
	}

	static std::set < std::string > fetchCommandsIntrospect ( const char *text ) {
		return filter_completions ( { "algorithms", "overloads", "casts", "datatypes", "variables", "bindings" }, text );
	}

	static std::set < std::string > fetchBindings ( const char *text ) {
		return filter_completions ( addPrefix ( Prompt::getPrompt ( ).getEnvironment ( ).getBindingNames ( ), "#" ), text );
	}

	static std::set < std::string > fetchVariables ( const char *text ) {
		return filter_completions ( addPrefix ( Prompt::getPrompt ( ).getEnvironment ( ).getVariableNames ( ), "$" ), text );
	}

	static std::set < std::string > fetchSet ( const char *text ) {
		return filter_completions ( { "verbose", "measure", "optimizeXml", "seed" }, text );
	}

	static std::set < std::string > fetchFilepath ( const char *text ) {
		std::set < std::string > res;
		char *str;
		int state = 0;

		while ( ( str = rl_filename_completion_function ( text, state++ ) ) != nullptr ) {
			res.insert ( str );
		}

		return res;
	}

public:
	enum class CompletionContext {
		COMMAND,
		COMMAND_INTROSPECT,

		ALGORITHM,
		ALGORITHM_GROUP,

		DATATYPE_GROUP,

		FILEPATH,
		FILEPATH_OR_VARIABLE,

		VARIABLE,
		BINDING,

		SET,
	};

	static char** readline_completion ( const char *text, int start, int end );
	static CompletionContext context ( const char *text, int start, int end );

private:
	static bool masterCommandCompletionTest ( const std::string & line, int start, const std::string & expectation );
	static std::set < std::string > addPrefix ( const std::set < std::string > & collection, const std::string & prefix );
	static std::set < std::string > getGroups ( const std::string & qualified_name );
	static std::set < std::string > filter_completions ( const std::set < std::string > & choices, const char *text );

	/**
	 * @param text Prefix
	 * @param state Invocation number of this completion
	 * @param generator Function that generates the completion-strings
	 */
	template < typename... CompletionGeneratorFunc >
	static char * completion_generator ( const char *text, int state, const CompletionGeneratorFunc & ... generators ) {
		static std::string prefix;
		static std::set < std::string > choices;
		static std::set < std::string > :: const_iterator iter;

		/* on first call initialize choices */
		if ( state == 0 ) {
			prefix = text;

			choices = std::set < std::string > ( );

			/* merge choices from all generators */
			const std::vector < std::function < std::set < std::string > ( const char* ) > > gens = { generators... };
			for ( const auto & gen : gens ) {
				std::set < std::string > tmpres;
				std::set < std::string > tmpg = gen ( text );

				std::set_union ( std::begin ( choices ), std::end ( choices ),
						std::begin ( tmpg ), std::end ( tmpg ),
						std::inserter ( tmpres, std::begin ( tmpres ) ) );
				choices = tmpres;
			}

			iter = choices.begin ( );
		}

		/* iterate through choices */
		while ( iter != choices.end ( ) ) {
			return strdup ( iter ++ -> c_str ( ) );
		}

		return nullptr;
	}

	/**
	 * http://www.delorie.com/gnu/docs/readline/rlman_45.html
	 * @param state Function invocation number
	 * @return malloc-allocated string or nullptr if no more strings can be generated.
	 */
	static char * complete_algorithm ( const char *text, int state ) {
		return completion_generator ( text, state, fetchAlgorithmsFullyQualifiedName, fetchAlgorithmsLastSegmentName );
	}

	static char * complete_algorithm_group ( const char *text, int state ) {
		return completion_generator ( text, state, fetchAlgorithmGroups );
	}

	static char * complete_datatype_group ( const char *text, int state ) {
		return completion_generator ( text, state, fetchDatatypeGroups );
	}

	static char * complete_command ( const char *text, int state ) {
		return completion_generator ( text, state, fetchCommands );
	}

	static char * complete_command_introspect ( const char *text, int state ) {
		return completion_generator ( text, state, fetchCommandsIntrospect );
	}

	static char * complete_variable ( const char *text, int state ) {
		return completion_generator ( text, state, fetchVariables );
	}

	static char * complete_binding ( const char *text, int state ) {
		return completion_generator ( text, state, fetchBindings );
	}

	static char * complete_filepath ( const char *text, int state ) {
		return completion_generator ( text, state, fetchFilepath );
	}

	static char * complete_filepath_or_variable ( const char *text, int state ) {
		return completion_generator ( text, state, fetchFilepath, fetchVariables );
	}

	static char * complete_set ( const char *text, int state ) {
		return completion_generator ( text, state, fetchSet );
	}

};

