/*
 *
 *
 *    cmdline.cc
 *
 *    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
 *
 *    =========================================================================
 *
 *
 */


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

// this is for debugging only: REMOVEME
// #include <iostream>

#include <algorithm>
#include <my++/stype.h>
#include <my++/exception.h>
#include <my++/cmdline.h>

using namespace std;


/*
 *
 *   config::req_flag (...)
 *
 */

my::command_line::arg_flags my::command_line::config::req_flag (const std::string &name) const {
	// use the cached iterator, if possible
	if (c_current != c_data.end()) {
		if (c_current->n_option == name)
			return c_current->n_flags;
	}

	// otherwise, go search
	c_current = std::find(c_data.begin(), c_data.end(), name);

	// if we found the option, return the associated requirements flag
	if (c_current == c_data.end())
		throw config::option_not_found(this, name);
	return c_current->n_flags;
}


/*
 *
 *   config::add (...)
 *
 */

void my::command_line::config::add (const std::string &name, my::command_line::arg_flags fl) {
	// see if the node already exists
	container_t::iterator obj = std::find(c_data.begin(), c_data.end(), name);

	// if not, add it; if so, update it
	if (obj == c_data.end()) {
		c_data.push_back(node_t(name, fl));
	} else {
		obj->n_flags = fl;
	}

	// invalidate the cached iterator
	c_current = c_data.end();
}


/*
 *
 *   option::option (const option &)
 *
 */

my::command_line::option::option (const my::command_line::option &o) {
	o_name = o.o_name;
	o_value = o.o_value;
	o_count = o.o_count;
}


/*
 *
 *   parser::parser(my::command_line::config&, int, char**)
 *
 */

my::command_line::parser::parser (const my::command_line::config &cfg, int argc, char **argv)
	: p_config(cfg) {

	// parse the command line
	parse(argc, argv);
}


/*
 *
 *   parser::get (const std::string &)
 *
 */

const my::command_line::option my::command_line::parser::get (const std::string &name) const {
	option_container::const_iterator i = std::find(opt_data.begin(), opt_data.end(), name);
	if (i == opt_data.end())
		throw my::command_line::parser::option_not_found(this, name);
	return (*i);
}


/*
 *
 *   parser::read (int, char**)
 *
 */

void my::command_line::parser::parse (int argc, char **argv) {
	// holds the last option encountered
	std::string current_name;
	std::string current_value;
	std::string token;

	// clear out the private data, and start over
	opt_data.clear();
	non_options.clear();

	// for each token from the command line
	for (int i = 1; i < argc; ++i) {
		// put the token in a string object
		token = argv[i];

		// if this is the -- token, stop processing...
		if (token == "--") {
			++i; // skip the actual "--"; only record subsequent tokens
			while (i < argc)
				non_options.push_back(argv[i++]);
			return;
		}

		// see if this is an option or a nonoption argument
		if ((token.size() >= 2) && (token[0] == '-')) { // it's an option
			current_name.erase();
			current_value.erase();

			if (token[1] == '-') { // then long option
				std::string::iterator si;

				// seperate the option from its (optional) argument
				for (si = token.begin() + 2; si != token.end(); ++si) {
					if ((*si == '=') || isspace(*si)) {
						current_name = std::string(token.begin(), si);

						if ((! isspace(*si)) && (si != token.end()))
							current_value = std::string(++si, token.end());

						break;
					}
				}

				// if we fell off the end trying to get the name, finish up
				if (current_name.empty())
					current_name = token;

				// see if the option is valid
				if (!p_config.is_valid(current_name))
					throw invalid_option(this, current_name);

				// see what to do with option arguments
				if (current_value.empty()) {
					try {
						if (p_config.req_flag(current_name) == my::command_line::arg_required) {
							if (++i < argc)  // if it's valid, make it the value of the option
								current_value = argv[i];
							else
								throw incomplete_option(this, current_name, p_config.req_flag(current_name));
						}
					} catch (const config::option_not_found &e) {
						// TODO: this would be an internal error, throw something else
					}
				}

				// see if this item is already in the list
				option_container::iterator o = std::find(opt_data.begin(), opt_data.end(), current_name);

				// if the option was not found, add it
				if (o == opt_data.end()) {
					my::command_line::option new_opt;
					new_opt.name(current_name);
					new_opt.value(current_value);
					opt_data.push_back(new_opt);
				} else {  // else modify the object found
					o->value(current_value);
				}
			} else { // short option
				std::string::iterator si;
				current_name = "--";

				// step through the characters of the token, starting with the second one
				for (si = token.begin() + 1; si != token.end(); si = (si == token.end() ? si : si + 1)) {
					// get the single-character value of the option
					current_name[1] = *si;

					// start with an empty value
					current_value.erase();

					// if the name isn't a valid option, raise exception and quit
					if (!p_config.is_valid(current_name))
						throw invalid_option(this, current_name);

					try {
						// now consult the configuration on what to do
						switch (p_config.req_flag(current_name)) {
							// if an argument is required, make sure we have it
							case my::command_line::arg_required:
								current_value = std::string(si + 1, token.end());

								if (current_value.empty()) {
									if ((++i >= argc) || ((current_value = argv[i]).empty()) || (current_value[0] == '-'))
										throw incomplete_option(this, current_name, p_config.req_flag(current_name));
								}

								si = token.end();
								break;

							// if an argument is allowed, get it, if possible
							case my::command_line::arg_optional:
								current_value = std::string(si + 1, token.end());
								si = token.end();
								break;

							// if an argument is disallowed, don't do anything
							case my::command_line::arg_none:
								break;

							default:
								/* TODO: this is an internal error, throw something */
								break;
						}
					} catch (const config::option_not_found &e) {
						// TODO: this is an internal error, throw something
					}

					// see if this item is already in the list
					option_container::iterator o = std::find(opt_data.begin(), opt_data.end(), current_name);

					// add the option to the list
					if (o == opt_data.end()) {
						my::command_line::option new_opt;
						new_opt.name(current_name);
						new_opt.value(current_value);
						opt_data.push_back(new_opt);
					} else {  // or update it if it already exists
						o->value(current_value);
					}
				}
			}
		} else { // add non-option
			if (token != "--")
				non_options.push_back(token);
		}
	}
}

/* End of file: options.cc */
