/*
 *		An event-driven parser for command-line arguments.
 *  
 *		Copyright (c) 2004-2005 by N.Okazaki
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions (known as zlib license):
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 * Naoaki Okazaki <okazaki at chokkan.org>
 *
 */

/* $Id: optparse.h 2 2006-10-31 00:57:57Z naoaki $ */

/*
 * Class 'optparse' implements a parser for GNU-style command-line arguments.
 * Inherit this class to define your own option variables and to implement an
 * option handler with macros, BEGIN_OPTION_MAP, ON_OPTION(_WITH_ARG), and
 * END_OPTION_MAP. Consult the sample program attached at the bottom of this
 * source code.
 *
 * This code was comfirmed to be compiled with MCVC++ 2003 and gcc 3.3.
 * Define _BUILD_NCL_SAMPLE if you want to build a sample program.
 *	$ g++ -D_BUILD_NCL_SAMPLE -xc++ optparse.h
 */

#ifndef	__NCL_OPTPRASE_H__
#define	__NCL_OPTPRASE_H__

#include <cstring>
#include <sstream>
#include <stdexcept>
#include <string>


#ifdef	USE_NCL_NAMESPACE
namespace ncl {
#endif/*USE_NCL_NAMESPACE*/


/**
 * An event-driven parser for command-line arguments.
 *	@author	Naoaki Okazaki
 */
class optparse {
public:
	/**
	 * Exception class for unrecognized options.
	 */
	class unrecognized_option : public std::invalid_argument {
	public:
		unrecognized_option(char shortopt)
			: std::invalid_argument(std::string("-") + shortopt) {}
		unrecognized_option(const std::string& longopt)
			: std::invalid_argument(std::string("--") + longopt) {}
	};

	/**
	 * Exception class for invalid values.
	 */
	class invalid_value : public std::invalid_argument {
	public:
		std::string optionstr;

		invalid_value(const std::string& message)
			: std::invalid_argument(message) {}
		invalid_value(char shortopt, const char *longopt, const std::string& message) :
			std::invalid_argument(message),
			optionstr(
				shortopt ?
					(std::string("-") + shortopt) : 
					(longopt ? (std::string("--") + longopt) : std::string(""))
				) {}

		const std::string& option() const {return optionstr; }
	};

public:
	/** Construct. */
	optparse() {}
	/** Destruct. */
	virtual ~optparse() {}

	/**
	 * Parse options.
	 *	@param	argv		array of null-terminated strings to be parsed
	 *	@param	num_argv	specifies the number, in strings, of the array
	 *	@return				the number of used arguments
	 *	@throws				optparse_exception
	 */
	int parse(char * const argv[], int num_argv)
	{
		int i;
		for (i = 0;i < num_argv;++i) {
			const char *token = argv[i];
			if (*token++ == '-') {
				const char *next_token = (i+1 < num_argv) ? argv[i+1] : "";
				if (!*token) {
					break;	// only '-' was found.
				} else if (*token == '-') {
					const char *arg = std::strchr(++token, '=');
					if (arg) {
						arg++;
					} else {
						arg = next_token;
					}
					int ret = handle_option(0, token, arg);
					if (ret < 0) {
						throw unrecognized_option(token);
					}
					if (arg == next_token) {
						i += ret;
					}
				} else {
					char c;
					while ((c = *token++) != '\0') {
						const char *arg = *token ? token : next_token;
						int ret = handle_option(c, token, arg);
						if (ret < 0) {
							throw unrecognized_option(c);
						}
						if (ret > 0) {
							if (arg == token) {
								token = "";
							} else {
								i++;
							}
						}
					} // while
				} // else (*token == '-') 
			} else {
				break;	// a non-option argument was fonud.
			} 
		} // for (i)

		return i;
	}

protected:
	/**
	 * Option handler
	 *	This function should be overridden by inheritance class.
	 *	@param	c			short option character, 0 for long option
	 *	@param	longname	long option name
	 *	@param	arg			an argument for the option
	 *	@return				0 (success);
							1 (success with use of an argument);
							-1 (failed, unrecognized option)
	 *	@throws				option_parser_exception
	 */
	virtual int handle_option(char c, const char *longname, const char *arg)
	{
		return 0;
	}

	int __optstrcmp(const char *option, const char *longname)
	{
		const char *p = std::strchr(option, '=');
		return p ?
			std::strncmp(option, longname, p-option) :
			std::strcmp(option, longname);
	}
};


/** The begin of inline option map. */
#define	BEGIN_OPTION_MAP_INLINE() \
	virtual int handle_option(char __c, const char *__longname, const char *arg) \
	{ \
		int used_args = 0; \
		if (0) { \

/** Define of option map. */
#define	DEFINE_OPTION_MAP() \
	virtual int handle_option(char __c, const char *__longname, const char *arg);

/** Begin of option map implimentation. */
#define	BEGIN_OPTION_MAP(_Class) \
	int _Class::handle_option(char __c, const char *__longname, const char *arg) \
	{ \
		int used_args = 0; \
		if (0) { \

/** An entry of option map */
#define	ON_OPTION(test) \
			return used_args; \
		} else if (test) { \
			used_args = 0; \

#define	ON_OPTION_WITH_ARG(test) \
			return used_args; \
		} else if (test) { \
			used_args = 1; \

/** The end of option map implementation */
#define	END_OPTION_MAP() \
			return used_args; \
		} \
		return -1; \
	} \

/** A predicator for short options */
#define	SHORTOPT(x)		(__c == x)
/** A predicator for long options */
#define	LONGOPT(x)		(!__c && __optstrcmp(__longname, x) == 0)


#ifdef	USE_NCL_NAMESPACE
};
#endif/*USE_NCL_NAMESPACE*/






#ifdef	_BUILD_NCL_SAMPLE

#include <cstdio>
#include <iostream>

/**
 * A class to store parameters specified by command-line arguments
 */
class option : public optparse {
public:
	int bytes;
	int lines;
	bool quiet;

	option() : bytes(0), lines(0), quiet(false) {}

	BEGIN_OPTION_MAP_INLINE()
		ON_OPTION(SHORTOPT('b') || LONGOPT("bytes"))
			bytes = std::atoi(arg);
			used_args = 1;	// Notify the parser of a consumption of argument.

		ON_OPTION_WITH_ARG(SHORTOPT('l') || LONGOPT("lines"))
			lines = std::atoi(arg);
			// no need of the notification: used_args variable will be set to 1.

		ON_OPTION(SHORTOPT('q') || LONGOPT("quiet") || LONGOPT("silent"))
			quiet = true;

	END_OPTION_MAP()
};

int main(int argc, char *argv[])
{
	try {
		option opt;
		int argused = opt.parse(&argv[1], argc-1); // Skip argv[0].

		std::cout << "used argv: " << argused << std::endl;
		std::cout << "bytes: " << opt.bytes << std::endl;
		std::cout << "lines: " << opt.lines << std::endl;
		std::cout << "quiet: " << opt.quiet << std::endl;
	} catch (const optparse::unrecognized_option& e) {
		std::cout << "unrecognized option: " << e.what() << std::endl;
		return 1;
	} catch (const optparse::invalid_value& e) {
		std::cout << "invalid value: " << e.what() << std::endl;
		return 1;
	}

	return 0;
}

#endif/*_BUILD_NCL_SAMPLE*/


#endif/*__NCL_OPTPRASE_H__*/
