/*******************************************************************************
 * libretroshare/src/util: argstream.h                                         *
 *                                                                             *
 * libretroshare: retroshare core library                                      *
 *                                                                             *
 * Copyright (C) 2004 Xavier Decoret <Xavier.Decoret@imag.fr>                  *
 *                                                                             *
 * This program 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 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 Lesser General Public License for more details.                         *
 *                                                                             *
 * You should have received a copy of the GNU Lesser General Public License    *
 * along with this program. If not, see <https://www.gnu.org/licenses/>.       *
 *                                                                             *
 *******************************************************************************/

#ifndef ARGSTREAM_H
#define ARGSTREAM_H


#include <stdlib.h>
#include <string>
#include <list>
#include <deque>
#include <map>
#include <stdexcept>
#include <sstream>
#include <iostream> 

namespace 
{
	class argstream;

	template<class T>
		class ValueHolder;

	template <typename T>
		argstream& operator>> (argstream&, const ValueHolder<T>&);
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Interface of ValueHolder<T>
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	template<class T>
		class ValueHolder
		{
			public:
				ValueHolder(char s,
						const char* l,
						T& b,
						const char *valueName,
						const char* desc,
						bool mandatory);
				ValueHolder(const char* l,
						T& b,
						const char *valueName,
						const char* desc,
						bool mandatory);
				ValueHolder(char s,
						T& b,
						const char *valueName,
						const char* desc,
						bool mandatory);
				friend argstream& operator>><>(argstream& s,const ValueHolder<T>& v);
				std::string name() const;
				std::string description() const;
			private:
				std::string shortName_;
				std::string longName_;
				T*          value_;
				std::string valueName_;
				T           initialValue_;
				std::string description_;  
				bool        mandatory_;
		};
	template <class T>
		inline ValueHolder<T>
		parameter(char s,
				const char* l,
				T& b,
				const char* desc="",
				bool mandatory = true)
		{
			return ValueHolder<T>(s,l,b,"value",desc,mandatory);
		}
	template <class T>
		inline ValueHolder<T>
		parameter(char s,
				const char* l,
				T& b,
				const char* valueName="value",
				const char* desc="",
				bool mandatory = true)
		{
			return ValueHolder<T>(s,l,b,valueName,desc,mandatory);
		}
	template <class T>
		inline ValueHolder<T>
		parameter(char s,
				T& b,
				const char* valueName="value",
				const char* desc="",
				bool mandatory = true)
		{
			return ValueHolder<T>(s,b,valueName,desc,mandatory);
		}
	template <class T>
		inline ValueHolder<T>
		parameter(const char* l,
				T& b,
				const char* valueName="value",
				const char* desc="",
				bool mandatory = true)
		{
            return ValueHolder<T>(l,b,valueName,desc,mandatory);
		}
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Interface of OptionHolder
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	class OptionHolder
	{
		public:
			inline OptionHolder(char s,
					const char* l,
					bool& b,
					const char* desc);
			inline OptionHolder(const char* l,
					bool& b,
					const char* desc);
			inline OptionHolder(char s,
					bool& b,
					const char* desc);
			friend argstream& operator>>(argstream& s,const OptionHolder& v);
			inline std::string name() const;
			inline std::string description() const;
		protected:
			inline OptionHolder(char s,
					const char* l,
                                        const char* desc);
 			friend OptionHolder help(char s,
 					const char* l,
 					const char* desc);

		private:
			std::string shortName_;
			std::string longName_;
			bool*       value_;
			std::string description_;  
	};
	inline OptionHolder
		option(char s,
				const char* l,
				bool& b,
				const char* desc="")
		{
			return OptionHolder(s,l,b,desc);
		}
	inline OptionHolder
		option(char s,
				bool& b,
				const char* desc="")
		{
			return OptionHolder(s,b,desc);
		}
	inline OptionHolder
		option(const char* l,
				bool& b,
				const char* desc="")
		{
			return OptionHolder(l,b,desc);
		}
	inline OptionHolder
		help(char s,
				const char* l,
				const char* desc)
		{
			return OptionHolder(s,l,desc);
		}
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Interface of ValuesHolder
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	template<class T,class O>
		class ValuesHolder
		{
			public:
				ValuesHolder(const O& o,
						const char* desc,
						int len);
				template<class A,class B> friend argstream& operator>>(argstream& s,const ValuesHolder<A,B>& v);
				std::string name() const;
				std::string description() const;
				typedef T value_type;
			private:
				mutable O   value_;
				std::string description_;  
				int         len_;
				char        letter_;
		};
	template<class T,class O>
		inline ValuesHolder<T,O>
		values(const O& o,
				const char* desc="",
				int len=-1)
		{
			return ValuesHolder<T,O>(o,desc,len);
		}
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Interface of ValueParser
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	template <class T>
		class ValueParser
		{
			public:
				inline T operator()(const std::string& s) const
				{
					std::istringstream is(s);
					T t;
					is>>t;
					return t;
				}
		};
	// We need to specialize for string otherwise parsing of a value that
	// contains space (for example a string with space passed in quotes on the
	// command line) would parse only the first element of the value!!!
	template <>
		class ValueParser<std::string>
		{
			public:
				inline std::string operator()(const std::string& s) const
				{
					return s;
				}
		};  
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	// Interface of argstream
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	class argstream
	{
		public:
			inline argstream(int argc,char** argv);
			inline argstream(const char* c);
			template<class T>
				friend argstream& operator>>(argstream& s,const ValueHolder<T>& v);
			friend inline argstream& operator>>(argstream& s,const OptionHolder& v);
			template<class T,class O>
				friend argstream& operator>>(argstream& s,const ValuesHolder<T,O>& v);

			inline bool helpRequested() const;
			inline bool isOk() const;
			inline std::string errorLog() const;
            inline std::string usage(bool skipCommandLine = false,bool indent=true) const;
			inline bool defaultErrorHandling(bool ignoreUnused=false,bool skipCommandLine=false) const;
			static inline char uniqueLetter();
		protected:
			void parse(int argc,char** argv);
		private:
			typedef std::list<std::string>::iterator value_iterator;
			typedef std::pair<std::string,std::string> help_entry;
			std::string                          progName_;
			std::map<std::string,value_iterator> options_;
			std::list<std::string>               values_;
			bool                                 minusActive_;
			bool                                 isOk_;
			std::deque<help_entry>               argHelps_;
			std::string                          cmdLine_;
			std::deque<std::string>              errors_;
			bool                                 helpRequested_;
	};
	//************************************************************
	// Implementation of ValueHolder<T>
	//************************************************************
	template<class T>
		ValueHolder<T>::ValueHolder(char s,
				const char* l,
				T& v,
				const char* valueName,
				const char* desc,
				bool mandatory)
		:  shortName_(1,s),
		longName_(l),
		value_(&v),
		valueName_(valueName),
		initialValue_(v),
		description_(desc),
		mandatory_(mandatory)
	{
	}
	template<class T>
		ValueHolder<T>::ValueHolder(const char* l,
				T& v,
				const char* valueName,
				const char* desc,
				bool mandatory)
		:  longName_(l),
		value_(&v),
		valueName_(valueName),
		initialValue_(v),
		description_(desc),
		mandatory_(mandatory)
	{
	}
	template<class T>
		ValueHolder<T>::ValueHolder(char s,
				T& v,
	    const char* valueName,
				const char* desc,
				bool mandatory)
		:  shortName_(1,s),
		value_(&v),
		valueName_(valueName),
		initialValue_(v),
		description_(desc),
		mandatory_(mandatory)
	{
	}
	template<class T>
		std::string
		ValueHolder<T>::name() const
		{
			std::ostringstream  os;
			if (!shortName_.empty()) os<<'-'<<shortName_;
			if (!longName_.empty()) {      
				if (!shortName_.empty()) os<<'/';
				os<<"--"<<longName_;
			}
			return os.str();
		}
	template<class T>
		std::string
		ValueHolder<T>::description() const
		{
			std::ostringstream  os;  
			os<<description_;
			if (mandatory_)
			{
				os<<"(mandatory)";
			}
			else
			{
                std::ostringstream ss;
                ss << "(default="<<initialValue_<<")";

                if(ss.str().size() > 10)
                os<<ss.str();
			}
			return os.str();
		}
	//************************************************************
	// Implementation of OptionHolder
	//************************************************************
	inline OptionHolder::OptionHolder(char s,
			const char* l,
			bool& b,
			const char* desc)
		: shortName_(1,s),
		longName_(l),
		value_(&b),
		description_(desc)
	{
	}
	inline OptionHolder::OptionHolder(const char* l,
			bool& b,
			const char* desc)
		: longName_(l),
		value_(&b),
		description_(desc)
	{
	}
	inline OptionHolder::OptionHolder(char s,
			bool& b,
			const char* desc)
		: shortName_(1,s),
		value_(&b),
		description_(desc)
	{
	}
	inline OptionHolder::OptionHolder(char s,
			const char* l,
			const char* desc)
		: shortName_(1,s),
		longName_(l),
		value_(NULL),
		description_(desc)
	{
	}
	inline std::string
		OptionHolder::name() const
		{
			std::ostringstream  os;
			if (!shortName_.empty()) os<<'-'<<shortName_;
			if (!longName_.empty())
			{
				if (!shortName_.empty()) os<<'/';
				os<<"--"<<longName_;
			}
			return os.str();
		}
	inline std::string
		OptionHolder::description() const
		{
			return description_;
		}
	//************************************************************
	// Implementation of ValuesHolder<T,O>
	//************************************************************
	template<class T,class O>
		ValuesHolder<T,O>::ValuesHolder(const O& o,
				const char* desc,
				int len)
		: value_(o),
		description_(desc),
		len_(len)
	{
		letter_ = argstream::uniqueLetter();
	}
	template <class T,class O>
		std::string
		ValuesHolder<T,O>::name() const
		{
			std::ostringstream  os;
			os<<letter_<<"i";
			return os.str();
		}
	template <class T,class O>
		std::string
		ValuesHolder<T,O>::description() const
		{
			return description_;
		}
	//************************************************************
	// Implementation of argstream
	//************************************************************
	inline
		argstream::argstream(int argc,char** argv)
		: progName_(argv[0]),
		minusActive_(true),
		isOk_(true),
		helpRequested_(false)
	{
		parse(argc,argv);
	}
	inline
		argstream::argstream(const char* c)
		: progName_(""),
		minusActive_(true),
		isOk_(true)
	{
		std::string s(c);
		// Build argc, argv from s. We must add a dummy first element for
		// progName because parse() expects it!!
		std::deque<std::string> args;
		args.push_back("");
		std::istringstream is(s);
		while (is.good())
		{
			std::string t;
			is>>t;
			args.push_back(t);
		}
		char* pargs[args.size()];
		char** p = pargs;
		for (std::deque<std::string>::const_iterator
				iter = args.begin();
				iter != args.end();++iter)
		{
			*p++ = const_cast<char*>(iter->c_str());
		}
		parse(args.size(),pargs);
	}
	inline void
		argstream::parse(int argc,char** argv)
		{
			// Run thru all arguments.
			// * it has -- in front : it is a long name option, if remainder is empty,
			//                        it is an error
			// * it has - in front  : it is a sequence of short name options, if
			//                        remainder is empty, deactivates option (- will
			//                        now be considered a char).
			// * if any other char, or if option was deactivated
			//                      : it is a value. Values are split in parameters
			//                      (immediately follow an option) and pure values.
			// Each time a value is parsed, if the previously parsed argument was an
			// option, then the option is linked to the value in case of it is a
			// option with parameter.  The subtle point is that when several options
			// are given with short names (ex: -abc equivalent to -a -b -c), the last
			// parsed option is -c).
			// Since we use map for option, any successive call overides the previous
			// one: foo -a -b -a hello is equivalent to foo -b -a hello
			// For values it is not true since we might have several times the same
			// value. 
			value_iterator* lastOption = NULL;
			for (char** a = argv,**astop=a+argc;++a!=astop;)
			{
				std::string s(*a);
				if (minusActive_ && s[0] == '-')
				{
					if (s.size() > 1 && s[1] == '-')
					{
						if (s.size() == 2)
						{
							minusActive_ = false;
							continue;
						}
						lastOption = &(options_[s.substr(2)] = values_.end());
					}
					else 
					{
						if (s.size() > 1)
						{
							// Parse all chars, if it is a minus we have an error
							for (std::string::const_iterator cter = s.begin();
									++cter != s.end();)
							{
								if (*cter == '-')
								{
									isOk_ = false;
									std::ostringstream os;
									os<<"- in the middle of a switch "<<a;
									errors_.push_back(os.str());
									break;
								}
								lastOption = &(options_[std::string(1,*cter)] = values_.end());
							}
						}
						else
						{
							isOk_ = false;
							errors_.push_back("Invalid argument -");
							break;
						}
					}
				}
				else
				{
					values_.push_back(s);
					if (lastOption != NULL)
					{
						*lastOption = --values_.end();
					}
					lastOption = NULL;
				}
			}
#ifdef ARGSTREAM_DEBUG
			for (std::map<std::string,value_iterator>::const_iterator
					iter = options_.begin();iter != options_.end();++iter)
			{
				std::cout<<"DEBUG: option "<<iter->first;
				if (iter->second != values_.end())
				{
					std::cout<<" -> "<<*(iter->second);
				}
				std::cout<<std::endl;
			}
			for (std::list<std::string>::const_iterator
					iter = values_.begin();iter != values_.end();++iter)
			{
				std::cout<<"DEBUG: value  "<<*iter<<std::endl;
			}
#endif // ARGSTREAM_DEBUG
		}
	inline bool
		argstream::isOk() const
		{
			return isOk_;
		}
	inline bool
		argstream::helpRequested() const
		{
			return helpRequested_;
		}
	inline std::string
        argstream::usage(bool skipCommandLine,bool indent) const
		{
			std::ostringstream os;

			if(!skipCommandLine)
            {
			   os<<"usage: "<<progName_<<cmdLine_<<'\n';
               os<<std::endl;
            }
			unsigned int lmax = 0;
			for (std::deque<help_entry>::const_iterator
					iter = argHelps_.begin();iter != argHelps_.end();++iter)
			{
				if (lmax<iter->first.size()) lmax = iter->first.size();
			}  
			for (std::deque<help_entry>::const_iterator
					iter = argHelps_.begin();iter != argHelps_.end();++iter)
			{
                if(indent)
                os<<'\t';
                 os<<iter->first<<std::string(lmax-iter->first.size(),' ')
					<<" : "<<iter->second<<'\n';
			}
			return os.str();
		}
	inline std::string
		argstream::errorLog() const
		{
			std::string s;
			for(std::deque<std::string>::const_iterator iter = errors_.begin();
					iter != errors_.end();++iter)
			{
				s += *iter;
				s += '\n';
			}
			return s;
		}
	inline char
		argstream::uniqueLetter()
		{
			static unsigned int c = 'a';
			return c++;
		}
	template<class T>
		argstream&
		operator>>(argstream& s,const ValueHolder<T>& v)
		{
			// Search in the options if there is any such option defined either with a
			// short name or a long name. If both are found, only the last one is
			// used.
#ifdef ARGSTREAM_DEBUG    
			std::cout<<"DEBUG: searching "<<v.shortName_<<" "<<v.longName_<<std::endl;
#endif    
			s.argHelps_.push_back(argstream::help_entry(v.name(),v.description()));
			if (v.mandatory_)
			{
				if (!v.shortName_.empty())
				{
					s.cmdLine_ += " -";
					s.cmdLine_ += v.shortName_;
				}
				else
				{
					s.cmdLine_ += " --";
					s.cmdLine_ += v.longName_;
				}
				s.cmdLine_ += " " + v.valueName_;
			}
			else
			{
				if (!v.shortName_.empty())
				{
					s.cmdLine_ += " [-";
					s.cmdLine_ += v.shortName_;
				}
				else
				{
					s.cmdLine_ += " [--";
					s.cmdLine_ += v.longName_;
				}  
				s.cmdLine_ += " " + v.valueName_ +"]";

			}
			std::map<std::string,argstream::value_iterator>::iterator iter =
				s.options_.find(v.shortName_);
			if (iter == s.options_.end())
			{
				iter = s.options_.find(v.longName_);
			}
			if (iter != s.options_.end())
			{
				// If we find counterpart for value holder on command line, either it
				// has an associated value in which case we assign it, or it has not, in
				// which case we have an error.
				if (iter->second != s.values_.end())
				{
#ifdef ARGSTREAM_DEBUG
					std::cout<<"DEBUG: found value "<<*(iter->second)<<std::endl;
#endif	
					ValueParser<T> p;
					*(v.value_) = p(*(iter->second));
					// The option and its associated value are removed, the subtle thing
					// is that someother options might have this associated value too,
					// which we must invalidate.
					s.values_.erase(iter->second);      
					for (std::map<std::string,argstream::value_iterator>::iterator
							jter = s.options_.begin();jter != s.options_.end();++jter)
					{
						if (jter->second == iter->second)
						{
							jter->second = s.values_.end();
						}
					}
					s.options_.erase(iter);
				}
				else
				{
					s.isOk_ = false;
					std::ostringstream  os;
					os<<"No value following switch "<<iter->first
						<<" on command line";
					s.errors_.push_back(os.str());
				}
			}
			else
			{
				if (v.mandatory_)
				{
					s.isOk_ = false;
					std::ostringstream  os;
					os<<"Mandatory parameter ";
					if (!v.shortName_.empty()) os<<'-'<<v.shortName_;
					if (!v.longName_.empty())
					{
						if (!v.shortName_.empty()) os<<'/';
						os<<"--"<<v.longName_;
					}
					os<<" missing";
					s.errors_.push_back(os.str());
				}
			}
			return s;
		}
	inline argstream&
		operator>>(argstream& s,const OptionHolder& v)
		{
			// Search in the options if there is any such option defined either with a
			// short name or a long name. If both are found, only the last one is
			// used.
#ifdef ARGSTREAM_DEBUG    
			std::cout<<"DEBUG: searching "<<v.shortName_<<" "<<v.longName_<<std::endl;
#endif
			s.argHelps_.push_back(argstream::help_entry(v.name(),v.description()));
			{
				std::string c;
				if (!v.shortName_.empty())
				{
					c += " [-";
					c += v.shortName_;
				}
				else
				{
					c += " [--";
					c += v.longName_;
				}      
				c += "]";
				s.cmdLine_ = c+s.cmdLine_;
			}
			std::map<std::string,argstream::value_iterator>::iterator iter =
				s.options_.find(v.shortName_);
			if (iter == s.options_.end())
			{
				iter = s.options_.find(v.longName_);
			}
			if (iter != s.options_.end())
			{
				// If we find counterpart for value holder on command line then the
				// option is true and if an associated value was found, it is ignored
				if (v.value_ != NULL)
				{
					*(v.value_) = true;
				}
				else
				{
					s.helpRequested_ = true;
				}
				// The option only is removed
				s.options_.erase(iter);
			}
			else
			{
				if (v.value_ != NULL)
				{
					*(v.value_) = false;
				}
				else
				{
					s.helpRequested_ = false;
				}
			}
			return s;
		}
	template<class T,class O>
		argstream&
		operator>>(argstream& s,const ValuesHolder<T,O>& v)
		{
			s.argHelps_.push_back(argstream::help_entry(v.name(),v.description()));
			{
				std::ostringstream os;
				os<<' '<<v.letter_<<'1';
				switch (v.len_)
				{
					case -1:
						os<<"...";
						break;
					case 1:
						break;
					default:
						os<<"..."<<v.letter_<<v.len_;
						break;
				}
				s.cmdLine_ += os.str();
			}
			std::list<std::string>::iterator first = s.values_.begin();
			// We add to the iterator as much values as we can, limited to the length
			// specified (if different of -1)
			int n = v.len_ != -1?v.len_:s.values_.size();
			while (first != s.values_.end() && n-->0)
			{
				// Read the value from the string *first
				ValueParser<T> p;
				*(v.value_++) = p(*first );
				s.argHelps_.push_back(argstream::help_entry(v.name(),v.description()));
				// The value we just removed was maybe "remembered" by an option so we
				// remove it now.
				for (std::map<std::string,argstream::value_iterator>::iterator
						jter = s.options_.begin();jter != s.options_.end();++jter)
				{
					if (jter->second == first)
					{
						jter->second = s.values_.end();
					}
				}
				++first;
			}
			// Check if we have enough values
			if (n != 0)
			{
				s.isOk_ = false;
				std::ostringstream  os;
				os<<"Expecting "<<v.len_<<" values";
				s.errors_.push_back(os.str());
			}
			// Erase the values parsed
			s.values_.erase(s.values_.begin(),first);
			return s;
		}
	inline bool
		argstream::defaultErrorHandling(bool ignoreUnused,bool skipCommandLine) const
		{
			if (helpRequested_)
			{
				std::cout<<usage(skipCommandLine);
				exit(0);
			}
			if (!isOk_)
			{
				std::cerr<<errorLog();
				exit(1);
			}
			if (!ignoreUnused &&
					(!values_.empty() || !options_.empty()))
			{
				std::cerr<<"Unused arguments"<<std::endl;
				exit(1);
			}
			return true ;
		}
};
#endif // ARGSTREAM_H

