//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
// 
// This program is free software.  The contents of this file are subject
// to 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.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
// 
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please 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.
// 
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
// 
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
// cmdlineparser.cpp
#include "stdcore.h"
#include "cmdlineparser.h"
#include "corestrings.h"


///////////////////////////////////////////////////////////////////////////////
// ctor, dotr
///////////////////////////////////////////////////////////////////////////////
cCmdLineParser::cCmdLineParser() :
	mArgTable(HASH_VERY_SMALL),
	mLastArgInfo(-1, PARAM_NONE)
{
}

cCmdLineParser::~cCmdLineParser()
{
}

///////////////////////////////////////////////////////////////////////////////
// AddArg
///////////////////////////////////////////////////////////////////////////////
void cCmdLineParser::AddArg(int argId, const TSTRING& arg, const TSTRING& alias, ParamCount numParams, bool multipleAllowed)
{
	if(arg.empty() && alias.empty())
	{
		// this refers to the list of parameters that comes after all the cmd line switches
		mLastArgInfo.mId		= argId;
		mLastArgInfo.mNumParams = numParams;
		return ;
	}

	if(! arg.empty())
		mArgTable.Insert(arg,	cArgInfo(argId, numParams));
	if(! alias.empty())
	{
		// put the alias in the table with a '-' prepended to it so it matches '--'
		TSTRING str(_T("-"));
		str += alias;
		mArgTable.Insert(str, cArgInfo(argId, numParams));
	}
	// This argument can appear more than once on the command line.
	if( multipleAllowed )
		mMultipleAllowed.insert( argId );
}

///////////////////////////////////////////////////////////////////////////////
// Clear
///////////////////////////////////////////////////////////////////////////////
void cCmdLineParser::Clear()
{
	mLastArgInfo.mId		= -1;
	mLastArgInfo.mNumParams = PARAM_INVALID;
	mArgTable.Clear();
	mArgData.clear();
	mMutExList.clear();
}

///////////////////////////////////////////////////////////////////////////////
// Parse
///////////////////////////////////////////////////////////////////////////////
void cCmdLineParser::Parse(int argc, const TCHAR *const * argv)
{
	// clear out any existing data
	mArgData.clear();

	const TCHAR* pCurArg = 0;
	bool bProcessedFinalParams = false;		// gets set to true when the parameters to the command line are processed

	// I assume argv[0] is the executable name...
	for(int i=1; i < argc; i++)
	{
		if(argv[i][0] == _T('-'))
		{
			pCurArg = argv[i];

			// this is a switch; find it in the table...
			cArgInfo argInfo;
			if ( !mArgTable.Lookup( TSTRING(&argv[i][1] ), argInfo ) )
			{
				// unknown switch!
				throw eCmdLineInvalidArg( 
                    TSS_GetString( cCore, core::STR_ERR2_BAD_ARG_PARAMS ) 
                        + pCurArg );
			}
			//
			// make sure this hasn't been specified yet...
			//
			if( ArgInList( argInfo.mId ) )
			{
				// Make sure it isn't okay for this one to appear more than once...
				std::set<int>::iterator it = mMultipleAllowed.find( argInfo.mId );
				if( it == mMultipleAllowed.end() )
				{
					// It wasn't in our list of allowed params, so error.
					throw eCmdLineMultiArg(
						TSS_GetString( cCore, core::STR_ERR2_BAD_ARG_PARAMS ) 
							+ argv[i] );
				}
			}
			//
			// add it to the list..
			//
			mArgData.push_back(cArgData(argInfo.mId, TSTRING(argv[i])));
			cArgData& curArg = mArgData.back();
			switch( argInfo.mNumParams )
			{
			case PARAM_NONE:
				// make sure there are no parameters to this, but be careful because 
                // it is legal to start the parameters to the executable here.
				if((i+1 < argc) && (argv[i+1][0] != _T('-')))
                {
                    // search for any more parameters
                    // TODO: In the future we may want to support a '--' switch that specifies the start
                    // of parameters to the executable.
                    for (int j = i + 2; j < argc; ++j )
                    {
                        if (argv[j][0] == _T('-'))
				        {
					        // >0 parameter passed !
							throw eCmdLineBadParam(
                                TSS_GetString( cCore, core::STR_ERR2_BAD_ARG_PARAMS )
                                    + pCurArg );
				        }
                    }
                }
				break;

			case PARAM_ONE:
				// get the next parameter...
				i++;
				if ( (i >= argc) || (argv[i][0] == _T('-')) )
				{
					// zero parameters passed to something that needed one param
					throw eCmdLineBadParam(
                        TSS_GetString( cCore, core::STR_ERR2_BAD_ARG_PARAMS )
                            + pCurArg );
				}

				curArg.mParams.push_back( TSTRING(argv[i]) );
                break;

			case PARAM_MANY:
				i++;
				while((i < argc) && (argv[i][0] != _T('-')))
				{
					curArg.mParams.push_back(TSTRING(argv[i]));	
					i++;
				}
				i--;	// since we have gone too far at this point
				break;

            default:
				ASSERTMSG( false, "Unknown number of arguments to parser" );
			}
		}
		else
		{
			bProcessedFinalParams = true;
			// this must be the final "unnamed" arg
			// first, make sure it is consistent with the current info...
			bool bResult = true;
			switch(mLastArgInfo.mNumParams)
			{
			case PARAM_NONE:
				// this is an error; they didn't want any command line parameters...
				bResult = false;
				break;
			case PARAM_ONE:
				if(i+1 != argc)
					// there is >1 final parameter; it is an error
					bResult = false;
				break;
			case PARAM_MANY:
				// we'll catch errors below
				break;
			default:
				ASSERT(false);
			
			}
			if(! bResult)
			{
				throw eCmdLineBadParam( );
			}

			// ok, we can push the final parameter info onto the list...
			mArgData.push_back(cArgData(mLastArgInfo.mId));
			cArgData& curArg = mArgData.back();
			
			while ( i < argc )
			{
				if ( argv[i][0] == _T('-') )
				{
					if ( ! pCurArg )
					{
						throw eCmdLineBadSwitchPos(
                            TSS_GetString( cCore, core::STR_ERR2_BAD_ARG_PARAMS )
                                + argv[i] );
					}
					else
					{
						// there was an extra parameter passed somewhere!
						throw eCmdLineBadArgParam(
                            TSS_GetString( cCore, core::STR_ERR2_BAD_ARG_PARAMS )
                                + pCurArg );
					}
				}

				// add this param to the list
				curArg.mParams.push_back(TSTRING(argv[i]));
				i++;
			}
			
			
		}

	}

	// it is possible not to process the final command line parameters in the "else" case above
	// (this only occurs if there are no command line parameters specified) so let's make sure that
	// is consistent with what we are configured with...
	// NOTE -- it is ok to have no cmd line parameters if they specified PARAM_NONE or PARAM_MANY
	if(! bProcessedFinalParams)
	{
		if(mLastArgInfo.mNumParams == PARAM_ONE)
		{
			throw eCmdLineBadParam( );
		}
	}

	// Check for "relationship errors":
	TestMutEx();
	TestDependency();
}

///////////////////////////////////////////////////////////////////////////////
// TestMutEx
///////////////////////////////////////////////////////////////////////////////
void cCmdLineParser::TestMutEx()
{
	std::list<std::pair<int,int> >::const_iterator i;
	cCmdLineIter iter1(*this), iter2(*this);
	for(i = mMutExList.begin(); i != mMutExList.end(); i++)
	{
		//TODO -- there is a much more efficent way to do this (using cFCOPropVector, for example)
		//		the command line is presumably small enough, tho, that it probably isn't a big
		//		deal to do it this way.
		iter1.SeekToArg(i->first);
		if(! iter1.Done())
		{
			iter2.SeekToArg(i->second);
			if(! iter2.Done())
			{
				// we have a mutual exclusion violation!
				throw eCmdLineMutEx( 
                    iter1.ActualParam() 
                        + _T(", ")
                        + iter2.ActualParam() );
			}
		}
	}

}

///////////////////////////////////////////////////////////////////////////////
// TestDependency
///////////////////////////////////////////////////////////////////////////////
void cCmdLineParser::TestDependency()
{
	std::list< std::pair< std::pair< int, int>, bool > >::const_iterator i;
	cCmdLineIter iter1(*this), iter2(*this);

	for( i = mDependencyList.begin(); i != mDependencyList.end(); ++i)
	{
		iter1.SeekToArg( i->first.first );
		// was it on the command line?
		if( !iter1.Done() )
		{
			// it was, is the corresponding arg on the command line?
			iter2.SeekToArg( i->first.second );
			if( iter2.Done() ) // it wasn't, dependency error
			{
				TSTRING arg1, arg2, alias1, alias2;
				cCmdLineParser::LookupArgInfo( i->first.first, arg1, alias1 );
				cCmdLineParser::LookupArgInfo( i->first.second, arg2, alias2 );

				// determine in which form the user passed the arguments,
				// and construct the error message in the same form
				if ( iter1.ActualParam().length() == 2 )
					throw eCmdLineDependency( _T("The switch -") + arg1 + _T(" requires -") + arg2 +_T(".") );
				else
					throw eCmdLineDependency( _T("The switch --") + alias1 + _T(" requires --") + alias2 + _T(".") );
			}
		}
		else if( i->second )
		// only make this second check if the dependencies are MUTUAL,
		// as indicated (or not) by the bool value.
		{
			iter2.SeekToArg( i->first.second ); 
				// the first arg in the pair was not on the command line,
				// so just make sure the second isn't there...
			if( !iter2.Done() )
			{
				// arg2 appeared without arg1, so dependency error.
				TSTRING arg1, arg2, alias1, alias2;
				cCmdLineParser::LookupArgInfo( i->first.first, arg1, alias1 );
				cCmdLineParser::LookupArgInfo( i->first.second, arg2, alias2 );

				// determine in which form the user passed the arguments,
				// and construct the error message in the same form
				if ( iter1.ActualParam().length() == 2 )
					throw eCmdLineDependency( _T("The switch -") + arg2 + _T(" requires -") + arg1 +_T(".") );
				else
					throw eCmdLineDependency( _T("The switch --") + alias2 + _T(" requires --") + alias1 + _T(".") );
			}
		}

	} //end for
}


///////////////////////////////////////////////////////////////////////////////
// AddMutEx
///////////////////////////////////////////////////////////////////////////////
void cCmdLineParser::AddMutEx(int argId1, int argId2)
{
	// note that I do no checking for duplicates here...
	std::pair<int, int> mutEx(argId1, argId2);
	ASSERT(argId1 != argId2);
	mMutExList.push_back(mutEx);
}

///////////////////////////////////////////////////////////////////////////////
// AddDependency
///////////////////////////////////////////////////////////////////////////////
void cCmdLineParser::AddDependency(int argId1, int argId2, bool mutual )
{
	// again, no checking for duplicates... would a set
	// prove to be a better container for this operation?
	std::pair< int, int > Args( argId1, argId2 );
	std::pair< std::pair< int, int >, bool > Dep( Args, mutual ); 

	ASSERT(argId1 != argId2);
	mDependencyList.push_back( Dep);
}

///////////////////////////////////////////////////////////////////////////////
// TraceContents
///////////////////////////////////////////////////////////////////////////////
#ifdef _DEBUG
void cCmdLineParser::TraceContents(int dl) 
{
	cDebug d("cCmdLineParser::TraceContents");
	if(dl == -1)
		dl = cDebug::D_DEBUG;

	std::list<cArgData>::const_iterator i;
	for(i = mArgData.begin(); i != mArgData.end(); i++)
	{
		d.Trace(dl, "* Item id:%d\n", i->mId);
		for(std::vector<TSTRING>::const_iterator vi = i->mParams.begin(); vi != i->mParams.end(); vi++)
		{
			d.Trace(dl, "\t%s\n", vi->c_str());
		}
	}

	d.Trace(dl, "--- Switch id table ---\n");

	cHashTableIter<TSTRING, cArgInfo> iter(mArgTable);
	for(iter.SeekBegin(); ! iter.Done(); iter.Next())
	{
		d.Trace(dl, "[%d] %s\n", iter.Val().mId, iter.Key().c_str());
	}

	d.Trace(dl, "[%d] Final Param List\n", mLastArgInfo.mId);
}
#endif

///////////////////////////////////////////////////////////////////////////////
// LookupArgInfo
///////////////////////////////////////////////////////////////////////////////
bool cCmdLineParser::LookupArgInfo(int argId, TSTRING& arg, TSTRING& alias) const
{
	arg		= _T("");
	alias	= _T("");

	cHashTableIter<TSTRING, cArgInfo> iter(mArgTable);
	for(iter.SeekBegin(); ! iter.Done(); iter.Next())
	{
		if(iter.Val().mId == argId)
		{
			TSTRING str = iter.Key();
			if((str.length() > 0) && (str[0] == _T('-')))
			{
				// this is the alias!
				alias = (str.c_str() + 1);
			}
			else
			{
				// this is the arg...
				arg = str;
			}
		}
	}
	return ((! arg.empty()) || (! alias.empty()));
}

///////////////////////////////////////////////////////////////////////////////
// ArgInList
///////////////////////////////////////////////////////////////////////////////
bool cCmdLineParser::ArgInList(int argId)
{
	std::list<cArgData>::iterator i;
	for( i = mArgData.begin(); i != mArgData.end(); i++ )
	{
		if( i->mId == argId )
			return true;
	}
	return false;
}


//#############################################################################
// cCmdLineIter
//#############################################################################

///////////////////////////////////////////////////////////////////////////////
// SeekToArg
///////////////////////////////////////////////////////////////////////////////
bool	cCmdLineIter::SeekToArg(int argId) const
{
	for(SeekBegin(); ! Done(); Next())
	{
		if(ArgId() == argId)
			return true;
	}

	return false;

}

