/* args.hh - argument parsing made easy
 * Copyright 2003-2010 Bas Wijnen <wijnen@debian.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef SHEVEK_ARGS_HH
#define SHEVEK_ARGS_HH

#include <getopt.h>
#include <string>
#include <vector>
#include <list>
#include <sigc++/sigc++.h>
#include <glibmm/ustring.h>
#include "debug.hh"

// Allow using this class without using automake.
#ifndef PACKAGE_NAME
#define PACKAGE_NAME "[name not defined]"
#endif
#ifndef PACKAGE_TARNAME
#define PACKAGE_TARNAME PACKAGE_NAME
#endif
#ifndef PACKAGE_BUGREPORT
#define PACKAGE_BUGREPORT "[bug report e-mail not defined]"
#endif
#ifndef PACKAGE_VERSION
#define PACKAGE_VERSION "[version not defined]"
#endif
#ifndef COPYRIGHT_YEARS
#define COPYRIGHT_YEARS "[no time of copyright defined]"
#endif
#ifndef COPYRIGHT_EMAIL
#define COPYRIGHT_EMAIL ""
#endif
#ifndef COPYRIGHT_AUTHOR
#define COPYRIGHT_AUTHOR "[no author defined]"
#endif

namespace shevek
{
  /// Commandline and configuration file parsing helper.
  /** Args is a commandline parsing helper.  It allows giving the possible
   * short and long options in a simple list and provides --help and --version
   * output to the user of the program.
   *
   * Usage:
   * create an array of shevek::args::option, containing the desired options.
   * create an instance of args and pass it argc and argv on the constructor.
   * It will call all the callbacks of the options from the constructor.
   * size () and operator[] can be used to access the non-option arguments.
   */
  class args
  {
  public:
    class option;
    /// Parse the commandline.  Only the default arguments (--help, -h and
    /// --version) are understood.
    args (int &argc, char **&argv, int min_args, int max_args, Glib::ustring const &description,
	  /* The parameters below should always have their default values,
	   * they are just here to get the #define'd values into the library.
	   * The COPYRIGHT_* values should be defined in configure.ac
	   */
	  Glib::ustring const &copyright_years = COPYRIGHT_YEARS,
	  Glib::ustring const &copyright_email = (COPYRIGHT_EMAIL[0] == '\0' ? PACKAGE_BUGREPORT : COPYRIGHT_EMAIL),
	  Glib::ustring const &programmer = COPYRIGHT_AUTHOR,
	  Glib::ustring const &email = PACKAGE_BUGREPORT,
	  char const *programname = PACKAGE_NAME,
	  char const *packagename = PACKAGE_TARNAME,
	  char const *version = PACKAGE_VERSION);
    /// Parse the commandline providing a list of possible options.
    template <unsigned size_>
    args (int &argc, char **&argv, option (&o)[size_], int min_args,
	  int max_args, Glib::ustring const &description);
    /// The number of non-option arguments.
    unsigned size () const;
    /// Get the non-option arguments.
    std::string const &operator[] (unsigned idx) const;
    /// Iterate over the non-option arguments.
    std::vector <std::string>::const_iterator begin () const;
    /// Iterate over the non-option arguments.
    std::vector <std::string>::const_iterator end () const;
  private:
    std::vector <std::string> m_args;
    void l_setup (int &argc, char **&argv, option *o, unsigned num_options,
		  int min_args, int max_args,
		  Glib::ustring const &description,
		  struct ::option *longopts,
	  	  Glib::ustring const &copyright_years = COPYRIGHT_YEARS,
	  	  Glib::ustring const &copyright_email = (COPYRIGHT_EMAIL[0] == '\0' ? PACKAGE_BUGREPORT : COPYRIGHT_EMAIL),
	  	  Glib::ustring const &programmer = COPYRIGHT_AUTHOR,
	  	  Glib::ustring const &email = PACKAGE_BUGREPORT,
	  	  char const *programname = PACKAGE_NAME,
	  	  char const *packagename = PACKAGE_TARNAME,
	  	  char const *version = PACKAGE_VERSION);
  };

  /// Define an option which can be given to the program.
  class args::option
  {
  public:
    /// Callback for options without an argument.
    typedef sigc::slot1 <void, bool> callback0;
    /// Callback for options with an argument.
    typedef sigc::slot2 <void, bool, Glib::ustring const &> callback1;
    /// Option has no argument and calls a function.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, callback0 handle, bool *used = NULL);
    /// Option has mandatory argument and calls a function.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, callback1 handle, Glib::ustring default_val = Glib::ustring (), bool *used = NULL);
    /// Option has optional argument and calls respective function.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, callback0 handle0, callback1 handle1, bool *used = NULL);
    /// Set the value of a boolean variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool &var, bool value, bool *used = NULL);
    /// Set the value of a string variable (utf-8).
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, Glib::ustring &var, bool *used = NULL);
    /// Set the value of a string variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, std::string &var, bool *used = NULL);
    /// Set the value of an integer variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, unsigned long &var, bool *used = NULL);
    /// Set the value of an integer variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, long &var, bool *used = NULL);
    /// Set the value of an integer variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, unsigned &var, bool *used = NULL);
    /// Set the value of an integer variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, int &var, bool *used = NULL);
    /// Set the value of an integer variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, unsigned short &var, bool *used = NULL);
    /// Set the value of an integer variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, short &var, bool *used = NULL);
    /// Set the value of a floating point variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, float &var, bool *used = NULL);
    /// Set the value of a floating point variable.
    option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, double &var, bool *used = NULL);
    /// Fill a list of variables.  An item is appended to the list for each
    /// time the option is specified.
    template <typename _T> option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, std::list <_T> &list);
  private:
    friend class args;
    enum opt_t {NEED_ARG, NO_ARG, OPT_ARG};
    void setup (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, bool have_default, opt_t have_arg, callback0 handle0, callback1 handle1, bool *used);
    void call (bool is_double, char const *optarg);
    char m_shortopt;
    Glib::ustring m_longopt;
    Glib::ustring m_help;
    bool m_have_default;
    Glib::ustring m_default;
    opt_t m_have_arg;
    callback0 m_handle0;
    callback1 m_handle1;
    bool *m_used;
    bool m_is_used;
    static void l_set (bool is_double, Glib::ustring const &arg, Glib::ustring *var);
    static void l_setstd (bool is_double, Glib::ustring const &arg, std::string *var);
    static void l_setint (bool is_double, Glib::ustring const &arg, int *var);
    static void l_setuint (bool is_double, Glib::ustring const &arg, unsigned *var);
    static void l_setlint (bool is_double, Glib::ustring const &arg, long *var);
    static void l_setulint (bool is_double, Glib::ustring const &arg, unsigned long *var);
    static void l_setsint (bool is_double, Glib::ustring const &arg, short *var);
    static void l_setusint (bool is_double, Glib::ustring const &arg, unsigned short *var);
    static void l_setbool (bool is_double, bool val, bool *var);
    static void l_setfloat (bool is_double, Glib::ustring const &arg, float *var);
    static void l_setdfloat (bool is_double, Glib::ustring const &arg, double *var);
    template <typename _T>
    static void l_setlist (bool is_double, Glib::ustring const &arg, std::list <_T> *list);
  };

  template <unsigned size_> args::args (int &argc, char **&argv,
					option (&o)[size_],
					int min_args, int max_args,
					Glib::ustring const &description)
  {
    startfunc;
    struct ::option longopts[size_ + 4];
    l_setup (argc, argv, o, size_, min_args, max_args, description, longopts);
  }


  template <typename _T>
  args::option::option (char shortopt, Glib::ustring const &longopt, Glib::ustring const &help_line, std::list <_T> &list)
  {
    startfunc;
    setup (shortopt, longopt, help_line, false, NEED_ARG, callback0 (),
	   sigc::bind (sigc::ptr_fun (&l_setlist <_T>), &list), NULL);
  }

  template <typename _T>
  void args::option::l_setlist (bool is_double, Glib::ustring const &arg, std::list <_T> *list)
  {
    startfunc;
    (void *)&is_double;
    list->push_back (_T () );
    option tmp ('x', "x", "x", false, list->back () );
    tmp.m_handle1 (false, arg);
  }
}

#endif
