/*
 * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org)
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   * Neither the name of Redis nor the names of its contributors may be used
 *     to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef HAVE_REPLXX_HXX_INCLUDED
#define HAVE_REPLXX_HXX_INCLUDED 1

#include <memory>
#include <vector>
#include <string>
#include <functional>

/*
 * For use in Windows DLLs:
 *
 * If you are building replxx into a DLL,
 * unless you are using supplied CMake based build,
 * ensure that 'REPLXX_BUILDING_DLL' is defined when
 * building the DLL so that proper symbols are exported.
 */
#if defined( _WIN32 ) && ! defined( REPLXX_STATIC )
#	ifdef REPLXX_BUILDING_DLL
#		define REPLXX_IMPEXP __declspec( dllexport )
#	else
#		define REPLXX_IMPEXP __declspec( dllimport )
#	endif
#else
#	define REPLXX_IMPEXP /**/
#endif

#ifdef ERROR
enum { ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 = ERROR };
#undef ERROR
enum { ERROR = ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 };
#endif
#ifdef DELETE
enum { DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E = DELETE };
#undef DELETE
enum { DELETE = DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E };
#endif

namespace replxx {

class REPLXX_IMPEXP Replxx {
public:
	enum class Color {
		BLACK         = 0,
		RED           = 1,
		GREEN         = 2,
		BROWN         = 3,
		BLUE          = 4,
		MAGENTA       = 5,
		CYAN          = 6,
		LIGHTGRAY     = 7,
		GRAY          = 8,
		BRIGHTRED     = 9,
		BRIGHTGREEN   = 10,
		YELLOW        = 11,
		BRIGHTBLUE    = 12,
		BRIGHTMAGENTA = 13,
		BRIGHTCYAN    = 14,
		WHITE         = 15,
		NORMAL        = LIGHTGRAY,
		DEFAULT       = -1,
		ERROR         = -2
	};
	struct KEY {
		static char32_t const BASE         = 0x0010ffff + 1;
		static char32_t const BASE_SHIFT   = 0x01000000;
		static char32_t const BASE_CONTROL = 0x02000000;
		static char32_t const BASE_META    = 0x04000000;
		static char32_t const ESCAPE       = 27;
		static char32_t const PAGE_UP      = BASE      + 1;
		static char32_t const PAGE_DOWN    = PAGE_UP   + 1;
		static char32_t const DOWN         = PAGE_DOWN + 1;
		static char32_t const UP           = DOWN      + 1;
		static char32_t const LEFT         = UP        + 1;
		static char32_t const RIGHT        = LEFT      + 1;
		static char32_t const HOME         = RIGHT     + 1;
		static char32_t const END          = HOME      + 1;
		static char32_t const DELETE       = END       + 1;
		static char32_t const INSERT       = DELETE    + 1;
		static char32_t const F1           = INSERT    + 1;
		static char32_t const F2           = F1        + 1;
		static char32_t const F3           = F2        + 1;
		static char32_t const F4           = F3        + 1;
		static char32_t const F5           = F4        + 1;
		static char32_t const F6           = F5        + 1;
		static char32_t const F7           = F6        + 1;
		static char32_t const F8           = F7        + 1;
		static char32_t const F9           = F8        + 1;
		static char32_t const F10          = F9        + 1;
		static char32_t const F11          = F10       + 1;
		static char32_t const F12          = F11       + 1;
		static char32_t const F13          = F12       + 1;
		static char32_t const F14          = F13       + 1;
		static char32_t const F15          = F14       + 1;
		static char32_t const F16          = F15       + 1;
		static char32_t const F17          = F16       + 1;
		static char32_t const F18          = F17       + 1;
		static char32_t const F19          = F18       + 1;
		static char32_t const F20          = F19       + 1;
		static char32_t const F21          = F20       + 1;
		static char32_t const F22          = F21       + 1;
		static char32_t const F23          = F22       + 1;
		static char32_t const F24          = F23       + 1;
		static char32_t const MOUSE        = F24       + 1;
		static char32_t const PASTE_START  = MOUSE     + 1;
		static char32_t const PASTE_FINISH = PASTE_START + 1;
		static constexpr char32_t shift( char32_t key_ ) {
			return ( key_ | BASE_SHIFT );
		}
		static constexpr char32_t control( char32_t key_ ) {
			return ( key_ | BASE_CONTROL );
		}
		static constexpr char32_t meta( char32_t key_ ) {
			return ( key_ | BASE_META );
		}
		static char32_t const BACKSPACE    = 'H' | BASE_CONTROL;
		static char32_t const TAB          = 'I' | BASE_CONTROL;
		static char32_t const ENTER        = 'M' | BASE_CONTROL;
	};
	/*! \brief List of built-in actions that act upon user input.
	 */
	enum class ACTION {
		INSERT_CHARACTER,
		NEW_LINE,
		DELETE_CHARACTER_UNDER_CURSOR,
		DELETE_CHARACTER_LEFT_OF_CURSOR,
		KILL_TO_END_OF_LINE,
		KILL_TO_BEGINING_OF_LINE,
		KILL_TO_END_OF_WORD,
		KILL_TO_BEGINING_OF_WORD,
		KILL_TO_END_OF_SUBWORD,
		KILL_TO_BEGINING_OF_SUBWORD,
		KILL_TO_WHITESPACE_ON_LEFT,
		YANK,
		YANK_CYCLE,
		YANK_LAST_ARG,
		MOVE_CURSOR_TO_BEGINING_OF_LINE,
		MOVE_CURSOR_TO_END_OF_LINE,
		MOVE_CURSOR_ONE_WORD_LEFT,
		MOVE_CURSOR_ONE_WORD_RIGHT,
		MOVE_CURSOR_ONE_SUBWORD_LEFT,
		MOVE_CURSOR_ONE_SUBWORD_RIGHT,
		MOVE_CURSOR_LEFT,
		MOVE_CURSOR_RIGHT,
		HISTORY_NEXT,
		HISTORY_PREVIOUS,
		HISTORY_FIRST,
		HISTORY_LAST,
		HISTORY_INCREMENTAL_SEARCH,
		HISTORY_COMMON_PREFIX_SEARCH,
		HINT_NEXT,
		HINT_PREVIOUS,
		CAPITALIZE_WORD,
		LOWERCASE_WORD,
		UPPERCASE_WORD,
		CAPITALIZE_SUBWORD,
		LOWERCASE_SUBWORD,
		UPPERCASE_SUBWORD,
		TRANSPOSE_CHARACTERS,
		TOGGLE_OVERWRITE_MODE,
#ifndef _WIN32
		VERBATIM_INSERT,
		SUSPEND,
#endif
		BRACKETED_PASTE,
		CLEAR_SCREEN,
		CLEAR_SELF,
		REPAINT,
		COMPLETE_LINE,
		COMPLETE_NEXT,
		COMPLETE_PREVIOUS,
		COMMIT_LINE,
		ABORT_LINE,
		SEND_EOF
	};
	/*! \brief Possible results of key-press handler actions.
	 */
	enum class ACTION_RESULT {
		CONTINUE, /*!< Continue processing user input. */
		RETURN,   /*!< Return user input entered so far. */
		BAIL      /*!< Stop processing user input, returns nullptr from the \e input() call. */
	};
	typedef std::vector<Color> colors_t;
	class Completion {
		std::string _text;
		Color _color;
	public:
		Completion( char const* text_ )
			: _text( text_ )
			, _color( Color::DEFAULT ) {
		}
		Completion( std::string const& text_ )
			: _text( text_ )
			, _color( Color::DEFAULT ) {
		}
		Completion( std::string const& text_, Color color_ )
			: _text( text_ )
			, _color( color_ ) {
		}
		std::string const& text( void ) const {
			return ( _text );
		}
		Color color( void ) const {
			return ( _color );
		}
	};
	typedef std::vector<Completion> completions_t;
	class HistoryEntry {
		std::string _timestamp;
		std::string _text;
	public:
		HistoryEntry( std::string const& timestamp_, std::string const& text_ )
			: _timestamp( timestamp_ )
			, _text( text_ ) {
		}
		std::string const& timestamp( void ) const {
			return ( _timestamp );
		}
		std::string const& text( void ) const {
			return ( _text );
		}
	};
	class HistoryScanImpl;
	class HistoryScan {
	public:
		typedef std::unique_ptr<HistoryScanImpl, void (*)( HistoryScanImpl* )> impl_t;
	private:
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4251)
#endif
		impl_t _impl;
#ifdef _MSC_VER
#pragma warning(pop)
#endif
	public:
		HistoryScan( impl_t );
		HistoryScan( HistoryScan&& ) = default;
		HistoryScan& operator = ( HistoryScan&& ) = default;
		bool next( void );
		HistoryEntry const& get( void ) const;
	private:
		HistoryScan( HistoryScan const& ) = delete;
		HistoryScan& operator = ( HistoryScan const& ) = delete;
	};
	typedef std::vector<std::string> hints_t;

	/*! \brief Line modification callback type definition.
	 *
	 * User can observe and modify line contents (and cursor position)
	 * in response to changes to both introduced by the user through
	 * normal interactions.
	 *
	 * When callback returns Replxx updates current line content
	 * and current cursor position to the ones updated by the callback.
	 *
	 * \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far.
	 * \param cursorPosition[in,out] - a R/W reference to current cursor position.
	 */
	typedef std::function<void ( std::string& line, int& cursorPosition )> modify_callback_t;

	/*! \brief Completions callback type definition.
	 *
	 * \e contextLen is counted in Unicode code points (not in bytes!).
	 *
	 * For user input:
	 * if ( obj.me
	 *
	 * input == "if ( obj.me"
	 * contextLen == 2 (depending on \e set_word_break_characters())
	 *
	 * Client application is free to update \e contextLen to be 6 (or any other non-negative
	 * number not greater than the number of code points in input) if it makes better sense
	 * for given client application semantics.
	 *
	 * \param input - UTF-8 encoded input entered by the user until current cursor position.
	 * \param[in,out] contextLen - length of the additional context to provide while displaying completions.
	 * \return A list of user completions.
	 */
	typedef std::function<completions_t ( std::string const& input, int& contextLen )> completion_callback_t;

	/*! \brief Highlighter callback type definition.
	 *
	 * If user want to have colorful input she must simply install highlighter callback.
	 * The callback would be invoked by the library after each change to the input done by
	 * the user. After callback returns library uses data from colors buffer to colorize
	 * displayed user input.
	 *
	 * Size of \e colors buffer is equal to number of code points in user \e input
	 * which will be different from simple `input.length()`!
	 *
	 * \param input - an UTF-8 encoded input entered by the user so far.
	 * \param colors - output buffer for color information.
	 */
	typedef std::function<void ( std::string const& input, colors_t& colors )> highlighter_callback_t;

	/*! \brief Hints callback type definition.
	 *
	 * \e contextLen is counted in Unicode code points (not in bytes!).
	 *
	 * For user input:
	 * if ( obj.me
	 *
	 * input == "if ( obj.me"
	 * contextLen == 2 (depending on \e set_word_break_characters())
	 *
	 * Client application is free to update \e contextLen to be 6 (or any other non-negative
	 * number not greater than the number of code points in input) if it makes better sense
	 * for given client application semantics.
	 *
	 * \param input - UTF-8 encoded input entered by the user until current cursor position.
	 * \param contextLen[in,out] - length of the additional context to provide while displaying hints.
	 * \param color - a color used for displaying hints.
	 * \return A list of possible hints.
	 */
	typedef std::function<hints_t ( std::string const& input, int& contextLen, Color& color )> hint_callback_t;

	/*! \brief Key press handler type definition.
	 *
	 * \param code - the key code replxx got from terminal.
	 * \return Decision on how should input() behave after this key handler returns.
	 */
	typedef std::function<ACTION_RESULT ( char32_t code )> key_press_handler_t;

	struct State {
		char const* _text;
		int _cursorPosition;
		State( char const* text_, int cursorPosition_ = -1 )
			: _text( text_ )
			, _cursorPosition( cursorPosition_ ) {
		}
		State( State const& ) = default;
		State& operator = ( State const& ) = default;
		char const* text( void ) const {
			return ( _text );
		}
		int cursor_position( void ) const {
			return ( _cursorPosition );
		}
	};

	class ReplxxImpl;
private:
	typedef std::unique_ptr<ReplxxImpl, void (*)( ReplxxImpl* )> impl_t;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4251)
#endif
	impl_t _impl;
#ifdef _MSC_VER
#pragma warning(pop)
#endif

public:
	Replxx( void );
	Replxx( Replxx&& ) = default;
	Replxx& operator = ( Replxx&& ) = default;

	/*! \brief Register modify callback.
	 *
	 * \param fn - user defined callback function.
	 */
	void set_modify_callback( modify_callback_t const& fn );

	/*! \brief Register completion callback.
	 *
	 * \param fn - user defined callback function.
	 */
	void set_completion_callback( completion_callback_t const& fn );

	/*! \brief Register highlighter callback.
	 *
	 * \param fn - user defined callback function.
	 */
	void set_highlighter_callback( highlighter_callback_t const& fn );

	/*! \brief Register hints callback.
	 *
	 * \param fn - user defined callback function.
	 */
	void set_hint_callback( hint_callback_t const& fn );

	/*! \brief Read line of user input.
	 *
	 * Returned pointer is managed by the library and is not to be freed in the client.
	 *
	 * \param prompt - prompt to be displayed before getting user input.
	 * \return An UTF-8 encoded input given by the user (or nullptr on EOF).
	 */
	char const* input( std::string const& prompt );

	/*! \brief Get current state data.
	 *
	 * This call is intended to be used in handlers.
	 *
	 * \return Current state of the model.
	 */
	State get_state( void ) const;

	/*! \brief Set new state data.
	 *
	 * This call is intended to be used in handlers.
	 *
	 * \param state - new state of the model.
	 */
	void set_state( State const& state );

	/*! \brief Print formatted string to standard output.
	 *
	 * This function ensures proper handling of ANSI escape sequences
	 * contained in printed data, which is especially useful on Windows
	 * since Unixes handle them correctly out of the box.
	 *
	 * \param fmt - printf style format.
	 */
	void print( char const* fmt, ... );

	/*! \brief Prints a char array with the given length to standard output.
	 *
	 * \copydetails print
	 *
	 * \param str - The char array to print.
	 * \param length - The length of the array.
	 */
	void write( char const* str, int length );

	/*! \brief Schedule an emulated key press event.
	 *
	 * \param code - key press code to be emulated.
	 */
	void emulate_key_press( char32_t code );

	/*! \brief Invoke built-in action handler.
	 *
	 * \pre This method can be called only from key-press handler.
	 *
	 * \param action - a built-in action to invoke.
	 * \param code - a supplementary key-code to consume by built-in action handler.
	 * \return The action result informing the replxx what shall happen next.
	 */
	ACTION_RESULT invoke( ACTION action, char32_t code );

	/*! \brief Bind user defined action to handle given key-press event.
	 *
	 * \param code - handle this key-press event with following handler.
	 * \param handle - use this handler to handle key-press event.
	 */
	void bind_key( char32_t code, key_press_handler_t handler );

	/*! \brief Bind internal `replxx` action (by name) to handle given key-press event.
	 *
	 * Action names are the same as names of Replxx::ACTION enumerations
	 * but in lower case, e.g.: an action for recalling previous history line
	 * is \e Replxx::ACTION::HISTORY_PREVIOUS so action name to be used in this
	 * interface for the same effect is "history_previous".
	 *
	 * \param code - handle this key-press event with following handler.
	 * \param actionName - name of internal action to be invoked on key press.
	 */
	void bind_key_internal( char32_t code, char const* actionName );

	void history_add( std::string const& line );

	/*! \brief Synchronize REPL's history with given file.
	 *
	 * Synchronizing means loading existing history from given file,
	 * merging it with current history sorted by timestamps,
	 * saving merged version to given file,
	 * keeping merged version as current REPL's history.
	 *
	 * This call is an equivalent of calling:
	 * history_save( "some-file" );
	 * history_load( "some-file" );
	 *
	 * \param filename - a path to the file with which REPL's current history should be synchronized.
	 * \return True iff history file was successfully created.
	 */
	bool history_sync( std::string const& filename );

	/*! \brief Save REPL's history into given file.
	 *
	 * Saving means loading existing history from given file,
	 * merging it with current history sorted by timestamps,
	 * saving merged version to given file,
	 * keeping original (NOT merged) version as current REPL's history.
	 *
	 * \param filename - a path to the file where REPL's history should be saved.
	 * \return True iff history file was successfully created.
	 */
	bool history_save( std::string const& filename );

	/*! \brief Load REPL's history from given file.
	 *
	 * \param filename - a path to the file which contains REPL's history that should be loaded.
	 * \return True iff history file was successfully opened.
	 */
	bool history_load( std::string const& filename );

	/*! \brief Clear REPL's in-memory history.
	 */
	void history_clear( void );
	int history_size( void ) const;
	HistoryScan history_scan( void ) const;

	void set_preload_buffer( std::string const& preloadText );

	/*! \brief Set set of word break characters.
	 *
	 * This setting influences word based cursor movement and line editing capabilities.
	 *
	 * \param wordBreakers - 7-bit ASCII set of word breaking characters.
	 */
	void set_word_break_characters( char const* wordBreakers );

	/*! \brief How many completions should trigger pagination.
	 */
	void set_completion_count_cutoff( int count );

	/*! \brief Set maximum number of displayed hint rows.
	 */
	void set_max_hint_rows( int count );

	/*! \brief Set a delay before hint are shown after user stopped typing..
	 *
	 * \param milliseconds - a number of milliseconds to wait before showing hints.
	 */
	void set_hint_delay( int milliseconds );

	/*! \brief Set tab completion behavior.
	 *
	 * \param val - use double tab to invoke completions.
	 */
	void set_double_tab_completion( bool val );

	/*! \brief Set tab completion behavior.
	 *
	 * \param val - invoke completion even if user input is empty.
	 */
	void set_complete_on_empty( bool val );

	/*! \brief Set tab completion behavior.
	 *
	 * \param val - beep if completion is ambiguous.
	 */
	void set_beep_on_ambiguous_completion( bool val );

	/*! \brief Set complete next/complete previous behavior.
	 *
	 * COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations,
	 * in case when a partial completion is possible complete only partial part (`false` setting)
	 * or complete first proposed completion fully (`true` setting).
	 * The default is to complete fully (a `true` setting - complete immediately).
	 *
	 * \param val - complete immediately.
	 */
	void set_immediate_completion( bool val );

	/*! \brief Set history duplicate entries behaviour.
	 *
	 * \param val - should history contain only unique entries?
	 */
	void set_unique_history( bool val );

	/*! \brief Disable output coloring.
	 *
	 * \param val - if set to non-zero disable output colors.
	 */
	void set_no_color( bool val );

	/*! \brief Set maximum number of entries in history list.
	 */
	void set_max_history_size( int len );
	void clear_screen( void );
	int install_window_change_handler( void );
	void enable_bracketed_paste( void );
	void disable_bracketed_paste( void );

private:
	Replxx( Replxx const& ) = delete;
	Replxx& operator = ( Replxx const& ) = delete;
};

}

#endif /* HAVE_REPLXX_HXX_INCLUDED */

