/**
 * @file CmdLine.hpp
 * @author Schlund + Partner AG
 * @brief Utility for easy command line interfaces.
 *
 * Simple command line abstraction; automatically shows help on commands,
 * handles errors, can set variables, does variable expansion.
 *
 * Does not include a "real" scripting language (loops, variable
 * expansion, etc...); for this, rather rewrite this or use some
 * existing solution for embedded scripting.
 *
 */

/**
 * @example CmdLine.cpp
 * CmdLine example program
 * Should be installed as ui-utilcpp-cmdline along with the library.
 */

#ifndef UI_UTIL_CMDLINE_HPP
#define UI_UTIL_CMDLINE_HPP

// STDC++
#include <string>
#include <vector>
#include <map>
#include <iostream>
#include <cstdio>

namespace UI {
namespace Util {
// Better to have this extra namespace to avoid problems with local implementation-only Standard Command Classes
namespace CmdLine {

class Cmd;

/** @brief Simple Command Line interface.
 *
 * Short howto: Write your own command line class, inherit from CmdLine, inherit
 * custom command classes from Cmd (overwrite private run function).
 * Use run() to start the interface.
 *
 * When run interactively, libreadline is used.
 *
 * This class defines some common commands that cannot be used as
 * custom commands later.
 */
class CmdLine
{
	/** @brief These two need access to CmdLine variables -- might be better to encapsulate further ;). */
	friend class HelpCmd;
	/** @brief These two need access to CmdLine variables -- might be better to encapsulate further ;). */
	friend class SourceCmd;

public:
	/**
	 * @param is In-Stream; use Null-Pointer to run interactive (using libreadline).
	 * @param os Out-Stream.
	 * @param es Error-Out-Stream.
	 * @param title Initial description of the command line (variable __TITLE).
	 * @param prompt Initial prompt (variable __PROMPT).
	 */
	CmdLine(std::istream * is=&std::cin,
		std::ostream * os=&std::cout,
		std::ostream * es=&std::cerr,
		std::string const & title="Simple Command Line Interface",
		std::string const & prompt="\nCommand # ");
	~CmdLine();

	/** @brief Add a command to the command line.
	 *
	 * @param cmd Command to add.
	 * @note Command must be created via "new"; will be deleted automatically in CmdLine's destructor.
	 */
	void add(Cmd * cmd);

	/** @brief Get variable value. */
	std::string getVar(std::string const & key) const;
	/** @brief Set variable value. */
	void setVar(std::string const & key, std::string const & value);

	/** @brief Show (print) variable value. */
	void showVar(std::string const & key);
	/** @brief Show (print) variable values. */
	void showVar(std::map<std::string, std::string>::iterator i);
	/** @brief Show (print) all variable values. */
	void showVars();

	/** @brief Get out-stream. */
	std::ostream & os();
	/** @brief Get in-stream. */
	std::ostream & es();

	/** @brief Find a command from name. */
	Cmd * findCmd(std::string const & name) const;

	/**
	 * @returns: 0 if everything was fine (no errors), >0 if there have been # ignored errors,
	 *           <0 if it returned on error (with __FATAL set to 1). Negative amount of former errors.
	 */
	int run();

protected:
	/** @brief Only source for input. */
	std::string readLine(std::string const & promptVar="__PROMPT");

private:
	std::istream * is_;
	bool isNeedsDeletion_; // Should be set if in stream was created inside CmdLine (currently avoids leak with the source command)
	std::ostream * const os_;
	std::ostream * const es_;
	std::vector<Cmd *> commands_;
	std::map<std::string, std::string> variables_;
};

/** @brief Represents a command. */
class Cmd
{
public:
	/** @brief Standard constructor.
	 *
	 * @param name Name of the command.
	 * @param help Documentation string for this command.
	 */
	Cmd(std::string const & name,
			std::string const & help="No help for this command");

	virtual ~Cmd();

	/** @brief Get command name. */
	std::string getName() const;

	/** @brief Add mandatory argument. Use this in constructors of custom Cmd classes.
	 *
	 * @param name Name of the argument.
	 * @param help Help for this command.
	 */
	void addArg(std::string const & name, std::string const & help="No help for this option");

	/** @brief Add optional argument. Use this in constructors of custom Cmd classes.
	 *
	 * @param name Name of the argument.
	 * @param help Help for this command.
	 */
	void addOptArg(std::string const & name, std::string const & help="No help for this option");

	/** @brief Get min args. */
	int getMinArgs() const;

	/** @brief Get max args. */
	int getMaxArgs() const;

	/** @brief Get syntax line: cmd arg1 arg2 [arg3]. */
	std::string getSyntax() const;

	/** @brief Print help (i.e., syntax + documentation text). */
	std::string getHelp(bool showArgs=false) const;

	/** @brief Parse a line.
	 *
	 * @param line Input line.
	 */
	bool parse(std::string const & line);

	/** @brief Check if we are in "parsed" state. */
	bool isParsed() const;

	/** @brief Get the argument of a parsed command.
	 *
	 * @param i Number of argument to get.
	 */
	std::string getArg(int i) const;

	/** @brief Public run function. Use this to run commands. */
	int run();

	/** @brief Helper function. */
	static std::string stripLine(std::string const & line);

	/** @brief Helper function. */
	static std::string commandFromLine(std::string const & line);

	/** @brief Set Command Line Pointer; This must be run when adding commands from a CmdLine. */
	void setCL(CmdLine * cl);

protected:
	/** @brief Should be there to access CmdLine. */
	CmdLine * cl_;

private:
	/** @brief Virtual run function. Define this in custom Cmd classes.
	 *
	 * In this command, it is guaranteed to have "cl_" ready for use.
	 */
	virtual int runCmd() = 0;

	/** @brief Internal helper function. */
	std::string getArgString(int i) const;

	/** @brief Initializing variables. */
	std::string const name_;
	std::string const help_;

	/** @brief Argument variables. */
	std::vector<std::pair<std::string, std::string> > args_;
	int minArgs_;

	/** @brief Parser variables. */
	std::vector<std::string> tokens_;
	bool isParsed_;

	/** @brief Helper variables. */
	static std::string const spaces_;
};

/** @brief Pseudo command class to add header like seperators in help descriptions. */
class HeaderCmd: public Cmd
{
public:
	/**
	 * @param header Text to show as header.
	 */
	HeaderCmd(std::string const & header);

private:
	int runCmd();
};

}}}
#endif
