/*
 *
 *
 *    options.h
 *
 *    C++ source code for 'command_line' namespace tools, to turn command-line
 *    arguments into usable information.
 *
 *    =========================================================================
 *
 *    Copyright (C) 1999-2003 by Matt Roberts, KK5JY.
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation; either
 *    version 2.1 of the License, or (at your option) any later version.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *    =========================================================================
 *
 *
 */


#ifndef __OPTIONS_H
#define __OPTIONS_H

	// standard includes
	#include <list>
	#include <string>
	#include <algorithm>
	#include <iostream>

	// my++ library includes
	#include <my++/exception.h>

	namespace my {
		namespace command_line {
			enum arg_flags {
				arg_none = 0,
				arg_required = 1,
				arg_optional = 2
			};


			/*
			 *
			 *  class config
			 *
			 */

			class config {
				public: // public types
					typedef my::exception_template<config> exception;

					/*
					 *   option_not_found: thrown if a 'get' or similar function cannot find the
					 *                     option requested.
					 */
					class option_not_found : public config::exception {
						private:
							std::string opt_name;
						public:
							option_not_found (const config *c, const std::string n)
								: config::exception(c, "Option not found: " + n), opt_name(n)
								{ /* noop */ };
							~option_not_found (void) throw () { /* noop */ };
							const std::string &name (void) const { return opt_name; };
					};

				private: // private types
					struct node_t {
						std::string n_option;
						my::command_line::arg_flags n_flags;

						node_t (void) : n_flags(my::command_line::arg_none) { /* noop */ };
						node_t (const std::string &s, my::command_line::arg_flags f)
							: n_option(s), n_flags(f) { /* noop */ };

						bool operator == (const std::string &s) const { return n_option == s; };
						bool operator != (const std::string &s) const { return n_option != s; };
						bool operator == (const node_t &s) const { return n_option == s.n_option; };
						bool operator != (const node_t &s) const { return n_option != s.n_option; };
					};

					typedef std::list<node_t> container_t;

				private: // private data
					container_t c_data;
					mutable container_t::const_iterator c_current;

				public:  // public [cd]tors
					config (void) { /* noop */ };
					~config (void) throw() { /* noop */ };

				public:  // public accessors
					bool is_valid (const std::string &name) const;
					my::command_line::arg_flags req_flag (const std::string &name) const;

				public:  // public mutators
					void add (const std::string &name, arg_flags fl);

			};




			/*
			 *
			 *  class option
			 *
			 *  TODO: Make this a 'parser' inner class?
			 *
			 */

			struct option {
				private:  // private data
					std::string o_name;   // name of option (i.e. '--help')
					std::string o_value;  // value of option, if any
					unsigned o_count;     // how many times the option has been set

				public:  // public [cd]tors
					option (void) : o_count(0u) { /* noop */ };
					option (const my::command_line::option &o);
					~option (void) throw () { /* noop */ };

				public:  // public accessors
					const std::string name (void) const { return o_name; };
					const std::string value (void) const { return o_value; };

					unsigned count (void) const { return o_count; };

				public:  // public mutators
					void name (const std::string &s) { o_name = s; };
					void value (const std::string &s) { o_value = s; ++o_count; };

					void clear (void) { o_value.erase(); o_name.erase(); o_count = 0u; };

				public:  // public operators (for sorting and searching)
					bool operator == (const my::command_line::option &o) const { return (o_name == o.o_name); };
					bool operator != (const my::command_line::option &o) const { return (o_name != o.o_name); };
					bool operator <  (const my::command_line::option &o) const { return (o_name < o.o_name); };

					bool operator == (const std::string &s) const { return (o_name == s); };
					bool operator != (const std::string &s) const { return (o_name != s); };
					bool operator <  (const std::string &s) const { return (o_name < s); };

			};




			/*
			 *
			 *  class parser
			 *
			 */

			class parser {
				public:  // public types
					typedef std::list<my::command_line::option> option_container;
					typedef std::list<std::string> nonoption_container;

				public:  // public exceptions
					typedef my::exception_template<parser> exception;

					/*
					 *   invalid_option: thrown if an option is given that is not in the config object
					 */

					class invalid_option : public parser::exception {
						private:
							std::string opt_name;

						public:
							invalid_option (const parser *p, const std::string &n)
								: parser::exception(p, "Invalid option: " + n), opt_name(n)
								{ /* noop */ };
							~invalid_option (void) throw () { /* noop */ };
							const std::string &name (void) const { return opt_name; };
					};

					/*
					 *   incomplete_option: thrown if an option with required arg is given without arg
					 */
					class incomplete_option : public parser::exception {
						private:
							std::string opt_name;
							arg_flags opt_flag;
						public:
							incomplete_option(const parser *p, const std::string n, arg_flags f)
								: parser::exception(p, "Option requires an argument: " + n), opt_name(n), opt_flag(f)
								{ /* noop */ };
							~incomplete_option (void) throw () { /* noop */ };
							const std::string &name (void) const { return opt_name; };
							arg_flags flag (void) const { return opt_flag; };
					};

					/*
					 *   option_not_found: thrown if a 'get' or similar function cannot find the
					 *                     option requested.
					 */
					class option_not_found : public parser::exception {
						private:
							std::string opt_name;
						public:
							option_not_found (const parser *p, const std::string n)
								: parser::exception(p, "Option not found: " + n), opt_name(n)
								{ /* noop */ };
							~option_not_found (void) throw () { /* noop */ };
							const std::string &name (void) const { return opt_name; };
					};

				private: // private data
					option_container    opt_data;             // list of options passed
					nonoption_container non_options;          // list of nonoption strings
					const my::command_line::config &p_config; // reference to config data

				public:  // public [cd]tors
					parser (const my::command_line::config &cfg, int argc, char **argv);
					parser (const my::command_line::config &cfg) : p_config(cfg) { /* noop */ };
					~parser (void) throw () { /* noop */ };

				public: // public accessors
					const my::command_line::option get (const std::string &name) const;
					bool is_set (const std::string &name) const;
					void get_options (option_container &c) const { c = opt_data; };
					void get_nonoptions (nonoption_container &l) const { l = non_options; };

				public: // public mutators
					void parse (int argc, char **argv);

			};
		}
	}


	/*
	 *
	 *   config::is_valid (...)
	 *
	 */

	inline bool my::command_line::config::is_valid (const std::string &name) const {
		c_current = std::find(c_data.begin(), c_data.end(), name);
		return (c_current != c_data.end());
	}


	/*
	 *
	 *   parser::is_set(...)
	 *
	 */

	inline bool my::command_line::parser::is_set (const std::string &name) const {
		option_container::const_iterator i = std::find(opt_data.begin(), opt_data.end(), name);
		return (i != opt_data.end());
	}

#endif  /* __OPTIONS_H */
