/** @file

    $Id$

    $Date$
    $Author$

    @copy &copy; Marc W&auml;ckerlin
    @license LGPL, see file <a href="license.html">COPYING</a>

    $Log$
    Revision 1.7  2005/11/29 12:39:42  marc
    make it compilable with gcc 4.0.2 and newer doxygen

    Revision 1.6  2005/02/08 12:31:36  marc
    new static methods to simplify access to options

    Revision 1.5  2004/11/25 18:26:04  marc
    Constness corrected

    Revision 1.4  2004/10/07 09:23:39  marc
    bugs in documentation

    Revision 1.3  2004/08/31 16:23:29  marc
    no include of stacktrace.hxx

    Revision 1.2  2004/08/28 16:21:25  marc
    mrw-c++-0.92 (mrw)
    - new file: version.cxx
    - new file header for all sources
    - work around warning in mrw::auto<T>
    - possibility to compile without log4cxx
    - work around bugs in demangle.h and libiberty.h
    - corrections in documentation
    - added simple tracing mechanism
    - more warnings
    - small corrections in Auto<>::Free and a new test for it
    - possibility to compile without stack trace

*/
#include <mrw/smartpointer.hxx>
#include <stdlib.h> // exit
#include <string>
#include <map>
#include <vector>
#include <set>
#include <list>
#include <exception>
#include <stdexcept>
#include <sstream>
#include <iostream>

namespace mrw {

  /** @defgroup arguments C++ Evaluation of Command Line Arguments

      @brief These classes do simple and easy command line argment evaluation
      in C++.

      Features:
       - every argument has a long and a short option
       - all arguments are optional and provide a default value
       - the order of options is not important
       - every option can take any (fixed) number of additional parameter of
         type
          - string
          - integer
          - boolean (@c "yes", @c "on", @c "true" evaluates to @c true)
       - short options can be combined, instead of
         @c -a @c -b @c -c @c 15 you can simply write @c -abc @c 15
       - automated help display (support for option @c -h)

      @c mrw::Args is the main user interface class that represents
      all command line options with their arguments. It is implemented
      as singleton, so the same instance can be accessed from
      everywhere in the code. It mst be setup just in the beginning of
      the @c main() function.

      The other important class for the end user is @c mrw::Opt, one
      possible option with additional parameter. The end user needs @c
      mrw::Opt to setup all allowed command line options in the
      beginning, bevore evaluation of the user given command line is
      done (before @c argc and @c argv is shifted into @c mrw::Args.

      The third class a user should know is @c mrw::Param. It
      represents the arguments to one option. Every instance of @c
      mrw::Opt owns one instance of @c mrw::Param that is either empty
      or list of (mandatory) arguments of type @c std::string, @c int
      or @c bool.

      The classes are normally used this way:

      @code
  // this program may be called e.g. with the following arguments:
  //   ./a.out --coordinates 13 1 -vo out.txt -n MyName
  int main(int argv, char const*const*const argv) {
    try {
      mrw::Args::instance()
        // setup the possible options
        <<mrw::Opt('h', "--help", "Show this help text")
        <<mrw::Opt('v', "--verbose", "print more information")
        <<mrw::Opt('q', "--quiet", "be quiet")
        <<mrw::Opt('n', "--name", mrw::Param()<<"MRW", "name of the user")
        <<mrw::Opt('o', "--output-file", mrw::Param()<<"", "file to load")
        <<mrw::Opt('c', "--coordinates", mrw::Param()<<0<<0, "X, Y coordinate")
        // set a description text for help
        <<"This is a testprogram for argument evaluation in C++"
        // define the help option
        <<'h'
        // shift in the command line arguments
        <<argc<<argv;
         ...
        // example usage of simple option
        if (mrw::Args::instance().find('v')) // be verbose here
        if (mrw::Args::have('v')) // the same, simpler: be verbose here
         ...
        // example usage of option with one parameter
        ifstream file(mrw::Args::toString('o').c_str());
         ...
        // example usage of option with two parameter
        int x = mrw::Args::instance().find('c')[0]->toInt(); // first integer
        int y = mrw::Args::toInt('c', 1); // second; alternative, simpler access
         ...
        return 0
      }
    } catch (mrw::exception& x) {
      // trace error, print help or mention option -h
    }
  }
      @endcode
   */
  //@{

  /** @brief List of additional (mandatory) parameter to one command
      line argument.
      @pre \#include<mrw/arg.hxx>

      A new mandatory parameter is added to the list of parameter, by
      shifting the default value into the instance of @c
      mrw::Param. E.g. add a string, that defaults to @c "noname", an
      integer, that defaults to @c 4, another integer that defaults to
      @c 2 and a boolean that defaults to @c "true":

      @code
      // if you need the instance as variable:
      mrw::Param p();
      p<<"noname"<<4<<2<<true;
      // or in an expression:
      mrw::Opt o('e', "--example", mrw::Param()<<"noname"<<4<<2<<true, "");
      @endcode

      To access a value at a given position, simply use @c
      operator[]. Then use @c mrw::Value::toString, @c
      mrw::Value::toInt or @c mrw::Value::toBool to get the value of
      that parameter. Of course yo must know the correct type of a
      parameter at a given position, but since you are the programmer
      you know it, or you can get it by running your program with the
      help option, mostly @c -h. To retrieve the parameters setup in
      the example above (connected to option @c -e or @c --example),
      either the default value, or the value overwritten by the user,
      simply type:

      @code
      mrw::Args& args = mrw::Args::instance();
      std::string theString = args[0]->toString();
      int firstInteger = args[1]->toInt();
      int secondInteger = args[2]->toInt();
      bool theBoolean = args[3]->toBool();
      @endcode

      <h3>Setup Command Line from Different Program Parts</h3>

      If your software is large and splitted into different parts (or
      sub projects or modules, ...), all with their own parameter, you
      can use the following trick: Statical variables are initialized
      before the @c main() function is called.

      In part Abc write in a code file (not in a header file):

      @code
      class AbcArguments {
      public:
        AbcArguments() {
          mrw::Args::instance()
            <<mrw::Opt('n', "--name", mrw::Param()<<"MRW", "name of the user")
            <<mrw::Opt('o', "--output-file", mrw::Param()<<"", "file to load")
            <<mrw::Opt('c', "--coordinates", mrw::Param()<<0<<0,
                       "X, Y coordinate")
            <<"Description text for part Abc, will be added to the\n"
              "overall documentation";
        }
      };
      static AbcArgument abcArgumentInitializer;
      @endcode

      Do the same for all other parts Then the @c main() function reduces to:

      @code
      int main(int argc, char const*const*const argv) {
        // set the help and evaluate the user given arguments
        mrw::Args::instance()
          <<mrw::Opt('h', "--help", "Show this help text")
          <<'h'<<argc<<argv;
         ...
      }
      @endcode
  */
  class Param {
    
  public:

    /** @brief Abstract base class to represent one single parameter value.
        @pre \#include<mrw/arg.hxx>
     */
    class Value {
      
    public:
      
      virtual ~Value() {}
      
      /** @brief If the instance is a @c std::string, return that
          string, otherwise throw an exception.
          @throw mrw::bad_cast if the instance is not a string
          @return the string, if the instance is a string
      */
      virtual const std::string& toString() const throw(std::exception);
      
      /** @brief If the instance is an @c int, return that integer,
          otherwise throw an exception.
          @throw mrw::bad_cast if the instance is not a integer
          @return the integer, if the instance is a integer
      */
      virtual int toInt() const throw(std::exception);
      
      /** @brief If the instance is an @c bool, return that boolean,
          otherwise throw an exception.
          @note the following typings are converted to @c true:
            - true
            - yes
            - on
          Everything else is converted to @c false.
          @throw mrw::bad_cast if the instance is not a boolean
          @return the boolean, if the instance is a boolean
      */
      virtual bool toBool() const throw(std::exception);

      /// @brief returns a printable representation of the value
      virtual std::string printable() const throw(std::bad_exception) = 0;

      /// @brief returns a printable typename of the value
      virtual const std::string& typestr() const throw(std::bad_exception)=0;
      
    protected:
      /// Allow assign for Args, make it a friend.
      friend class Args;
      /// Only the class itself and friends (Args) are allowed to assign.
      virtual void operator=(const std::string&) throw(std::exception) = 0;
    };

  private:
    
    class StringValue: public Value {
    public:
      virtual ~StringValue() {}
      StringValue(const std::string& s) throw(std::bad_exception): _s(s) {
      }
      virtual const std::string& toString() const throw(std::exception) {
        return _s;
      }
      virtual const std::string& typestr() const throw(std::bad_exception) {
        static std::string name("string");
        return name;
      }
      virtual std::string printable() const throw(std::bad_exception) {
        return _s;
      }
    protected:
      virtual void operator=(const std::string& s) throw(std::exception) {
        _s = s;
      }
    private:
      std::string _s;
    };
    
    class IntValue: public Value {
    public:
      virtual ~IntValue() {}
      IntValue(int i) throw(std::bad_exception): _i(i) {
      }
      virtual int toInt() const throw(std::exception) {
        return _i;
      }
      virtual const std::string& typestr() const throw(std::bad_exception) {
        static std::string name("integer");
        return name;
      }
      virtual std::string printable() const throw(std::bad_exception) {
        return ((std::stringstream&)(std::stringstream()<<_i)).str();
      }
    protected:
      virtual void operator=(const std::string& s) throw(std::exception);
    private:
      int _i;
    };
    
    class BoolValue: public Value {
    public:
      virtual ~BoolValue() {}
      BoolValue(bool b) throw(std::bad_exception): _b(b) {
      }
      virtual bool toBool() const throw(std::exception) {
        return _b;
      }
      virtual const std::string& typestr() const throw(std::bad_exception) {
        static std::string name("boolean (\"yes\" or \"no\")");
        return name;
      }
      virtual std::string printable() const throw(std::bad_exception) {
        return _b?"yes":"no";
      }
    protected:
      virtual void operator=(const std::string& s) throw(std::exception) {
        _b = s=="true" || s=="yes" || s=="on";
      }
    private:
      bool _b;
    };
    
    typedef std::vector< mrw::SmartPointer<Value> > Params;
    Params _params;
    
  public:

    /// @brief returns the number of (mandatory) parameter
      Params::size_type size() const throw(std::bad_exception) {
      return _params.size();
    }

    /// @brief add one more mandatory string parameter
    Param& operator<<(char const*const s) throw(std::bad_exception) {
      _params.push_back(new StringValue(s));
      return *this;
    }

    /// @brief add one more mandatory string parameter
    Param& operator<<(const std::string& s) throw(std::bad_exception) {
      _params.push_back(new StringValue(s));
      return *this;
    }

    /// @brief add one more mandatory integer parameter
    Param& operator<<(int i) throw(std::bad_exception) {
      _params.push_back(new IntValue(i));
      return *this;
    }

    // @brief add one more mandatory boolean parameter
    Param& operator<<(bool b) throw(std::bad_exception) {
      _params.push_back(new BoolValue(b));
      return *this;
    }
    
    /** @brief get parameter number @c i
        @throw mrw::out_of_range if @c i is too big */
    const mrw::SmartPointer<Value>& operator[](unsigned int i) const
      throw(std::exception);

  private:

    /// Allow set for Args, make it a friend.
    friend class Args; // allow set
    /// Get a parameter with acces right for setting.
    /// This is allowed for the class itself and friends (Args) only.
    mrw::SmartPointer<Value>& setable(unsigned int i) throw(std::exception);    
  };

  /** @brief this class represents one command line option
      @pre \#include<mrw/arg.hxx>

      The library user needs this class when setting up the list of
      supported command line ooptions: Simply shift one instance of @c
      mrw::Opt per supported command line option into @c
      mrw::Args::instance(), e.g.:

      @code
      mrw::Args::instance()
        <<mrw::Opt('h', "--help", "Show this help text");
      @endcode
   */
  class Opt {
    
  public:
    
    /** @brief create an @c mrw::Opt with additional parameter
        @param shortname short name of the option
        @param longname long name of the option, must start with "--"
        @param param the additional parameter
        @param helptext the help string for this option
    */
    Opt(const char shortname, const std::string& longname,
        const Param& param, const std::string& helptext)
      throw(std::bad_exception):
      _set(false), _shortname(shortname), _longname(longname),
      _param(param), _help(helptext) {
    }
    
    /** @brief create a simple @c mrw::Opt

        This option is either set or not set, there are no additional
        parameter.
        
        @param shortname short name of the option
        @param longname long name of the option, must start with "--"
        @param helptext the help string for this option
    */
    Opt(const char shortname, const std::string& longname,
        const std::string& helptext) throw(std::bad_exception):
      _set(false), _shortname(shortname), _longname(longname),
      _help(helptext) {
    }

    /** @brief get the help text for this option */
    const std::string& help() const throw(std::bad_exception) {
      return _help;
    }

    /** @brief find out, whether this option was set by the user

        Example: Check whether the user has set the @c -v option for
        verbose output, there are two ways for doing it:

        @code
        if (mrw::Args::instance().find('v')) // -v is set
        if (mrw::Args::have('v')) // -v is set
        @endcode

        @return
          - @c true if the user has started the program with this option
          - @c false if the user has not set this option
    */
    operator bool() const throw(std::bad_exception) {return _set;}

    /** @brief get one of the additional parameter

        If this option has additional parameter, get the @c i-th of them.

        @throw mrw::out_of_range if @c i is too big
        @param i number of the additional parameter to get (starting with @c 0)
        @return a smart pointer to the value (default or given by the user)
    */
    const mrw::SmartPointer<Param::Value>& operator[](unsigned int i) const
      throw(std::exception) {
      return _param[i];
    }
    
  private:

    /// Allow set values, make Args a friend.
    friend class Args;
    /// Set @c _set to true, available only for friends (Args).
    void set() const throw(std::bad_exception) {
      _set = true;
    }
    Param& args() const throw(std::bad_exception) {
      return _param;
    }
    mutable bool _set;
    char _shortname;
    std::string _longname;
    mutable Param _param;
    std::string _help;
  };

  /** @brief handle command line arguments
      @pre \#include<mrw/arg.hxx>

      This class handles command line arguments. It is a
      singleton. Get the one and only instance of this class with @c
      mrw::Args::instance(). It is setup by shifting values into this
      class. The order is important, be sure that you shift in @c argc
      and @c argv as last parameters.

      Example setup:

      @code
      mrw::Args::instance()
        <<mrw::Opt('h', "--help", "Show this help text")
        <<mrw::Opt('v', "--verbose", "print more information")
        <<"This is a testprogram for argument evaluation in C++"
        <<'h'<<argc<<argv;
      @endcode
  */
  class Args {
    
  public:
    
    typedef std::list<std::string> OtherArgs;

    /// @brief get the one and only instance
    static Args& instance() throw(std::bad_exception) { // singleton
      static Args _instance;
      return _instance;
    }

    /** @brief check a simple option

        This is a shortcut. Instead of the expression:
        @code
        if (mrw::Args::instance().find('x')) ...
        @endcode
        you can simply write the expression:
        @code
        if (mrw::Args::have('x')) ...
        @endcode
        It is exactly the same.

        @param c the short name of the parameter
        
        @throw mrw::out_of_range if the parameter is not available
               (this would be a coding error)
    */
    static bool have(char c) throw(std::exception) {
      return mrw::Args::instance().find(c);
    }

    /** @brief get a simple string parameter

        This is a shortcut. Instead of the expression:
        @code
        mrw::Args::instance().find('x')[0].toString()
        @endcode
        you can simply write the expression:
        @code
        mrw::Args::toString('x')
        @endcode
        It is exactly the same.

        @param c the short name of the parameter
        
        @throw mrw::bad_cast if the parameter is not a string
        @throw mrw::out_of_range if the parameter is empty or not available
    */
    static const std::string& toString(char c) throw(std::exception) {
      return mrw::Args::instance().find(c)[0]->toString();
    }

    /** @brief get a simple integer parameter

        This is a shortcut. Instead of the expression:
        @code
        mrw::Args::instance().find('x')[0].toInt()
        @endcode
        you can simply write the expression:
        @code
        mrw::Args::toInt('x')
        @endcode
        It is exactly the same.

        @param c the short name of the parameter
        
        @throw mrw::bad_cast if the parameter is not an int
        @throw mrw::out_of_range if the parameter is empty or not available
    */
    static int toInt(char c) throw(std::exception) {
      return mrw::Args::instance().find(c)[0]->toInt();
    }

    /** @brief get a simple boolean parameter

        This is a shortcut. Instead of the expression:
        @code
        mrw::Args::instance().find('x')[0].toBool()
        @endcode
        you can simply write the expression:
        @code
        mrw::Args::toBool('x')
        @endcode
        It is exactly the same.

        @param c the short name of the parameter
        
        @throw mrw::bad_cast if the parameter is not an bool
        @throw mrw::out_of_range if the parameter is empty or not available
    */
    static bool toBool(char c) throw(std::exception) {
      return mrw::Args::instance().find(c)[0]->toBool();
    }

    /** @brief get the n-th string parameter

        This is a shortcut. Instead of the expression:
        @code
        mrw::Args::instance().find('x')[n].toString()
        @endcode
        you can simply write the expression:
        @code
        mrw::Args::toString('x', n)
        @endcode
        It is exactly the same.

        @param c the short name of the parameter
        @param n the number of the parameter (starting at 0)
        
        @throw mrw::bad_cast if the parameter is not a string
        @throw mrw::out_of_range if the parameter is not available
    */
      static const std::string& toString(char c, int n) throw(std::exception) {
      return mrw::Args::instance().find(c)[n]->toString();
    }

    /** @brief get the n-th integer parameter

        This is a shortcut. Instead of the expression:
        @code
        mrw::Args::instance().find('x')[n].toInt()
        @endcode
        you can simply write the expression:
        @code
        mrw::Args::toInt('x', n)
        @endcode
        It is exactly the same.

        @param c the short name of the parameter
        @param n the number of the parameter (starting at 0)
        
        @throw mrw::bad_cast if the parameter is not an int
        @throw mrw::out_of_range if the parameter is not available
    */
      static int toInt(char c, int n) throw(std::exception) {
      return mrw::Args::instance().find(c)[n]->toInt();
    }

    /** @brief get the n-th boolean parameter

        This is a shortcut. Instead of the expression:
        @code
        mrw::Args::instance().find('x')[n].toBool()
        @endcode
        you can simply write the expression:
        @code
        mrw::Args::toBool('x', n)
        @endcode
        It is exactly the same.

        @param c the short name of the parameter
        @param n the number of the parameter (starting at 0)
        
        @throw mrw::bad_cast if the parameter is not an bool
        @throw mrw::out_of_range if the parameter not available
    */
      static bool toBool(char c, int n) throw(std::exception) {
      return mrw::Args::instance().find(c)[n]->toBool();
    }

    /** @brief setup an acceptable option

        Setup an acceptable user option.

        Example:

        @code
          mrw::Args::instance()
            <<mrw::Opt('v', "--verbose", "print more information");
        @endcode

        @throw mrw::invalid_argument if opt is not setup correctly
    */
    Args& operator<<(const mrw::Opt& opt) throw(std::exception);

    /** @brief setup the number of arguments

        Setup the number of arguments.
        This must be done before @c argv is shifted in.
        
        Example:

        @code
        int main(int argv, char const*const*const argv) {
          mrw::Args::instance()<<argc<<argv;
           ...
        }
        @endcode
    */
    Args& operator<<(int argc) throw(std::bad_exception) {
      _argc = argc;
      return *this;
    }
    
    /** @brief setup the C array of command line arguments

        Setup the C array of command line arguments. This must be the
        very last thing shifted in.
        
        Example:

        @code
        int main(int argv, char const*const*const argv) {
          mrw::Args::instance()<<argc<<argv;
           ...
        }
        @endcode
    */
    Args& operator<<(char const*const*const argv) throw(std::exception);

    /** @brief add a description text

        Add a description text. This description text is shown in the
        @c DESCRIPTION section of the help display. If the description
        text is shifted in more then once, the different sections are
        appended with new line and an empty line between.

        Example:
        
        @code
          mrw::Args::instance()<<"this is a description for --help";
        @endcode
    */
    Args& operator<<(const std::string& description) throw(std::exception) {
      if (_description=="")
        _description = description;
      else
        _description += "\n\n"+description;
      return *this;
    }

    /** @brief set the help option

        Define which option prints the help text. There is no code
        needed for printing the help text: if the help option has been
        shifted in, help is printed automatically at user request,
        then the program is terminated. Only specify the short option
        name, the long option name is known.
        
        Example:
        
        @code
          mrw::Args::instance()<<'h';
        @endcode
    */
    Args& operator<<(char helpopt) throw(std::exception) {
      _help = helpopt;
      return *this;
    }

    /** @brief get an option, given the short option name
        @throw mrw::out_of_range if the option does not exist
    */
    const Opt& find(char c) const throw(std::exception);
    
    /** @brief get an option, given the long option name
        @throw mrw::out_of_range if the option does not exist
    */
    const Opt& find(const std::string& s) const throw(std::exception);
    
    /** @brief get all non interpreted options

        All user options that don't fit the defined and interpreted
        options. The meaning for this is, that a user may append,
        e.g. a list of file names.
    */
    const OtherArgs& otherArgs() {
      return _otherargs;
    }

    /** @brief get the file name of the executable, that's @c argv[0] */
    const std::string& filename() throw(std::bad_exception) {
      return _filename;
    }

    /** @brief print the help text, then exit */
    void help() {
      std::cout<<"USAGE: "<<std::endl
               <<"  "<<_filename<<" [ OPTIONS ]"<<std::endl
               <<"OPTIONS:"<<std::endl;
      for (Options::iterator it(_options.begin()); it!=_options.end(); ++it) {
        std::cout<<"  -"<<it->_shortname<<" | "<<it->_longname;
        for (Param::Params::size_type i(0); i<it->_param.size(); ++i)
          std::cout<<" <"<<(*it)[(unsigned int)i]->typestr()<<">";
        if (it->_param.size()>0) std::cout<<" (default: ";
        for (Param::Params::size_type i(0); i<it->_param.size()-1; ++i)
          std::cout<<(*it)[(unsigned int)i]->printable()<<" ";
        if (it->_param.size()>0)
          std::cout<<(*it)[(unsigned int)(it->_param.size()-1)]->printable()
                   <<")";
        std::cout<<std::endl<<"    "<<it->help()<<std::endl;
      }
      if (_description.size()>0)
        std::cout<<"DESCRIPTION:"<<std::endl
                 <<_description<<std::endl;
      exit(0);
    }

  private:
    Args(): _argc(-1), _help(0) {} // singleton
    Args& operator=(const Args&); // singleton, not implemented
    Args& parse(int argc, char const*const*const argv) throw(std::exception);
    typedef std::list<Opt> Options;
    typedef std::map<char, Options::iterator> ShortOpts;
    typedef std::map<std::string, Options::iterator> LongOpts;
    std::string _filename;
    Options _options;
    ShortOpts _shortopts;
    LongOpts _longopts;
    OtherArgs _otherargs;
    int _argc;
    char _help;
    std::string _description;
  };

  //@}
}
