#ifndef __ENVIRON_H__
#define __ENVIRON_H__

// standard headers
#include <list>
#include <map>
#include <string>
#include <vector>
#include <cassert>
#include <iostream>
using namespace std;

#ifndef PRINTVAR
#define PRINTVAR(a) std::cerr << #a"\t" << a << std::endl;
#endif

class  Environment;
extern Environment env;

/// Enumeration type used to distinguish the type of the environment variable
enum OptType
{
	/// integer variable
	optInt,
	/// float variable
	optFloat,
	/// boolean variable
	optBool,
	/// 3D vector variable
	optVector,
	/// string variable
	optString,
	/// enumeration (one string from a set which is translated to int)
	optEnum
};

// mapping from enum name to its value
typedef std::map<const std::string, unsigned int> EnumTable;

///Command line and environment file reader.
/**
*  This class is used for reading command line, reading environment file and
*  managing options. Each recognizable option must be registered in the
*  constructor of this class.
*/
class Environment
{
protected:

	///Environment internal option data
	class OptionData 
	{
	public:
		string    name;      // name of the parameter
		string    abbrev;    // abbreviation for the command line
		OptType   type;      // type of the environment variable 
		EnumTable enumTable; // enumeration possibilities (only for optEnum)
	protected:
		string    value;     // actual value
		bool      valueSet;  // has any valu been assigned?

	public:
		void assignValue(const std::string& val) 
		{
			// std::cout << name << " = " << val << std::endl;
			value=val; valueSet=true; 
		}
		bool hasValue() const { return valueSet; }
		const string& getValue() const { return value; }
	};

	/// list of command line arguments
	typedef list<string> ArgList;
	/// mapping from option name to option data
	typedef map<const string, OptionData>  OptionTable;
	/// mapping from option abbreviation to option data poiter
	typedef map<const string, OptionData*> OptionPtrTable;

	/// application name
	string appname;
	/// command line arguments
	ArgList argv;
	/// list of params (such as file names etc)
	vector<string> nonOptions;
	/// Options table: maps names to values
	OptionTable options;

protected:

	/** 
	  Prints help message, if -h is found among cmd line parameters.
	  @return  true if help message was printed, false otherwise
	*/
	bool doHelp(void);

	/// Print help along with the list of options.
	void printUsage() const;

	/// Look up the given string in the command line arguments.
	ArgList::iterator findArg(const std::string &str);

	/** 
	Check if the value is of the given type.

	@param value Tested value of the variable.
	@param type The type of the variable.
	@param table of allowed values for optEnum
	@return true if the variable is of the specified type, false elsewhere.
	*/
	bool isTypeOk(const char* value, const OptType type, const EnumTable& table) const;

	/** 
	Method for checking whether an option is registered and its value is set.

	@param  name     Name of the checked option.
	@return Iterator pointing at the queried option.
	*/
	OptionTable::const_iterator checkIfOptionRegisteredAndPresent(const string& name) const;

	/** 
	Processes the environment file.

	Looks to the command line for a -E switch. If specified, reads the 
	environment from file.
	*/
	void doEnvironmentFile(void);

	/** 
	Reading of the environment file.

	This function reads the environment file.
	@param filename The name of the environment file to read.
	@return true if the file was read, false if the file could not be opened
	*/
	bool readEnvFile(const char *filename);

	/** 
	Auxiliary method for parsing environment file.

	Function gets one string from buffer, skipping leading whitespaces, and
	appends it at the end of the string parameter. Returns pointer to the next
	string in the buffer with trailing whitespaces skipped. If no string in
	the buffer can be found, returns NULL.

	@param  buffer  input line for parsing.
	@param  string  Buffer to append gathered string to.
	@return Pointer to next string or NULL if not succesful.
	*/
	char* parseString(char *buffer, char *string) const; 

	/** Auxiliary function for parsing the command line.
	    Gets the string in the form 'name=value', splits it to 'name' and 'value'
	    and updates the options table.
	*/
	void parseDefinition(const char* str);

	/// Parse command line and update environment. 
	/** Assumes '-E' and '-H' were already processed and removed from the command line argument list. */
	void parseCmdLine(void);

	/// Find the option with the given abbreviation.
	OptionTable::iterator lookUpAbbrevation(const std::string& str);

public:  

	Environment() {}
	virtual ~Environment() {}

	/** 
	Method for registering new option.

	Register options giving their name, type, abbreviation and default value.

	@param name     Name of the option.
	@param type     The type of the variable.
	@param abbrev   If not NULL, alias usable as a command line option.
	@param defValue Implicit value used if not specified explicitly.
	@param table    Table of enumeration values for optEnum
	*/
	void registerOption(
		const string&    name,
		const OptType    type,
		const char*      abbrev = NULL,
		const char*      defValue = NULL,
		const EnumTable & table = EnumTable());

	/// Call this after all options are registered and before the environment is first used.
	/**
	    All errors are reported by throwing an exception
	    @return 
		  - false: ok-carry on
		  - true:  ok, but the application should exit (help was printed)
	*/ 
	bool init(const int argc, char **argv);

	/// sets option to a integral value.
	void setInt(const string& name, const int value);
	/// sets option to a floating point value.
	void setFloat(const string& name, const float value);
	/// sets option to a boolean value.
	void setBool(const string& name, const bool value);
	/// sets option to a 3D vector.
	void setVector(const string& name, const float x, const float y, const float z);
	/// sets option to a character string.
	void setString(const string& name, const string& value);

	/// returns named option as a integral value.
	int getInt(const string& name) const;
	/// returns named option as a floating point value.
	float getFloat(const string& name) const;
	/// returns named option as a boolean value.
	bool getBool(const string& name) const;
	/// returns named option as a 3D vector.
	void getVector(const string& name, float &x, float &y, float &z) const;
	/// returns named option as a character string.
	const string& getString(const string& name) const;
	/// returns named option as a character string (not type checking is done)
	const string& getAsString(const string& name) const;
	/// returns named option as a enumeration value.
	unsigned getEnum(const string& name) const;

	/// Get the number of command line parameters that are not options
	int getNumParams() const 
	{ 
		return (int)nonOptions.size(); 
	}

	/// Get the i-th non-option command line parameter
	string getParam(int i) const 
	{ 
		assert(i>=0 && i<(int)nonOptions.size()); 
		return nonOptions[i]; 
	}

	/// Replaces existing file extension with a new one.
	static std::string changeExt(const std::string& str, const std::string& newExt)
	{
		return str.substr(0, str.find_last_of('.') ) + "." + newExt;
	}

	/// Add a file extension if str does not have one.
	static std::string addExt(const std::string& str, const std::string& ext)
	{
		if( str.find_last_of('.') == string::npos)
			return str + "." + ext;
		else
			return str;
	}

	/// "path/name.ext" => "path/nameprefix.ext"
	static std::string addBeforeExt(const std::string &filename, const string &prefix)
	{
		std::string::size_type pos = filename.find_last_of('.');

		if(pos==std::string::npos) 
			return filename+prefix;
		else
			return filename.substr(0,pos) + prefix + filename.substr(pos, filename.length()-pos);
	}

	/// parse string intxint and returns the two ints
	static void parseIntXInt(int &m, int &n, const std::string& str)
	{
		int xpos = (int) str.find_first_of('x');
		if (xpos == string::npos)
		{
			m = atoi(str.c_str());
			n = m;
		}
		else
		{
			m = atoi(str.substr(0, xpos).c_str());
			n = atoi(str.substr(xpos + 1).c_str());
		}
	}


#if 0
	/// Prints all the options.
	void printOptions(ostream& ostr, const string& startWith="", const string& notStartWith="");
#endif

};

#endif // __ENVIRON_H__