/*  -*- mode: clanlib -*-
**  $Id: command_line_generic.cpp,v 1.5 2003/11/12 16:28:24 mbn Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library 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
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
*/

#include "Core/precomp.h"

#include <iomanip>
#include <iostream>
#include <stdio.h>

#include "API/Core/System/error.h"
#include "API/Core/System/clanstring.h"
#include "command_line_generic.h"


#ifdef WIN32
#define snprintf _snprintf
#endif

CL_CommandLine_Generic::CL_CommandLine_Generic()
{
	help_indent = 18;
}

void CL_CommandLine_Generic::parse_args(int argc, char** argv)
{
	programm = argv[0];

	for(int i = 1; i < argc; ++i) 
	{
		if (argv[i][0] == '-') 
		{
			if (argv[i][1] == '-') 
			{
				// We got a long option
				if (argv[i][2] == '\0') { 
					// Got a '--', so we stop evaluating arguments
					++i;
					while(i < argc) 
					{
						read_option(CL_CommandLine::REST_ARG, argv[i]);
						++i;
					}
				} 
				else
				{
					std::string opt = argv[i] + 2;
					std::string long_opt;
					std::string long_opt_arg;

					std::string::size_type pos = opt.find('=');

					if (pos != std::string::npos) 
					{
						long_opt = opt.substr(0, pos);
						long_opt_arg = opt.substr(pos+1);
					}
					else 
					{
						long_opt = opt;
					}

					// Long Option
					Option* option = lookup_long_option(long_opt);

					if (option) 
					{
						if (option->argument.empty()) 
						{
							read_option(option->key, "");
						} 
						else
						{
							if (pos != std::string::npos) 
							{
								read_option(option->key, long_opt_arg);
							}
							else
							{            
								if (i == argc - 1) 
								{
									throw CL_Error("option '" + std::string(argv[i]) + "' requires an argument");
								}
								else 
								{
									read_option(option->key, argv[i + 1]);
									++i;
								}
							}
						}
					}
					else
					{
						throw CL_Error("unrecognized option '" + std::string(argv[i]) + "'");
					}
				}
			} 
			else 
			{
				// We got a short option
				char* p = argv[i] + 1;
          
				if (*p != '\0') {
					// Handle option chains
					while (*p) 
					{
						// Short option(s)
						Option* option = lookup_short_option(*p);

						if (option) 
						{
							if (option->argument.empty()) 
							{
								read_option(option->key, "");
							} 
							else 
							{
								if (i == argc - 1 || *(p+1) != '\0') 
								{
									// No more arguments
									throw CL_Error("option requires an argument -- " + std::string(1, *p));
								}
								else
								{
									read_option(option->key, argv[i + 1]);
									++i;
								}
							}
						} 
						else 
						{
							throw CL_Error("invalid option -- " + std::string(1, *p));
						}
						++p; 
					}
				} 
				else
				{
					read_option(CL_CommandLine::REST_ARG, "-");
				} 
			}
		} 
		else
		{
			read_option(CL_CommandLine::REST_ARG, argv[i]);
		}
	}

	current_option = parsed_options.end();
}

CL_CommandLine_Generic::Option *CL_CommandLine_Generic::lookup_short_option(char short_option)
{
	for(Options::iterator i = options.begin(); i != options.end(); ++i)
	{
		if (i->key == short_option)
			return &(*i);
	}
	return 0;
}

CL_CommandLine_Generic::Option *CL_CommandLine_Generic::lookup_long_option(const std::string& long_option)
{
	for(Options::iterator i = options.begin(); i != options.end(); ++i)
	{
		if (i->long_option == long_option)
			return &*i;
	}
	return 0;
}

void CL_CommandLine_Generic::read_option(int key, const std::string& argument)
{
	ParsedOption parsed_option;
  
	parsed_option.key = key;
	parsed_option.argument = argument;

	parsed_options.push_back(parsed_option);
}

void CL_CommandLine_Generic::print_help()
{
	bool first_usage = true;
	for(Options::iterator i = options.begin(); i != options.end(); ++i)
	{
		if (i->visible)
		{
			if (i->key == USAGE) 
			{
				if (first_usage) 
				{
					std::cout << "Usage: " << programm << " " <<  i->help << std::endl; 
					first_usage = false;
				}
				else
				{
					std::cout << "or:    " << programm << " " << i->help << std::endl; 
				}
			} 
			else if (i->key == GROUP) 
			{
				if (i != options.begin())
					std::cout << std::endl;
				std::cout << i->help << std::endl;
			}
			else if (i->key == DOC) 
			{
				if (i != options.begin())
					std::cout << std::endl;
				std::cout << i->help << std::endl;
			}
			else 
			{
				char option[256]   = { 0 };
				char argument[256] = { 0 };

				if (i->key > 255 || i->key < 0)
					snprintf(option, 256, "--%s", i->long_option.c_str());
				else if (i->long_option.empty())
					snprintf(option, 256, "-%c", char(i->key));
				else
					snprintf(option, 256, "-%c, --%s", char(i->key), i->long_option.c_str());

				if (!i->argument.empty())
				{
					if (i->long_option.empty())
						snprintf(argument, 256, " %s", i->argument.c_str());
					else
						snprintf(argument, 256, "=%s", i->argument.c_str());
				}

				std::cout << "  " 
							 << std::setiosflags(std::ios::left) << std::setw(help_indent)
							 << (std::string(option) + std::string(argument)) << std::setw(0)
							 << " " << i->help << std::endl;
			}
		}
	}
	std::cout << std::endl;
}

void CL_CommandLine_Generic::add_usage(const std::string& usage)
{
	Option option;

	option.key          = USAGE;
	option.help         = usage;
	option.visible      = true;

	options.push_back(option);   
}

void CL_CommandLine_Generic::add_doc(const std::string& grouptopic)
{
	Option option;

	option.key          = DOC;
	option.help         = grouptopic;
	option.visible      = true;

	options.push_back(option);  
}

void CL_CommandLine_Generic::add_group(const std::string& grouptopic)
{
	Option option;

	option.key          = GROUP;
	option.help         = grouptopic;
	option.visible      = true;

	options.push_back(option);  
}

void CL_CommandLine_Generic::add_option(int key, 
                      const std::string& long_option, 
                      const std::string& argument,
                      const std::string& help,
                      bool visible)
{
	Option option;

	option.key          = key;
	option.help         = help;
	option.long_option  = long_option;
	option.argument     = argument;
	option.visible      = visible;

	options.push_back(option);
}

bool CL_CommandLine_Generic::next()
{
	if (current_option == parsed_options.end()) 
	{
		(current_option = parsed_options.begin());
		return current_option != parsed_options.end();
	}
	else
	{
		return (++current_option) != parsed_options.end();
	}
}

int CL_CommandLine_Generic::get_key()
{
	return current_option->key;
}

std::string CL_CommandLine_Generic::get_argument()
{
	return current_option->argument;
}
