/**------------------------------------------------------------------------
 * @file
 *
 * Copyright 2001 by the University of Toronto (UT).
 *
 *                       All Rights Reserved
 * 
 * Permission to copy and modify this software and its documentation 
 * only for internal use in your organization is hereby granted, 
 * provided that this notice is retained thereon and on all copies.  
 * UT makes no representations as to the suitability and operability 
 * of this software for any purpose.  It is provided "as is" without 
 * express or implied warranty.
 *
 * UT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT
 * SHALL UT BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
 * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * No other rights, including for example, the right to redistribute 
 * this software and its documentation or the right to prepare 
 * derivative works, are granted unless specifically provided in a 
 * separate license agreement.
 *
 * Author: Diego Macrini
 * 
 * $Log: CmdLineParser.cpp,v $
 * Revision 1.2  2002/05/17 00:26:39  dmac
 * Modification to make the code work with any kind of DAG.
 *
 * Revision 1.1.1.1  2002/03/10 07:38:08  dmac
 * Imported sources
 *
 *
 *-----------------------------------------------------------------------*/
#include <iomanip>
#include <fstream>
#include <vector>
#include <list>
#include <algorithm> // for sort

#ifdef WIN32
#include <windows.h>
#include "getopt.h"
#else
typedef char TCHAR;
#define MAX_PATH 1024
#endif

#include "CmdLineParser.h"
#include "BasicUtils.h"

typedef std::list<std::string> CmdLineParams;

/*!
	Creates a new process and waits for it to finish. 

	Note: the arguments 'szArgs' should not include the app name
	as its first argument. It will be added by this function.
*/
bool CreateProcessAndWait(const char* szAppName, const char* szArgs)
{
	/*STARTUPINFO si;
	PROCESS_INFORMATION pi;

	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	ZeroMemory(&pi, sizeof(pi));

	// We need a non-const string with the arguments and also
	// to add the app name as the first parameter surrounded by quotes
	// The number of extra chars to add is 4: 2 quotes, one space, one null char
	char* szCmds = new char[strlen(szAppName) + strlen(szArgs) + 4];

	sprintf(szCmds, "\"%s\" %s", szAppName, szArgs);

	//std::cerr << "Creating process with params: " << szCmds << std::endl;

	BOOL bHasProc = CreateProcess(szAppName, szCmds, 
		NULL,           // Process handle not inheritable
		NULL,           // Thread handle not inheritable
		FALSE,          // Set handle inheritance to FALSE
		0,              // No creation flags
		NULL,           // Use parent's environment block
		NULL,           // Use parent's starting directory 
		&si,            // Pointer to STARTUPINFO structure
		&pi             // Pointer to PROCESS_INFORMATION structure
	);

	if (!bHasProc)
		return false;

	// Wait until child process exits
	WaitForSingleObject(pi.hProcess, INFINITE);

	DWORD dwExitCode;

	// Not really used right now
	GetExitCodeProcess(pi.hProcess, &dwExitCode);

	// Close process and thread handles 
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	// Delete the temporary command line args
	delete[] szCmds;

	return true;*/
	return false;
}

void ShowCurrentDirectory()
{
#ifdef WIN32
	TCHAR szDirectory[MAX_PATH];

	szDirectory[0] = 0;

	GetCurrentDirectory(sizeof(szDirectory) - 1, szDirectory);

	std::cerr << "\nCurrent directory is: " 
		<< szDirectory << std::endl;
#else
	std::cerr << "\nThis function is windows only. sorry." << std::endl;
#endif
}

/*!
	This handy function is from: 
	http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html
*/
void Tokenize(const std::string& str, std::vector<std::string>& tokens, 
			  const std::string& delimiters = " ")
{
	using namespace std;

    // Skip delimiters at beginning
	string::size_type lastPos = str.find_first_not_of(delimiters, 0);

    // Find first "non-delimiter"
    string::size_type pos = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector
        tokens.push_back(str.substr(lastPos, pos - lastPos));

        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);

        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

/*!
	Checks if pat is a section beginning

	Possible patters are:

	'=>' starts a new set of commands

	'^>' starts a common set of commands added as prefix.

	'$>' starts a common set of commands added as subfix.

	'<*>' starts of comment pattern. In this case, * means any character.
	      For example, '<=>', '<^>', '<$>', ..., '<*>'
*/
bool IsBeginningOfSection(const std::string& pat)
{
	if (pat.size() == 2 && pat[1] == '>')
	{
		return (pat[0] == '=' || pat[0] == '^' || pat[0] == '$');
	}
	else if (pat.size() == 3 && pat[0] == '<' && pat[2] == '>')
	{
		return true;
	}

	return false;
}

/*!
	Read the commands in the current section and returns the next
	patter of section beginning found, or the empty string if EOF is found
*/
std::string ReadPatternDelimitedSection(std::ifstream& is, std::string& cmds)
{
	std::string buf;

	cmds.clear();

	while (is >> buf)
	{
		if (IsBeginningOfSection(buf))
		{
			return buf;
		}
		else
		{
			cmds += buf + " ";
		}
	}

	// We reached th end of the file. Return the empty string.
	return std::string();
}

/*!
	@bried Reads command-line arguments from a file

	If the -file argument is given with a file name, the respective
	file is opened and the command line arguments are read from it.

	The parameters in the file are specified by using the following patterns
	of characters:

	'=>' starts a new set of commands

	'^>' starts a common set of commands added as prefix.

	'$>' starts a common set of commands added as subfix.

	'<*>' starts of comment pattern. In this case, * means any character.
	      For example, '<=>', '<^>', '<$>', ..., '<*>'
	
	Both commands and comments section can include new line characters and
	end whenever another pattern is found or at the end of the file (EOF).

	Each set of commands beginning with '=>' is run on a different process.
	All processes are run sequentially.
*/
bool CmdLineParser::Parse(int argc, char** argv)
{
	if (argc == 3 && !strcmp(argv[1], "-file"))
	{
		CmdLineParams paramList;
		std::ifstream is(argv[2]);
		
		// Read the first list of arguments found in the file
		if (is) 
		{
			std::string curPat, nextPat, cmdLinePrefix, cmdLine, 
				cmdLineSubfix, fullCmdLine;

			// Find the first pattern
			curPat = ReadPatternDelimitedSection(is, cmdLine);
			
			while (!curPat.empty())
			{
				nextPat = ReadPatternDelimitedSection(is, cmdLine);

				if (curPat == "=>")
				{
					fullCmdLine = cmdLinePrefix + cmdLine + cmdLineSubfix;

					paramList.push_back(fullCmdLine);
				}
				else if (curPat == "^>")
				{
					cmdLinePrefix = cmdLine;
				}
				else if (curPat == "$>")
				{
					cmdLineSubfix = cmdLine;
				}
				else
				{
					// ignore comments
				}

				curPat = nextPat;
			}

			// We are done reading the file, so we can close it
			is.close();

			// If there is only one set of commads to execute, we do
			// so with the current process so that we can debug the code
			if (paramList.size() == 1)
			{
				return ParseArguments(argv[0], paramList.front());
			}
			else
			{
				CmdLineParams::iterator it;

				for (it = paramList.begin(); it != paramList.end(); it++)
				{
					if (!CreateProcessAndWait(argv[0], it->c_str()))
					{
						std::cerr << "Error: Cannot create new process '" << argv[0]
							 <<"' with command-line arguments: " << *it << std::endl;

						ShowCurrentDirectory();

						return false;
					}

				}

				// Return false to signal that there are no params to process
				return false;
			}
		}
		else 
		{
			std::cerr << "Error: Cannot open command-line argument file '" 
				<< argv[2] << "'" << std::endl;

			return false;
		}
	}
	
	return ParseArguments(argc, argv);
}

/*! 
	Tokenizes the command line string and stores each
	token in m_localArgv that they can be referenced outside
	this function. Tokens are only deleted when the program exits
*/
bool CmdLineParser::ParseArguments(const char* argv0, const std::string& args)
{
	std::vector<std::string> tokens;

	Tokenize(args, tokens, " ");

	m_localArgc = tokens.size() + 1;

	m_localArgv = new char*[m_localArgc];

	m_localArgv[0] = new char[strlen(argv0) + 1];

	strcpy(m_localArgv[0], argv0);

	for (int i = 1; i < m_localArgc; i++)
	{
		m_localArgv[i] = new char[tokens[i - 1].size() + 1];

		strcpy(m_localArgv[i], tokens[i - 1].c_str());
	}

	return ParseArguments(m_localArgc, m_localArgv);
}

/*
          struct option {
              const char *name;
              int has_arg;
              int *flag;
              int val;
          };

       The meanings of the different fields are:

       name   is the name of the long option.

       has_arg
              is:   no_argument   (or   0)  if  the  option  does  not  take  an  argument,
              required_argument  (or  1)  if  the   option   requires   an   argument,   or
              optional_argument (or 2) if the option takes an optional argument.

       flag   specifies  how results are returned for a long option.  If flag is NULL, then
              getopt_long() returns val.  (For example, the calling program may set val  to
              the  equivalent short option character.)  Otherwise, getopt_long() returns 0,
              and flag points to a variable which is set to val if the option is found, but
              left unchanged if the option is not found.

       val    is the value to return, or to load into the variable pointed to by flag.

       The last element of the array has to be filled with zeroes.

       If  longindex  is not NULL, it points to a variable which is set to the index of the
       long option relative to longopts.

*/

bool CmdLineParser::ParseArguments(int argc, char** argv)
{         
	/*std::cerr << "\n\nInput parameters: ";

	for (int i = 1; i < argc; i++)
		std::cerr << argv[i] << " "; 
	
	std::cerr << std::endl;
	
	return false;*/

	CmdLineArgT* pArg;
	bool bError = false;
	int c, i, j, nOptionIndex, argCount;

	SetPrgName(argv[0]);

	InitOptions();
	
	OPTION* longOptions = new OPTION[m_nOptArgs + 1];
	char* szShortOptions = new char[m_nOptArgs + 1];
	
	for (i = 0, j = 0; i < m_nOptArgs; i++)
	{
		pArg = m_pOptArgs[i];
		
		longOptions[i] = *pArg;
		
		if (pArg->HasShortName())
		{
			szShortOptions[j++] = pArg->GetShortName();
			
			if (pArg->HasOptArg())
				szShortOptions[j++] = ':';
		}
	}
	
    longOptions[m_nOptArgs].SetToZero();
	szShortOptions[j] = '\0';
	argCount = 0;
	
	while (true)
	{
		c = getopt_long_only(argc, argv, szShortOptions, longOptions, &nOptionIndex);
		
		if (c == -1)
			break;
		else if (c == 0)
			pArg = m_pOptArgs[nOptionIndex];
		else if (c == '?') {
			bError = true;
			break;
		}
		else {
			// Find the arg that corresponds to the short option returned
			i = GetArgIndex(c);
			
			if (i >= 0)
				pArg = m_pOptArgs[i];
			else {	
				printf ("?? getopt returned character code 0%o ??\n", c);
				bError = true;
				break;
			}
		}
		
		pArg->Select(optarg);
		pArg->SetOrder(argCount++);
	}

	delete[] longOptions;
	delete[] szShortOptions;
	
	if (bError)
	{
		ShowUsageMsg();
		return false;
	}

	// Process non-option ARGV-elements
	
	if (optind < argc)
	{
		m_pNonOptArgs = argv + optind;
		m_nNonOptArgs = argc - optind;
	}
	
	return true;
}

//! Find the arg that corresponds to the given short option
int CmdLineParser::GetArgIndex(char c)
{
	for (int i = 0; i < m_nOptArgs; i++)
		if (c == m_pOptArgs[i]->GetShortName())
			return i;

	return -1;
}

int CmdLineParser::GetArgIndex(const char* szArgName)
{
	for (int i = 0; i < m_nOptArgs; i++)
		if (!strcmp(szArgName, m_pOptArgs[i]->GetLongName()))
			return i;

	return -1;
}

bool CmdLineParser::IsSelected(char c)
{
	int i = GetArgIndex(c);
	return i >= 0 ? m_pOptArgs[i]->IsSelected():false;
}

bool CmdLineParser::IsSelected(const char* szArgName)
{
	int i = GetArgIndex(szArgName);
	return i >= 0 ? m_pOptArgs[i]->IsSelected():false;
}

std::ostream& CmdLineParser::Print(std::ostream& os)
{
	os << "\nCurrent parameter values:\n\n";

	for (int i = 0; i < m_nOptArgs; i++)
		if (m_pOptArgs[i]->Print(os, false) && i < m_nOptArgs - 1) 
			os << ", ";

	return os << std::endl;
}

/*!
	Executes actions in the order they are given in the command line
*/
void CmdLineParser::ExecActions()
{
	std::vector< std::pair<int, int> > actions;

	actions.reserve(m_nOptArgs);

	// Call the functions associated with the selected options
	for (int i = 0; i < m_nOptArgs; i++)
		if (m_pOptArgs[i]->IsSelected() && m_pOptArgs[i]->HasAction())
			actions.push_back(std::make_pair(m_pOptArgs[i]->GetOrder(), i));

	// Sort by order (ie, first field of pair)
	std::sort(actions.begin(), actions.end());

	for (unsigned int i = 0; i < actions.size(); i++)
		ExecAction(m_pOptArgs[actions[i].second]->GetAction());
}

void CmdLineParser::SetPrgName(const char* szProgramName)
{
	/*int len = strlen(szProgramName);
	int i;
	char c;

	for (i = len - 1; i >= 0; i--) 
	{
		c = szProgramName[i];

		if (c == '/' || c == '\\')
		{
			i++;
			break;
		}
	}

	m_szProgramName = szProgramName + i;*/
	m_szProgramName = szProgramName;
}

void CmdLineParser::ShowUsageMsg()
{
	const int FCS = 30; // first column size
	char szLine[512];
	char* p;

	CmdLineArgT* pArg;

	std::cerr << "\nUsage:\n\t" << m_szProgramName << " [option (val)]* ";
		
	if (m_szOptArgHelp)
		std::cerr << m_szOptArgHelp;
	
	std::cerr << "\n\nOptions:\n";

	for (int i = 0; i < m_nOptArgs; i++)
	{
		pArg = m_pOptArgs[i];
		p = szLine;

		strcpy(szLine, "\t");
		p += strlen(p);

		if (pArg->HasShortName())
		{
			sprintf(p, "-%c ", pArg->GetShortName());
			p += strlen(p);
		}

		if (pArg->HasLongName())
		{
			if (pArg->HasShortName())
			{
				strcpy(p, "or ");
				p += strlen(p);
			}

			sprintf(p, "-%s ", pArg->GetLongName());
			p += strlen(p);
		}

		if (pArg->HasOptArg())
		{
			sprintf(p, "[val]");
			p += strlen(p);
		}
		
		for (; p < szLine + FCS; p++)
			*p = ' ';

		strcpy(p, pArg->GetDesc());

		std::cerr << szLine << std::endl;
	}
	
	Print(std::cerr);

	ShowUsageExamples();
}

void CmdLineParser::ShowErrorMsg(const char* szMsg) const
{
	std::cerr << std::endl << "Incorrect command line argument(s): " 
		<< szMsg << std::endl << std::endl;
}

//! ascii to ascii trivial function
LPCSTR asciitoascii(LPCSTR sz) 
{ 
	return sz; 
}
