/* -*- mode: C++; indent-tabs-mode: nil; -*-
 *
 * This file is a part of LEMON, a generic C++ optimization library.
 *
 * Copyright (C) 2003-2010
 * Egervary Jeno Kombinatorikus Optimalizalasi Kutatocsoport
 * (Egervary Research Group on Combinatorial Optimization, EGRES).
 *
 * Permission to use, modify and distribute this software is granted
 * provided that this copyright notice appears in all copies. For
 * precise terms see the accompanying LICENSE file.
 *
 * This software is provided "AS IS" with no warranty of any kind,
 * express or implied, and with no claim as to its suitability for any
 * purpose.
 *
 */

#ifndef LEMON_ARG_PARSER_H
#define LEMON_ARG_PARSER_H

#include <vector>
#include <map>
#include <list>
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <lemon/core.h>
#include <lemon/assert.h>

///\ingroup misc
///\file
///\brief A tool to parse command line arguments.

namespace lemon {

  ///Exception used by ArgParser

  ///Exception used by ArgParser.
  ///
  class ArgParserException : public Exception {
  public:
    /// Reasons for failure

    /// Reasons for failure.
    ///
    enum Reason {
      HELP,         ///< <tt>--help</tt> option was given.
      UNKNOWN_OPT,  ///< Unknown option was given.
      INVALID_OPT   ///< Invalid combination of options.
    };

  private:
    Reason _reason;

  public:
    ///Constructor
    ArgParserException(Reason r) throw() : _reason(r) {}
    ///Virtual destructor
    virtual ~ArgParserException() throw() {}
    ///A short description of the exception
    virtual const char* what() const throw() {
      switch(_reason)
        {
        case HELP:
          return "lemon::ArgParseException: ask for help";
          break;
        case UNKNOWN_OPT:
          return "lemon::ArgParseException: unknown option";
          break;
        case INVALID_OPT:
          return "lemon::ArgParseException: invalid combination of options";
          break;
        }
      return "";
    }
    ///Return the reason for the failure
    Reason reason() const {return _reason; }
  };


  ///Command line arguments parser

  ///\ingroup misc
  ///Command line arguments parser.
  ///
  ///For a complete example see the \ref arg_parser_demo.cc demo file.
  class ArgParser {

    static void _showHelp(void *p);
  protected:

    int _argc;
    const char * const *_argv;

    enum OptType { UNKNOWN=0, BOOL=1, STRING=2, DOUBLE=3, INTEGER=4, FUNC=5 };

    class ParData {
    public:
      union {
        bool *bool_p;
        int *int_p;
        double *double_p;
        std::string *string_p;
        struct {
          void (*p)(void *);
          void *data;
        } func_p;

      };
      std::string help;
      bool mandatory;
      OptType type;
      bool set;
      bool ingroup;
      bool has_syn;
      bool syn;
      bool self_delete;
      ParData() : mandatory(false), type(UNKNOWN), set(false), ingroup(false),
                  has_syn(false), syn(false), self_delete(false) {}
    };

    typedef std::map<std::string,ParData> Opts;
    Opts _opts;

    class GroupData
    {
    public:
      typedef std::list<std::string> Opts;
      Opts opts;
      bool only_one;
      bool mandatory;
      GroupData() :only_one(false), mandatory(false) {}
    };

    typedef std::map<std::string,GroupData> Groups;
    Groups _groups;

    struct OtherArg
    {
      std::string name;
      std::string help;
      OtherArg(std::string n, std::string h) :name(n), help(h) {}

    };

    std::vector<OtherArg> _others_help;
    std::vector<std::string> _file_args;
    std::string _command_name;


  private:
    //Bind a function to an option.

    //\param name The name of the option. The leading '-' must be omitted.
    //\param help A help string.
    //\retval func The function to be called when the option is given. It
    //  must be of type "void f(void *)"
    //\param data Data to be passed to \c func
    ArgParser &funcOption(const std::string &name,
                    const std::string &help,
                    void (*func)(void *),void *data);

    bool _exit_on_problems;

    void _terminate(ArgParserException::Reason reason) const;

  public:

    ///Constructor
    ArgParser(int argc, const char * const *argv);

    ~ArgParser();

    ///\name Options
    ///

    ///@{

    ///Add a new integer type option

    ///Add a new integer type option.
    ///\param name The name of the option. The leading '-' must be omitted.
    ///\param help A help string.
    ///\param value A default value for the option.
    ///\param obl Indicate if the option is mandatory.
    ArgParser &intOption(const std::string &name,
                    const std::string &help,
                    int value=0, bool obl=false);

    ///Add a new floating point type option

    ///Add a new floating point type option.
    ///\param name The name of the option. The leading '-' must be omitted.
    ///\param help A help string.
    ///\param value A default value for the option.
    ///\param obl Indicate if the option is mandatory.
    ArgParser &doubleOption(const std::string &name,
                      const std::string &help,
                      double value=0, bool obl=false);

    ///Add a new bool type option

    ///Add a new bool type option.
    ///\param name The name of the option. The leading '-' must be omitted.
    ///\param help A help string.
    ///\param value A default value for the option.
    ///\param obl Indicate if the option is mandatory.
    ///\note A mandatory bool obtion is of very little use.
    ArgParser &boolOption(const std::string &name,
                      const std::string &help,
                      bool value=false, bool obl=false);

    ///Add a new string type option

    ///Add a new string type option.
    ///\param name The name of the option. The leading '-' must be omitted.
    ///\param help A help string.
    ///\param value A default value for the option.
    ///\param obl Indicate if the option is mandatory.
    ArgParser &stringOption(const std::string &name,
                      const std::string &help,
                      std::string value="", bool obl=false);

    ///Give help string for non-parsed arguments.

    ///With this function you can give help string for non-parsed arguments.
    ///The parameter \c name will be printed in the short usage line, while
    ///\c help gives a more detailed description.
    ArgParser &other(const std::string &name,
                     const std::string &help="");

    ///@}

    ///\name Options with External Storage
    ///Using this functions, the value of the option will be directly written
    ///into a variable once the option appears in the command line.

    ///@{

    ///Add a new integer type option with a storage reference

    ///Add a new integer type option with a storage reference.
    ///\param name The name of the option. The leading '-' must be omitted.
    ///\param help A help string.
    ///\param obl Indicate if the option is mandatory.
    ///\retval ref The value of the argument will be written to this variable.
    ArgParser &refOption(const std::string &name,
                    const std::string &help,
                    int &ref, bool obl=false);

    ///Add a new floating type option with a storage reference

    ///Add a new floating type option with a storage reference.
    ///\param name The name of the option. The leading '-' must be omitted.
    ///\param help A help string.
    ///\param obl Indicate if the option is mandatory.
    ///\retval ref The value of the argument will be written to this variable.
    ArgParser &refOption(const std::string &name,
                      const std::string &help,
                      double &ref, bool obl=false);

    ///Add a new bool type option with a storage reference

    ///Add a new bool type option with a storage reference.
    ///\param name The name of the option. The leading '-' must be omitted.
    ///\param help A help string.
    ///\param obl Indicate if the option is mandatory.
    ///\retval ref The value of the argument will be written to this variable.
    ///\note A mandatory bool obtion is of very little use.
    ArgParser &refOption(const std::string &name,
                      const std::string &help,
                      bool &ref, bool obl=false);

    ///Add a new string type option with a storage reference

    ///Add a new string type option with a storage reference.
    ///\param name The name of the option. The leading '-' must be omitted.
    ///\param help A help string.
    ///\param obl Indicate if the option is mandatory.
    ///\retval ref The value of the argument will be written to this variable.
    ArgParser &refOption(const std::string &name,
                      const std::string &help,
                      std::string &ref, bool obl=false);

    ///@}

    ///\name Option Groups and Synonyms
    ///

    ///@{

    ///Bundle some options into a group

    /// You can group some option by calling this function repeatedly for each
    /// option to be grouped with the same groupname.
    ///\param group The group name.
    ///\param opt The option name.
    ArgParser &optionGroup(const std::string &group,
                           const std::string &opt);

    ///Make the members of a group exclusive

    ///If you call this function for a group, than at most one of them can be
    ///given at the same time.
    ArgParser &onlyOneGroup(const std::string &group);

    ///Make a group mandatory

    ///Using this function, at least one of the members of \c group
    ///must be given.
    ArgParser &mandatoryGroup(const std::string &group);

    ///Create synonym to an option

    ///With this function you can create a synonym \c syn of the
    ///option \c opt.
    ArgParser &synonym(const std::string &syn,
                           const std::string &opt);

    ///@}

  private:
    void show(std::ostream &os,Opts::const_iterator i) const;
    void show(std::ostream &os,Groups::const_iterator i) const;
    void showHelp(Opts::const_iterator i) const;
    void showHelp(std::vector<OtherArg>::const_iterator i) const;

    void unknownOpt(std::string arg) const;

    void requiresValue(std::string arg, OptType t) const;
    void checkMandatories() const;

    void shortHelp() const;
    void showHelp() const;
  public:

    ///Start the parsing process
    ArgParser &parse();

    /// Synonym for parse()
    ArgParser &run()
    {
      return parse();
    }

    ///Give back the command name (the 0th argument)
    const std::string &commandName() const { return _command_name; }

    ///Check if an opion has been given to the command.
    bool given(std::string op) const
    {
      Opts::const_iterator i = _opts.find(op);
      return i!=_opts.end()?i->second.set:false;
    }


    ///Magic type for operator[]

    ///This is the type of the return value of ArgParser::operator[]().
    ///It automatically converts to \c int, \c double, \c bool or
    ///\c std::string if the type of the option matches, which is checked
    ///with an \ref LEMON_ASSERT "assertion" (i.e. it performs runtime
    ///type checking).
    class RefType
    {
      const ArgParser &_parser;
      std::string _name;
    public:
      ///\e
      RefType(const ArgParser &p,const std::string &n) :_parser(p),_name(n) {}
      ///\e
      operator bool()
      {
        Opts::const_iterator i = _parser._opts.find(_name);
        LEMON_ASSERT(i!=_parser._opts.end(),
                     std::string()+"Unkown option: '"+_name+"'");
        LEMON_ASSERT(i->second.type==ArgParser::BOOL,
                     std::string()+"'"+_name+"' is a bool option");
        return *(i->second.bool_p);
      }
      ///\e
      operator std::string()
      {
        Opts::const_iterator i = _parser._opts.find(_name);
        LEMON_ASSERT(i!=_parser._opts.end(),
                     std::string()+"Unkown option: '"+_name+"'");
        LEMON_ASSERT(i->second.type==ArgParser::STRING,
                     std::string()+"'"+_name+"' is a string option");
        return *(i->second.string_p);
      }
      ///\e
      operator double()
      {
        Opts::const_iterator i = _parser._opts.find(_name);
        LEMON_ASSERT(i!=_parser._opts.end(),
                     std::string()+"Unkown option: '"+_name+"'");
        LEMON_ASSERT(i->second.type==ArgParser::DOUBLE ||
                     i->second.type==ArgParser::INTEGER,
                     std::string()+"'"+_name+"' is a floating point option");
        return i->second.type==ArgParser::DOUBLE ?
          *(i->second.double_p) : *(i->second.int_p);
      }
      ///\e
      operator int()
      {
        Opts::const_iterator i = _parser._opts.find(_name);
        LEMON_ASSERT(i!=_parser._opts.end(),
                     std::string()+"Unkown option: '"+_name+"'");
        LEMON_ASSERT(i->second.type==ArgParser::INTEGER,
                     std::string()+"'"+_name+"' is an integer option");
        return *(i->second.int_p);
      }

    };

    ///Give back the value of an option

    ///Give back the value of an option.
    ///\sa RefType
    RefType operator[](const std::string &n) const
    {
      return RefType(*this, n);
    }

    ///Give back the non-option type arguments.

    ///Give back a reference to a vector consisting of the program arguments
    ///not starting with a '-' character.
    const std::vector<std::string> &files() const { return _file_args; }

    ///Throw instead of exit in case of problems
    void throwOnProblems()
    {
      _exit_on_problems=false;
    }
  };
}

#endif // LEMON_ARG_PARSER_H
