// Local configuration
#include "config.h"

// Implementation
#include "GetOpt.hpp"

// STDC++
#include <iostream>
#include <algorithm>
#include <cassert>

// C++ Libraries
#include <ui-utilcpp/Misc.hpp>

namespace UI {
namespace Util {

//
// CLOption
//
CLOption::CLOption(std::string const & nameLong, char nameShort, std::string const & doc, std::string const & defaultArg)
	:nameLong_(nameLong)
	,nameShort_(nameShort)
	,doc_(doc)
	,isGiven_(false)
	,arg_("")
	,defaultArg_(defaultArg)
{}

CLOption::~CLOption()
{}

std::string CLOption::getNameLong() const
{
	return nameLong_;
}

char CLOption::getNameShort() const
{
	return nameShort_;
}

std::string CLOption::getDoc() const
{
	return doc_;
}

bool CLOption::isGiven() const
{
	return isGiven_;
}

std::string CLOption::getArg() const
{
	return isGiven() ? arg_ : defaultArg_;
}

option * CLOption::getCOptLong(option * opt) const
{
	assert(opt);

	// Generate a GNU C long option from values. Compare "getopt.h".
	opt->name = nameLong_.c_str();
	opt->has_arg = 0;	// No args
	opt->flag = 0;		// This is evil, we do not use it
	opt->val = nameShort_;
	return opt;
}

std::string CLOption::getCOptShort() const
{
	std::string res (1, nameShort_);
	return res;
}

void CLOption::printUsage() const
{
	std::cout << "--" << nameLong_ << ", -" << nameShort_ << std::endl;
	std::cout << "  " << doc_ << std::endl;
}

void CLOption::setGiven(std::string const & arg)
{
	isGiven_ = true;
	arg_ = arg;
}


////////////////////////////////////////////////////////////////////////
// CLOptionArg Declarations
//
CLOptionArg::CLOptionArg(std::string const & nameLong, char nameShort, std::string const & doc, std::string const & defaultArg)
	:CLOption(nameLong, nameShort, doc, defaultArg)
{}

std::string CLOptionArg::getCOptShort() const
{
	std::string res (1, getNameShort());
	return res + ":";
}

option * CLOptionArg::getCOptLong (option * opt) const
{
	assert(opt);
	CLOption::getCOptLong (opt);
	opt->has_arg = 1;	// Argument is mandatory
	return opt;
}

void CLOptionArg::printUsage() const
{
	std::cout << "--" << getNameLong() << "=<argument>, -" << getNameShort() << "<argument>";
	if (!defaultArg_.empty())
	{
		std::cout << "     :: defaults to \"" << defaultArg_ << "\"" << std::endl;
	}
	else
	{
		std::cout << std::endl;
	}
	std::cout << "  " << getDoc() << std::endl;
}

////////////////////////////////////////////////////////////////////////
// CLOptionOptArg Declarations
//
CLOptionOptArg::CLOptionOptArg(std::string const & nameLong, char nameShort, std::string const & doc, std::string const & defaultArg)
	:CLOption(nameLong, nameShort, doc, defaultArg)
{}

std::string CLOptionOptArg::getCOptShort() const
{
	std::string res (1, getNameShort());
	return res + "::";
}

option * CLOptionOptArg::getCOptLong (option * opt) const
{
	assert (opt);
	CLOption::getCOptLong(opt);
	opt->has_arg = 2;	// Argument is optional
	return opt;
}

void CLOptionOptArg::printUsage() const
{
	std::cout << "--" << getNameLong() << "[=<argument>], -" << getNameShort() << "[<argument>]";
	if (!defaultArg_.empty())
	{
		std::cout << "     :: defaults to \"" << defaultArg_ << "\"" << std::endl;
	}
	else
	{
		std::cout << std::endl;
	}
	std::cout << "  " << getDoc() << std::endl;
}

////////////////////////////////////////////////////////////////////////
// GetOpt Declarations
//
GetOpt::GetOpt(int argc, char * const * const argv)
	:argc_(argc)
	,argv_(argv)
	,isValid_(false)
	,isParsed_(false)
{}

GetOpt::~GetOpt()
{
	UI::Util::delAnySeqContainer<CLOption>(opts_);
}

GetOpt & GetOpt::set(std::string const & nameLong, char nameShort, Type type, std::string const & doc, std::string const & defaultArg)
{
	assert(!isParsed_);
	CLOption * newOpt = 0;
	switch(type)
	{
	case NoArg_:
		newOpt = new CLOption(nameLong, nameShort, doc, defaultArg);
		break;
	case OptArg_:
		newOpt = new CLOptionOptArg(nameLong, nameShort, doc, defaultArg);
		break;
	case Arg_:
		newOpt = new CLOptionArg(nameLong, nameShort, doc, defaultArg);
		break;
	}
	assert(newOpt);
	opts_.push_back(newOpt);

	return *this;
}

CLOption * GetOpt::get(char nameShort)
{
	if (! isParsed_)
	{
		parse();
	}
	CLOption * result = 0;

	for (std::vector < CLOption * >::iterator i=opts_.begin(); i != opts_.end(); i++)
	{
		CLOption *co = *i;
		if (co->getNameShort() == nameShort)
		{
			result = co;
			continue;
		}
	}
	assert(result);
	return result;
}

CLOption * GetOpt::get(std::string const & nameLong)
{
	if (!isParsed_)
	{
		parse();
	}
	CLOption * result = 0;
	for (std::vector < CLOption * >::iterator i=opts_.begin(); i != opts_.end(); i++)
	{
		CLOption *co = *i;
		if (co->getNameLong() == nameLong)
		{
			result = co;
			continue;
		}
	}
	assert(result);
	return result;
}

void GetOpt::parse()
{
	isParsed_ = true;
	isValid_ = true;
	// Generate long and short opts as needed by getopt_long(3)
	const int s(opts_.size() + 1);
	std::vector<struct option> gnucLongOpts(s);
	std::string gnucShortOpts;

	unsigned int j(0);
	for (std::vector <CLOption *>::const_iterator i(opts_.begin()); i != opts_.end(); i++)
	{
		assert(j < opts_.size());
		// Longopts
		CLOption *co = *i;
		co->getCOptLong(&gnucLongOpts[j]);
		// ShortOpts
		gnucShortOpts += co->getCOptShort();
		++j;
	};
	// Finish gnuc long opts with end struct
	const struct option endOpt = { 0, 0, 0, 0 };
	gnucLongOpts[j] = endOpt;

	// This Loop will parse arguments, and set option objects accordingly
	int opt;
	int option_index;
	while ((opt =	getopt_long (argc_, argv_, gnucShortOpts.c_str(), (struct option *) &gnucLongOpts[0], &option_index)) != -1)
	{
		if (opt == '?')
		{
			isValid_ = false;
		}
		else
		{
			for (std::vector < CLOption * >::iterator i = opts_.begin(); i != opts_.end(); i++)
			{
				CLOption *co = *i;
				if (co->getNameShort() == opt)
				{
					co->setGiven (std::string(optarg == 0 ? "" : optarg));
				}
			}
		}
	}
}

bool GetOpt::isValid()
{
	if (!isParsed_)
	{
		parse();
	}
	return isValid_;
}

int GetOpt::wrongUsage(std::string const & reason, std::string const & title) const
{
	std::cerr << "\nWrong usage: " << reason << "." << std::endl;
	printUsage(title);
	return 1;
}

void GetOpt::printUsage(std::string const & title) const
{
	std::cout << title;
	std::for_each(opts_.begin(), opts_.end(), std::mem_fn(&CLOption::printUsage));
}

}}
