/*
GetOpt_pp:	Yet another C++ version of getopt.
    Copyright (C) 2007-2008  Daniel Gutson, FuDePAN

    This file is part of GetOpt_pp.
    
    GetOpt_pp is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as
    published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <unistd.h>
#include "getopt_pp.h"

#if __APPLE__
extern char** environ;
#endif


namespace GetOpt {

const char GetOpt_pp::EMPTY_OPTION = 0;

GETOPT_INLINE void GetOpt_pp::_init_flags()
{
	std::stringstream ss;
	_flags = ss.flags();
}

GETOPT_INLINE void GetOpt_pp::_parse(int argc, char* argv[])
{
	OptionData* currentData = NULL;
	_app_name = argv[0];

	// parse arguments by their '-' or '--':
	//   (this will be a state machine soon)
	for(int i=1; i < argc; i++)
	{
		const char current = argv[i][0];
		const char next = argv[i][1];
		
		if (current == '-' && (isalpha(next) || next == '-' ) )
		{			
			// see what's next, differentiate whether it's short or long:
			if (next == '-' && argv[i][2] != 0)
			{
				// long option
				currentData = &_longOps[&argv[i][2]];
			}
			else
			{
				// short option
				// iterate over all of them, keeping the last one in currentData
				// (so the intermediates will generate 'existent' arguments, as of '-abc')
				size_t j=1;
				do
				{
					currentData = &_shortOps[argv[i][j]];
					j++;
				}
				while (argv[i][j] != 0);
			}
		}
		else
		{
			// save value!
			if (currentData == NULL)
				currentData = &_shortOps[EMPTY_OPTION];
				
			currentData->args.push_back(argv[i]);
		}
	}
	
	_last = _Option::OK;	// TODO: IMPROVE!!
}

GETOPT_INLINE void GetOpt_pp::_parse_env()
{
	// this will be optimized in version 3
	std::string var_name;
	std::string var_value;
	size_t var=0;
	std::string::size_type pos;
	OptionData* data;
	
	while (environ[var] != NULL)
	{
		var_name = environ[var];
		pos = var_name.find('=');
		
		if (pos != std::string::npos)
		{
			var_value = var_name.substr(pos+1);
			var_name = var_name.substr(0, pos);
			
			if (_longOps.find(var_name) == _longOps.end())
			{
				data = &_longOps[var_name];
				data->args.push_back(var_value);
				data->flags = OptionData::Envir;
			}
		}
		else
			(data = &_longOps[var_name])->flags = OptionData::Envir;
			
		var++;
	}
}

GETOPT_INLINE GetOpt_pp::GetOpt_pp(int argc, char* argv[])
	: _exc(std::ios_base::goodbit)
{
	_init_flags();
	_parse(argc, argv);	
}

GETOPT_INLINE GetOpt_pp::GetOpt_pp(int argc, char* argv[], _EnvTag)
{
	_init_flags();
	_parse(argc, argv);	
	_parse_env();
}

GETOPT_INLINE GetOpt_pp& GetOpt_pp::operator >> (const _Option& opt) throw (GetOptEx)
{
	if (_last != _Option::ParsingError)
	{
		_last = opt(_shortOps, _longOps, _flags);
		
		switch(_last)
		{
			case _Option::OK: 
				break;
				
			case _Option::OptionNotFound:
				if (_exc & std::ios_base::eofbit )
					throw OptionNotFoundEx();
				break;
				
			case _Option::BadType:
				if (_exc & std::ios_base::failbit )
					throw InvalidFormatEx();
				break;
				
			case _Option::NoArgs:
				if (_exc & std::ios_base::eofbit )
					throw ArgumentNotFoundEx();
				break;
				
			case _Option::TooManyArgs:
				if (_exc & std::ios_base::failbit )
					throw TooManyArgumentsEx();
				break;
			
			case _Option::OptionNotFound_NoEx:
			    break;  // Ok, it will be read by casting to bool
			    
			case _Option::ParsingError: break;	// just to disable warning
		}
	}
	else if (_exc & std::ios_base::failbit )
		throw ParsingErrorEx();
		
	return *this;
}

GETOPT_INLINE GetOpt_pp& GetOpt_pp::operator >> (std::ios_base& (*iomanip)(std::ios_base&))
{
	std::stringstream ss;
	ss.flags(_flags);
	_flags = (ss << iomanip).flags();
	return *this;
}

GETOPT_INLINE bool GetOpt_pp::options_remain() const
{
	bool remain = false;
	ShortOptions::const_iterator it = _shortOps.begin();
	while (it != _shortOps.end() && !remain)
	{
		remain = (it->second.flags == OptionData::CmdLine_NotExtracted);
		++it;
	}
	
	if (!remain)
	{
		LongOptions::const_iterator it = _longOps.begin();
		while (it != _longOps.end() && !remain)
		{
			remain = (it->second.flags == OptionData::CmdLine_NotExtracted);
			++it;
		}
	}
	
	return remain;
}

}
