// Copyright (C) 2001-2005 Gianni Mariani
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
//
// This exception applies only to the code released under the name GNU
// Common C++.  If you copy code from other releases into a copy of GNU
// Common C++, as the General Public License permits, the exception does
// not apply to the code that you add in this way.  To avoid misleading
// anyone as to the status of such modified files, you must delete
// this exception notice from them.
//
// If you write modifications of your own for GNU Common C++, it is your choice
// whether to permit this exception to apply to your modifications.
// If you do not wish that, delete this exception notice.
//

//
// cmdoptns.cpp
//

#include <cc++/config.h>
#include <cc++/string.h>
#include <cc++/thread.h>
#include <cc++/exception.h>
#include <cc++/export.h>
#include <cc++/cmdoptns.h>

#ifndef HAVE_GETOPT_LONG
// fix problem with vc++ library
#undef __argc
#undef __argv
#include "getopt.h"
#else
#include <getopt.h>
#endif

#ifndef WIN32
#include <unistd.h>
#endif

#include <cstdlib>
#include <iostream>
#include <fstream>

using std::fstream;

#ifdef	CCXX_NAMESPACES
namespace ost {
#endif

//
// In most cases, users will use this default option list.
//
CommandOption * defaultCommandOptionList = 0;

CommandOption::CommandOption(
	const char		* inOptionName,
	const char		* inOptionLetter,
	const char		* inDescription,
	OptionType		  inOptionType,
	bool			  inRequired,
	CommandOption  ** ppNext
)
	:	optionName( inOptionName ),
		optionLetter( inOptionLetter ),
		description( inDescription ),
		optionType( inOptionType ),
		required( inRequired ),
		next( * ppNext ) {
	* ppNext = this;
}

CommandOptionWithArg::CommandOptionWithArg(
	const char		* inOptionName,
	const char		* inOptionLetter,
	const char		* inDescription,
	OptionType		  inOptionType,
	bool			  inRequired,
	CommandOption  ** ppNext
)
	: CommandOption(
		inOptionName,
		inOptionLetter,
		inDescription,
		inOptionType,
		inRequired,
		ppNext
	), values( 0 ), numValue( 0 )
{
}


CommandOptionArg::CommandOptionArg(
	const char		* inOptionName,
	const char		* inOptionLetter,
	const char		* inDescription,
	bool			  inRequired,
	CommandOption  ** ppNext
)
	: CommandOptionWithArg(
		inOptionName,
		inOptionLetter,
		inDescription,
		hasArg,
		inRequired,
		ppNext
	)
{
}

CommandOptionRest::CommandOptionRest(
	const char		* inOptionName,
	const char		* inOptionLetter,
	const char		* inDescription,
	bool			  inRequired,
	CommandOption  ** ppNext
)
	: CommandOptionWithArg(
		inOptionName,
		inOptionLetter,
		inDescription,
		trailing,
		inRequired,
		ppNext
	)
{
}

CommandOptionCollect::CommandOptionCollect(
	const char		* inOptionName,
	const char		* inOptionLetter,
	const char		* inDescription,
	bool			  inRequired,
	CommandOption  ** ppNext
)
	: CommandOptionWithArg(
		inOptionName,
		inOptionLetter,
		inDescription,
		collect,
		inRequired,
		ppNext
	)
{
}

CommandOptionNoArg::CommandOptionNoArg(
	const char		* inOptionName,
	const char		* inOptionLetter,
	const char		* inDescription,
	bool			  inRequired,
	CommandOption  ** ppNext
)
	: CommandOption(
		inOptionName,
		inOptionLetter,
		inDescription,
		noArg,
		inRequired,
		ppNext
	), numSet( 0 )
{
}

// ======== CommandOption =============================================
// PURPOSE:
//	CommandOption dummy methods ..
//

void CommandOption::parseDone( CommandOptionParse * cop )
{
}

void CommandOption::foundOption( CommandOptionParse * cop, const char * value)
{
}

void CommandOption::foundOption( CommandOptionParse * cop, const char ** value, int num )
{
}

void CommandOption::performTask( CommandOptionParse * cop )
{
}

bool CommandOption::hasValue()
{
	return true;
}

CommandOption::~CommandOption()
{
}

CommandOptionWithArg::~CommandOptionWithArg()
{
	if ( values ) {
		free( values );
		values = 0;
	}
}

CommandOptionParse::~CommandOptionParse(void)
{
}

// ======== CommandOptionArg ==========================================
// PURPOSE:
//	Methods for CommandOptionArg
//

bool CommandOptionWithArg::hasValue()
{
	return numValue > 0;
}

CommandOptionArg::~CommandOptionArg()
{
}

//
//
static void my_alloc( char *** vals, int num, int incr )
{
	int	num_alloc = 0;
	if ( * vals ) {
		num_alloc = num | 3;
	}

	if ( ( incr + num ) > num_alloc ) {
		int newsiz = ( incr + num ) | 3;
		* vals = ( char ** ) realloc( * vals, sizeof( ** vals ) * newsiz );
	}
}

void CommandOptionWithArg::foundOption( CommandOptionParse * cop, const char * value )
{
	if ( value ) {
		my_alloc( ( char *** ) & values, numValue ? numValue + 1 : 0, 1 );
		values[ numValue ++ ] = value;
		values[ numValue ] = 0;
	}
}

void CommandOptionWithArg::foundOption( CommandOptionParse * cop, const char ** value, int num )
{
	my_alloc( ( char *** ) & values, numValue ? numValue + 1 : 0, num + 1 );

	int	j = 0;
	for ( int i = numValue; j < num; i ++, j ++ ) {
		values[ i ] = value[ j ];
	}
	numValue += num;
	values[ numValue ] = 0;
}


void CommandOptionNoArg::foundOption( CommandOptionParse * cop, const char * value)
{
	numSet ++;
}

// ======== CommandOptionParse ========================================
// PURPOSE:
//	methods for CommandOptionParse
//

class CommandOptionParse_impl : public CommandOptionParse {
public:

	char			* comment;
	int				  num_options;
	struct option   * long_options;
	CommandOption  ** opt_list;
	CommandOption  ** co_list;
	char		    * optstring;
	int		argc;
	char		** argv;
	bool		has_err;
	char		* fail_arg;
	bool		usage_string_set;
	bool		required_errors_set;
	String		error_msgs;
	CommandOption	* fail_option;
	CommandOption	* trailing;

	String			  usage_string;

	virtual ~CommandOptionParse_impl() {
		delete[] opt_list;
		delete[] co_list;
		delete[] optstring;
		delete[] long_options;
	}

	CommandOptionParse_impl(
		int				in_argc,
		char		 ** in_argv,
		char		  * in_comment,
		CommandOption * options
	) :
		comment( in_comment ),
		argc( in_argc ),
		argv( in_argv ),
		has_err( false ),
		fail_arg( 0 ),
		usage_string_set( false ),
		required_errors_set( false ),
		error_msgs( "" ),
		fail_option( 0 ),
		trailing(0) {

		// First need to count all options.

		CommandOption		* to = options;
		int					  ocnt = 0;
		int					  ccnt = 0;
		int					  flag;

		while ( to ) {
			if ( to->optionName ) ocnt ++;
			ccnt ++;
			to = to->next;
		}

		num_options = ccnt;
#ifdef	__KCC
		co_list = new (CommandOption **)[ocnt];
		opt_list = new (CommandOption **)[ccnt];
#else
		// fix compiling bug in vc++
		typedef CommandOption* PCommandOption;
		co_list = new PCommandOption[ocnt];
		opt_list = new PCommandOption[ccnt];
#endif
		long_options = new option[ccnt+1];
		optstring = new char[ 2*ccnt+2 ];

		// initialize the last option count
		long_options[ ocnt ].name = 0;
		long_options[ ocnt ].has_arg = 0;
		long_options[ ocnt ].flag = 0;
		long_options[ ocnt ].val = 0;

		char	*tos = optstring;
		*(tos++) = '+';
		to = options;
		while ( to ) {

			if ( to->optionType == CommandOption::trailing ) {
				if ( ! trailing ) {
					trailing = to;
				}
			} else if ( to->optionType == CommandOption::collect ) {
				trailing = to;
			}

			opt_list[ -- ccnt ] = to;

			if ( to->optionName ) {
				-- ocnt;
				co_list[ ocnt ] = to;
				long_options[ ocnt ].name = to->optionName;
				long_options[ ocnt ].has_arg = to->optionType == CommandOption::hasArg;
				long_options[ ocnt ].flag = & flag;
				long_options[ ocnt ].val = ocnt;
			}

			if (  to->optionLetter && to->optionLetter[ 0 ] ) {
				* tos ++ = to->optionLetter[ 0 ];
				if ( to->optionType == CommandOption::hasArg ) {
					* tos ++ = ':';
				}
			}


			to = to->next;
		}
		* tos = 0;

		int c;
		int optionIndex;

		opterr = 0;	// tell getopt_long not to print any errors
		flag = -1;
		while ( optind < argc ) {

			if (
				(
					c = getopt_long(
						argc, argv, optstring, long_options, &optionIndex
					)
				) == -1
			) {
				if ( ! trailing ) {
					break;
				} else if ( trailing->optionType == CommandOption::trailing ) {
					break;
				} else {
					optarg = argv[ optind ];
					optind ++;
					to = trailing;
				}

			} else if ( flag != -1 ) {
				to = co_list[ flag ];
				flag = -1;
			} else if ( c == '?' ) {

				if ( optind < 2 ) {
					fail_arg = argv[ optind ];
				} else {
					fail_arg = argv[ optind - 1 ];
				}

				has_err = true;

				return;

			} else {

				// need to search through the options.

				for ( int i = 0; i < num_options; i ++ ) {

					to = opt_list[ i ];
					if ( ! to->optionLetter ) continue;

					if ( c == to->optionLetter[ 0 ] ) {

						break;
					}
				}
				// assert( to );
			}

			// do we terminate here ?
			if ( to->optionType == CommandOption::trailing ) {
				break;
			}

			if ( c != ':' ) {
				to->foundOption( this, optarg );
			} else {
				has_err = true;
				fail_option = to;
				break;
			}

		}

		if ( optind < argc ) {
			if ( trailing ) {
				trailing->foundOption(
					this,
					( const char ** ) ( argv + optind ),
					argc - optind
				);
			} else {
				has_err = true;
				fail_arg = argv[ optind ];
			}
		}

		// Now check to see that all required args made it !

		for ( int i = 0; i < num_options; i ++ ) {
			CommandOption	* toq = opt_list[ i ];

			// Tell this parameter that it's done now.
			toq->parseDone( this );

			if ( toq->required && ! toq->hasValue() ) {
				has_err = true;
				break;
			}
		}

	}

	bool	argsHaveError();

	virtual const char * printUsage();
	virtual const char * printErrors();

	void makePrintErrors() {
		if ( required_errors_set ) return;
		required_errors_set = true;

		if ( fail_arg ) {
			error_msgs = error_msgs + "Unknown/malformed option '" + fail_arg + "' \n";
		} else if ( fail_option ) {
			String name;
			bool name_msg;
			if ( fail_option->optionName ) {
				name_msg = true;
				name = fail_option->optionName;
			} else if ( fail_option->optionLetter ) {
				name_msg = true;
				name = fail_option->optionLetter;
			} else if ( fail_option == trailing ) {
				name_msg = false;
			} else {
				name = "--option with no name--";
				name_msg = true;
			}
			if ( name_msg ) {
				error_msgs = error_msgs + "Option '" + name + "' requires value\n";
			}
		} else if ( has_err ) {

			// loop thru all required args

			for ( int i = 0; i < num_options; i ++ ) {
				CommandOption	* to = opt_list[ i ];

				if ( to->required && ! to->hasValue() ) {
					error_msgs = error_msgs + "Value required for option '";

					if ( to->optionName ) {
						error_msgs = error_msgs + "--" + to->optionName;
					} else if ( to->optionLetter && to->optionLetter[ 0 ] ) {
						error_msgs = error_msgs + '-' + to->optionLetter[ 0 ];
					} else {
						error_msgs = error_msgs + to->description;
					}

					error_msgs = error_msgs + "' is missing\n";
				}
			}

		}

	}


	void makePrintUsage() {
		if ( usage_string_set ) return;

		String	str( "" );

		String	str_argv0 = argv[ 0 ];

		str = str + "Usage : ";

		String::size_type slashpos = str_argv0.rfind('/');
		if ( slashpos > str_argv0.length() ) {
			slashpos = 0;
		} else {
			slashpos ++;
		}

		str.append( str_argv0, slashpos, str_argv0.length() - slashpos );

		str = str + ' ' + comment + '\n';

		for ( int i = 0; i < num_options; i ++ ) {

			CommandOption	* to = opt_list[ i ];
			char			* begin = "\t";
			char			* obegin = "\t";

			to = opt_list[ i ];

			if ( to->optionLetter && to->optionLetter[ 0 ] ) {
				str = str + begin + '-' + to->optionLetter[ 0 ];
				begin = ", ";
				obegin = " - ";
			}

			if ( to->optionName ) {
				str = str + begin + "--" + to->optionName;
				begin = ", ";
				obegin = " - ";
			}

			if ( to->optionType == CommandOption::hasArg ) {
				str = str + begin + " <value>";
			} else if ( to->optionType == CommandOption::trailing ) {
				str = str + begin + " <rest of command...>";
			} else if ( to->optionType == CommandOption::collect ) {
				str = str + begin + " <...>";
			}

			str = str + obegin + to->description + "\n";
		}

		usage_string = str;
	}

	virtual void registerError( const char * errMsg ) {
		error_msgs = error_msgs + errMsg + '\n';
		has_err = true;
	}

	virtual void performTask() {
		for ( int i = 0; i < num_options; i ++ ) {
			CommandOption	* to = opt_list[ i ];

			// Each parameter has this invoked
			to->performTask( this );

		}
	}

};

CommandOptionParse * makeCommandOptionParse(
	int				   argc,
	char			** argv,
	char			 * comment,
	CommandOption	 * options
) {
	return new CommandOptionParse_impl( argc, argv, comment, options );
}

bool	CommandOptionParse_impl::argsHaveError()
{
	return has_err;
}

const char * CommandOptionParse_impl::printUsage()
{
	makePrintUsage();
	return usage_string.c_str();
}

const char * CommandOptionParse_impl::printErrors()
{
	makePrintErrors();
	return error_msgs.c_str();
}

#ifdef	CCXX_NAMESPACES
}
#endif

/** EMACS **
 * Local variables:
 * mode: c++
 * c-basic-offset: 8
 * End:
 */

