/**
 * @file popt_options.cpp
 * option parsing
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Philippe Elie
 * @author John Levon
 */

#include <iostream>

#include "op_popt.h"
#include "op_version.h"

#include "popt_options.h"
#include "string_manip.h"

using namespace std;

namespace popt {
 
/**
 * option_base - base class for implementation of a command line option
 *
 * Every command line option added before calling parse_options()
 * is of this type.
 */
class option_base {
public:
	/**
	 * option_base - construct an option with the given options.
	 * @param option_name name part of long form e.g. --option
	 * @param short_name short form name e.g. -o
	 * @param help_str short description of the option
	 * @param arg_help_str short description of the argument (if any)
	 * @param data a pointer to the data to fill in
	 * @param popt_flags the popt library data type
	 */
	option_base(char const * option_name, char short_name,
		    char const * help_str, char const * arg_help_str,
		    void * data, int popt_flags);

	virtual ~option_base() {}

	/**
	 * post_process - perform any necessary post-processing
	 */
	virtual void post_process() {}

protected:
	char const * option_name;
};


/** the popt array singleton options */
static vector<poptOption> & popt_options(void)
{
	static vector<poptOption> *x = new(vector<poptOption>);
	return *x;
}

static vector<option_base *> & options_list(void)
{
	static vector<option_base *> *x = new(vector<option_base *>);
	return *x;
}

static int showvers;

static struct poptOption appended_options[] = {
	{ "version", 'v', POPT_ARG_NONE, &showvers, 0, "show version", NULL, },
	POPT_AUTOHELP
	POPT_TABLEEND
	};


/* options parameter can't be a local variable because caller can use the
 * returned poptContext which contains  pointer inside the options array */
static poptContext do_parse_options(int argc, char const ** argv,
                                    vector<poptOption> & options,
                                    vector<string> & additional_params)
{
	options = popt_options();

	int const nr_appended_options =
		sizeof(appended_options) / sizeof(appended_options[0]);

	options.insert(options.end(), appended_options,
		       appended_options + nr_appended_options);

	poptContext con = op_poptGetContext(NULL, argc, argv, &options[0], 0);

	if (showvers)
		show_version(argv[0]);

	char const * file;
	while ((file = poptGetArg(con)) != 0)
		additional_params.push_back(file);

	for (size_t i = 0 ; i < options_list().size() ; ++i)
		options_list()[i]->post_process();

	return con;
}


void parse_options(int argc, char const ** argv,
                   vector<string> & additional_params)
{
	vector<poptOption> options;

	poptContext con =
		do_parse_options(argc, argv, options, additional_params);

	poptFreeContext(con);
}


template <typename T> class option_imp;


/**
 * option<void> - a binary option
 *
 * Use this option type for constructing specified / not-specified
 * options e.g. --frob
 */
template <> class option_imp<void> : public option_base {
public:
	option_imp(bool & value, char const * option_name, char short_name,
	           char const * help_str);

	~option_imp() {}

	void post_process();

private:
	bool & value;
	int popt_value;
};


/**
 * option<int> - a integer option
 *
 * Use this for options taking an integer e.g. --frob 6
 */
template <> class option_imp<int> : public option_base {
public:
	option_imp(int & value, char const * option_name, char short_name,
	           char const * help_str, char const * arg_help_str);

	~option_imp() {}
};


/**
 * option<string> - a string option
 *
 * Use this for options taking a string e.g. --frob parsley
 */
template <> class option_imp<string> : public option_base {
public:
	option_imp(string & value, char const * option_name,
	           char short_name, char const * help_str,
	           char const * arg_help_str);

	void post_process();

	~option_imp() {}

private:
	// we need an intermediate char array to pass to popt libs
	char * popt_value;
	string & value;
};


/**
 * option< vector<string> > - a string vector option
 *
 * Use this for options taking a number of string arguments,
 * separated by the given separator.
 */
template <> class option_imp< vector<string> > : public option_base {
public:
	option_imp(vector<string> & value,
	           char const * option_name, char short_name,
	           char const * help_str, char const * arg_help_str,
	           char separator = ',');

	void post_process();

	~option_imp() {}

private:
	vector<string> & value;
	// we need an intermediate char array to pass to popt libs
	char * popt_value;
	char const separator;
};


option::~option()
{
	delete the_option;
}


/// non templatized ctor for boolean option
option::option(bool & value, char const * name, char short_name, char const * help)
	: the_option(new option_imp<void>(value, name, short_name, help))
{
}


/// specialization of option ctor for integer option
template <>
option::option(int & value, char const * name, char short_name,
               char const * help, char const * arg_help)
	: the_option(new option_imp<int>
			(value, name, short_name, help, arg_help))
{
}


/// specialization of option ctor for string option
template <>
option::option(string & value, char const * name, char short_name,
               char const * help, char const * arg_help)
	: the_option(new option_imp<string>
			(value, name, short_name, help, arg_help))
{
}


/// specialization of option ctor for vector<string> option
template <>
option::option(vector<string> & value, char const * name, char short_name,
               char const * help, char const * arg_help)
	: the_option(new option_imp< vector<string> >
			(value, name, short_name, help, arg_help))
{
}


option_base::option_base(char const * name, char short_name,
                         char const * help, char const * arg_help,
                         void * data, int popt_flags)
	: option_name(name)
{
	poptOption const opt = { name, short_name, popt_flags,
	                         data, 0, help, arg_help };

	popt_options().push_back(opt);

	options_list().push_back(this);
}


option_imp<void>::option_imp(bool & val, char const * name, char short_name,
                             char const * help)
	: option_base(name, short_name, help, 0, &popt_value, POPT_ARG_NONE),
	  value(val), popt_value(0)
{
}


void option_imp<void>::post_process()
{
	if (popt_value) {
		if (is_prefix(option_name, "no-"))
			value = !popt_value;
		else 
			value = popt_value;
	}
}


option_imp<int>::option_imp(int & value, char const * name, char short_name,
                            char const * help, char const * arg_help)
	: option_base(name, short_name, help, arg_help, &value, POPT_ARG_INT)
{
}


option_imp<string>::option_imp(string & val, char const * name, char short_name,
                               char const * help, char const * arg_help)
	: option_base(name, short_name, help, arg_help,
                      &popt_value, POPT_ARG_STRING),
	  popt_value(0), value(val)
{
}


void option_imp<string>::post_process()
{
	if (popt_value) {
		value = popt_value;
		popt_value = 0;
	}
}


option_imp< vector<string> >::option_imp(vector<string> & val,
                                         char const * name, char short_name,
                                         char const * help,
                                         char const * arg_help, char sepchar)
	: option_base(name, short_name, help, arg_help,
	              &popt_value, POPT_ARG_STRING),
	  value(val), popt_value(0), separator(sepchar)
{
}


void option_imp< vector<string> >::post_process()
{
	if (popt_value) {
		value = separate_token(popt_value, separator);

		popt_value = 0;
	}
}

} // namespace popt