/**
 * Copyright (C) 1999-2010  Free Software Foundation, Inc.
 *
 * This file is part of GNU gengetopt
 *
 * GNU gengetopt 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, or (at your option)
 * any later version.
 *
 * GNU gengetopt 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 gengetopt; see the file COPYING. If not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include <set>
#include <algorithm> // for pair

#include <fstream>
#include <sstream>

extern "C"
{
#include "argsdef.h"
#include "global_opts.h"
};

#include "ggo_options.h"

#include "gm.h"

#include "groups.h"
#include "skels/option_arg.h"
#include "skels/required_option.h"
#include "skels/dependant_option.h"
#include "skels/generic_option.h"
#include "skels/group_option.h"
#include "skels/group_counter.h"
#include "skels/handle_help.h"
#include "skels/handle_version.h"
#include "skels/print_help_string.h"
#include "skels/multiple_opt_list.h"
#include "skels/multiple_fill_array.h"
#include "skels/free_string.h"
#include "skels/free_multiple.h"
#include "skels/reset_group.h"
#include "skels/exit_failure.h"
#include "skels/update_given.h"
#include "skels/given_field.h"
#include "skels/clear_given.h"
#include "skels/clear_arg.h"
#include "skels/free_list.h"
#include "skels/file_save.h"
#include "skels/file_save_multiple.h"
#include "skels/init_args_info.h"
#include "skels/custom_getopt_gen.h"
#include "skels/check_modes.h"
#include "skels/enum_decl.h"
#include "gm_utils.h"
#include "fileutils.h"

#ifndef FIX_UNUSED
#define FIX_UNUSED(X) (void) (X)
#endif // FIX_UNUSED

#define MAX_STARTING_COLUMN 32

#define EXE_NAME "argv[0]"

#define PARSER_NAME_PREFIX (c_source_gen_class::parser_name + "_")
#define OPTION_VALUES_NAME(n) (PARSER_NAME_PREFIX + n + "_values")

using namespace std;

extern char * gengetopt_package;
extern char * gengetopt_version;
extern char * gengetopt_purpose;
extern char * gengetopt_versiontext;
extern char * gengetopt_description;
extern char * gengetopt_usage;

extern groups_collection_t gengetopt_groups;
extern modes_collection_t gengetopt_modes;

// a map where for each mode we store the corresponding given field names
// and the options
typedef std::pair<string, string> OptionValueElem;
typedef std::list<OptionValueElem> ModeOptions;
typedef std::map<string, ModeOptions> ModeOptionMap;

static ModeOptionMap modeOptionMap;

static const ModeOptionMap &getModeOptionMap() {
    if (modeOptionMap.size() == 0) {
        // it's the first time, so we build it
        struct gengetopt_option * opt;
        foropt {
            if (opt->mode_value) {
                modeOptionMap[opt->mode_value].push_back
                (std::make_pair("args_info->" + string(opt->var_arg) + "_given",
                        string("\"--") + opt->long_opt + "\""));
            }
        }
    }

    return modeOptionMap;
}

// a map associating to a mode the list of gengetopt_options
typedef std::map<string, gengetopt_option_list> ModeOptMap;

static ModeOptMap modeOptMap;

static const ModeOptMap &getModeOptMap() {
    if (modeOptMap.size() == 0) {
        // it's the first time, so we build it
        struct gengetopt_option * opt;
        foropt {
            if (opt->mode_value) {
                modeOptMap[opt->mode_value].push_back(opt);
            }
        }
    }

    return modeOptMap;
}

static void _generate_option_arg(ostream &stream,
                                 unsigned int indentation,
                                 struct gengetopt_option * opt);

static void
generate_help_desc_print(ostream &stream,
                         unsigned int desc_column,
                         const char *descript, const char *defval,
                         const string &values,
                         const string &show_required_string);

CmdlineParserCreator::CmdlineParserCreator (char *function_name,
                                            char *struct_name,
                                            char *unnamed_options_,
                                            char *filename_,
                                            char *header_ext, char *c_ext,
                                            bool long_help_,
                                            bool no_handle_help_,
                                            bool no_help_,
                                            bool no_handle_version_,
                                            bool no_version_,
                                            bool no_handle_error_,
                                            bool strict_hidden_,
                                            bool conf_parser_,
                                            bool string_parser_,
                                            bool gen_version,
                                            bool gen_getopt,
                                            bool no_options_,
                                            const string &comment_,
                                            const string &outdir,
                                            const string &header_outdir,
                                            const string &src_outdir,
                                            const string &show_required) :
  filename (filename_),
  args_info_name (struct_name),
  output_dir (outdir),
  header_output_dir (header_outdir),
  src_output_dir (src_outdir),
  comment (comment_),
  unnamed_options (unnamed_options_),
  show_required_string (show_required),
  long_help (long_help_), no_handle_help (no_handle_help_),
  no_help (no_help_),
  no_handle_version (no_handle_version_),
  no_version (no_version_),
  no_handle_error (no_handle_error_),
  strict_hidden (strict_hidden_),
  conf_parser (conf_parser_), string_parser (string_parser_),
  gen_gengetopt_version (gen_version),
  tab_indentation (0)
{
  parser_function_name = canonize_names (function_name);
  c_filename = create_filename (filename, c_ext);
  header_filename = create_filename (filename, header_ext);

  // header_gen_class
  const string stripped_header_file_name = strip_path (filename);
  set_header_file_name (stripped_header_file_name);
  header_gen_class::set_header_file_ext (header_ext);
  c_source_gen_class::set_header_file_ext (header_ext);
  if (gen_gengetopt_version) {
    header_gen_class::set_generator_version("version " VERSION);
  }
  const string my_ifndefname =
    to_upper (strip_path (stripped_header_file_name));
  set_ifndefname (canonize_names (my_ifndefname.c_str ()));
  header_gen_class::set_parser_name (parser_function_name);
  const string my_package_var_name =
    to_upper (parser_function_name) + "_PACKAGE";
  const string my_version_var_name =
    to_upper (parser_function_name) + "_VERSION";
  header_gen_class::set_package_var_name (my_package_var_name);
  c_source_gen_class::set_package_var_name (my_package_var_name);
  header_gen_class::set_version_var_name (my_version_var_name);
  c_source_gen_class::set_version_var_name (my_version_var_name);
  header_gen_class::set_args_info (args_info_name);
  c_source_gen_class::set_args_info (args_info_name);
  const string uppersand = "\"";

  // if no_options then we don't need to generate update_arg,
  // but if we need to handle help or version we still need to generate it
  set_no_options (no_options_ && !no_handle_help && !no_handle_version);

  if (gengetopt_package)
    set_package_var_val
      (uppersand + gengetopt_package + uppersand);
  else
    set_package_var_val ("PACKAGE");

  if (gengetopt_version)
    set_version_var_val
      (uppersand + gengetopt_version + uppersand);
  else
    set_version_var_val ("VERSION");

  header_gen_class::set_generate_config_parser (conf_parser);

  header_gen_class::set_generate_string_parser (string_parser);
  c_source_gen_class::set_generate_string_parser (string_parser);

  // c_source_gen_class
  set_command_line (comment);
  if (gen_gengetopt_version)
    c_source_gen_class::set_generator_version
      ("version " VERSION);
  c_source_gen_class::set_parser_name (parser_function_name);
  set_source_name (filename);

  ostringstream exit_failure_str;
  exit_failure_gen_class exit_gen;
  exit_gen.set_parser_name (c_source_gen_class::parser_name);
  exit_gen.set_handle_error (! no_handle_error);
  exit_gen.generate_exit_failure (exit_failure_str);
  set_final_exit (exit_failure_str.str ());

  set_conf_parser (conf_parser);
  set_cmd_list (conf_parser || string_parser);
  set_include_getopt (gen_getopt);

  struct gengetopt_option * opt;
  gen_strdup = (unnamed_options != 0 || conf_parser || string_parser);

  if (! gen_strdup)
    {
      foropt
        if (opt->type != ARG_FLAG || opt->type != ARG_NO) {
          gen_strdup = true;
          break;
        }
    }

  set_do_generate_strdup(gen_strdup);
  set_check_possible_values(has_values());
  set_multiple_token_functions(has_multiple_options_with_type());
  set_multiple_options_with_default(has_multiple_options_with_default());
  set_multiple_options(has_multiple_options());
  set_multiple_options_string(has_multiple_options_string());
  set_multiple_options_all_string(has_multiple_options_all_string());
  set_has_typed_options(has_options_with_type());
  set_has_modes(has_options_with_mode());
  set_handle_unnamed(unnamed_options);
  set_check_required_options(has_required() || has_dependencies() || has_multiple_options());
  set_purpose(generate_purpose());
  set_description(generate_description());
  set_versiontext(generate_versiontext());
  set_no_package((gengetopt_package == 0));
  c_source_gen_class::set_has_hidden(!strict_hidden && has_hidden_options());
  header_gen_class::set_has_hidden(c_source_gen_class::has_hidden);
  c_source_gen_class::set_has_details(has_options_with_details(strict_hidden));
  header_gen_class::set_has_details(c_source_gen_class::has_details);

  set_has_arg_types();
}

void CmdlineParserCreator::set_has_arg_types() {
    struct gengetopt_option * opt;

    set_has_arg_flag(false);
    set_has_arg_string(false);
    set_has_arg_int(false);
    set_has_arg_short(false);
    set_has_arg_long(false);
    set_has_arg_float(false);
    set_has_arg_double(false);
    set_has_arg_longdouble(false);
    set_has_arg_longlong(false);

    foropt
    {
        switch (opt->type) {
        case ARG_NO:
            break;
        case ARG_FLAG:
            set_has_arg_flag(true);
            break;
        case ARG_STRING:
            set_has_arg_string(true);
            break;
        case ARG_INT:
            set_has_arg_int(true);
            break;
        case ARG_SHORT:
            set_has_arg_short(true);
            break;
        case ARG_LONG:
            set_has_arg_long(true);
            break;
        case ARG_FLOAT:
            set_has_arg_float(true);
            break;
        case ARG_DOUBLE:
            set_has_arg_double(true);
            break;
        case ARG_LONGDOUBLE:
            set_has_arg_longdouble(true);
            break;
        case ARG_LONGLONG:
            set_has_arg_longlong(true);
            break;
        case ARG_ENUM:
            set_has_arg_enum(true);
            break;
        default:
            fprintf (stderr, "gengetopt: bug found in %s:%d!!\n",
                    __FILE__, __LINE__);
            abort ();
        }
    }

}

void
CmdlineParserCreator::generateBreak(ostream &stream, unsigned int indentation)
{
  string indent_str (indentation, ' ');

  stream << endl;
  stream << indent_str;
  stream << "break;";
}

int
CmdlineParserCreator::generate ()
{
  int head_result;

  head_result = generate_header_file ();
  if (head_result)
    return head_result;

  return generate_source ();
}

int
CmdlineParserCreator::generate_header_file ()
{
  if (! gengetopt_options.size())
    {
      fprintf (stderr, "gengetopt: none option given\n");
      return 1;
    }

  /* ****************************************************** */
  /* HEADER FILE******************************************* */
  /* ****************************************************** */

    string header_file = header_filename;
    if (header_output_dir.size())
        header_file = header_output_dir + "/" + header_file;
    else if (output_dir.size())
        header_file = output_dir + "/" + header_file;

    ofstream *output_file = open_fstream
            (header_file.c_str());
    generate_header (*output_file);
    output_file->close ();
    delete output_file;

    return 0;
}

/**
 * generate the enum value from a given option
 * @param name the (canonized) name of the option
 * @param val the value of the option
 * @return the enum value string
 */
static const string from_value_to_enum(const string &name, const string &val) {
    return name + "_arg_" + canonize_enum(val);
}

void
CmdlineParserCreator::generate_enum_types(ostream &stream,
                                          unsigned int indentation)
{
  struct gengetopt_option * opt;
  FIX_UNUSED (indentation);

  if (has_arg_enum)
      stream << endl;

  foropt {
    // if type is enum then it should also have values (checked during parsing)
    // but it's better to check it
    if (opt->type == ARG_ENUM) {
        if (! (opt->acceptedvalues)) {
            fprintf (stderr, "gengetopt: bug found in %s:%d!!\n",
                            __FILE__, __LINE__);
            abort ();
        }
        ostringstream enum_values;
        enum_decl_gen_class enum_gen;
        enum_gen.set_var_arg(opt->var_arg);
        for (AcceptedValues::const_iterator iter = opt->acceptedvalues->begin();
            iter != opt->acceptedvalues->end(); ++iter) {
            enum_values << ", ";
            // the first enum element is set to 0
            enum_values << from_value_to_enum(opt->var_arg, *iter);
            if (iter == opt->acceptedvalues->begin())
                enum_values << " = 0";

        }
        enum_gen.set_enum_values(enum_values.str());
        enum_gen.generate_enum_decl(stream);
    }
  }
}

void
CmdlineParserCreator::generate_option_arg(ostream &stream,
                                          unsigned int indentation)
{
  struct gengetopt_option * opt;

  foropt {
    _generate_option_arg (stream, indentation, opt);
  }
}

void
_generate_option_arg(ostream &stream,
                     unsigned int indentation,
                     struct gengetopt_option *opt)
{
  option_arg_gen_class option_arg_gen;

  string type = "";
  if (opt->type)
      type = arg_types[opt->type];
  string origtype = "char *";

  if (opt->multiple) {
    type += "*";
    origtype += "*";
    option_arg_gen.set_multiple(true);
  } else {
    option_arg_gen.set_multiple(false);
  }

  option_arg_gen.set_type(type);
  option_arg_gen.set_origtype(origtype);
  option_arg_gen.set_flag_arg((opt->type == ARG_FLAG));
  option_arg_gen.set_desc(opt->desc);
  option_arg_gen.set_name(opt->var_arg);
  option_arg_gen.set_has_arg(opt->type != ARG_NO);
  option_arg_gen.set_has_enum(opt->type == ARG_ENUM);

  if (opt->default_given)
    {
      option_arg_gen.set_has_default(true);
      option_arg_gen.set_default_value(opt->default_string);
    }

  if (opt->type == ARG_FLAG)
    {
      option_arg_gen.set_default_on(opt->flagstat);
    }

  if (opt->type == ARG_LONGLONG)
    {
      // the fallback type in case longlong is not supported by the compiler
      string longtype = arg_types[ARG_LONG];
      if (opt->multiple)
          longtype += "*";

      option_arg_gen.set_long_long_arg(true);
      option_arg_gen.set_longtype(longtype);
    }

  option_arg_gen.generate_option_arg(stream, indentation);
}

void
CmdlineParserCreator::generate_option_given(ostream &stream,
                                            unsigned int indentation)
{
  struct gengetopt_option * opt;
  string indent_str (indentation, ' ');
  bool first = true;
  given_field_gen_class given_gen;

  foropt
    {
      switch (opt->type) {
      case ARG_NO:
      case ARG_FLAG:
      case ARG_STRING:
      case ARG_INT:
      case ARG_SHORT:
      case ARG_LONG:
      case ARG_FLOAT:
      case ARG_DOUBLE:
      case ARG_LONGDOUBLE:
      case ARG_LONGLONG:
      case ARG_ENUM:
          break;
      default:
        fprintf (stderr, "gengetopt: bug found in %s:%d!!\n",
                 __FILE__, __LINE__);
        abort ();
      }
      if (! first)
        stream << indent_str;
      else
        first = false;

      given_gen.set_arg_name (opt->var_arg);
      given_gen.set_long_opt (opt->long_opt);
      given_gen.set_group (opt->multiple && opt->group_value);
      given_gen.generate_given_field (stream);
    }

  if (unnamed_options)
    {
      stream << endl;
      stream << indent_str;
      stream << "char **inputs ; /**< @brief unnamed options (options without names) */\n" ;
      stream << indent_str;
      stream << "unsigned inputs_num ; /**< @brief unnamed options number */" ;
    }
}

void
CmdlineParserCreator::generate_option_values_decl(ostream &stream,
                                                  unsigned int indentation)
{
  struct gengetopt_option * opt;
  bool first = true;
  FIX_UNUSED (indentation);

  foropt
    {
      if (opt->acceptedvalues) {
        if (first) {
          first = false;
        }

        stream << "extern const char *" << OPTION_VALUES_NAME(opt->var_arg) <<
          "[];  /**< @brief Possible values for " << opt->long_opt << ". */\n";
      }
    }

  if (! first)
    stream << "\n";
}

void
CmdlineParserCreator::generate_option_values(ostream &stream,
                                             unsigned int indentation)
{
  struct gengetopt_option * opt;
  bool first = true;
  FIX_UNUSED (indentation);

  foropt
    {
      if (opt->acceptedvalues) {
        if (first) {
          first = false;
        }

        stream << "const char *" << OPTION_VALUES_NAME(opt->var_arg) <<
          "[] = {" << opt->acceptedvalues->toString(false) <<
          ", 0}; /*< Possible values for " << opt->long_opt << ". */\n";
      }
    }

  if (! first)
    stream << "\n";
}

static void generate_option_usage_string(gengetopt_option * opt, ostream &usage) {
    const char   *type_str;

    usage << " ";

    if (!opt->required)
        usage << "[";

    switch (opt->type) {
    case ARG_NO:
    case ARG_FLAG:
        if (opt->short_opt)
            usage << "-" << opt->short_opt << "|";
        usage << "--" << opt->long_opt;
        break;
    case ARG_INT:
    case ARG_SHORT:
    case ARG_LONG:
    case ARG_FLOAT:
    case ARG_DOUBLE:
    case ARG_LONGDOUBLE:
    case ARG_LONGLONG:
    case ARG_STRING:
    case ARG_ENUM:
        if (opt->type_str)
            type_str = opt->type_str;
        else
            type_str = arg_names[opt->type];

        if (opt->short_opt)
            usage << "-" << opt->short_opt << type_str << "|";
        usage << "--" << opt->long_opt << "=" << type_str;

        break;
    default: fprintf (stderr, "gengetopt: bug found in %s:%d!!\n",
            __FILE__, __LINE__);
    abort ();
    }

    if (!opt->required)
        usage << "]";
}

const string
CmdlineParserCreator::generate_usage_string(bool use_config_package)
{
  FIX_UNUSED (use_config_package);
  // if specified by the programmer, the usage string has the precedence
  if (gengetopt_usage) {
      string wrapped_usage;
      wrap_cstr(wrapped_usage, 0, 0, gengetopt_usage);
      return wrapped_usage;
  }

  struct gengetopt_option * opt;
  ostringstream usage;

  // otherwise the config.h package constant will be used
  if (gengetopt_package)
    usage << gengetopt_package;

  if ( long_help ) {
      // we first generate usage strings of required options
      // handle mode options separately
      foropt
          if (opt->required && !opt->hidden && !opt->mode_value) {
              generate_option_usage_string(opt, usage);
          }

      foropt
          if (!opt->required && !opt->hidden && !opt->mode_value) {
              generate_option_usage_string(opt, usage);
          }
  } else { /* if not long help we generate it as GNU standards */
      usage << " [OPTION]...";
  }

  string wrapped;

  if ( unnamed_options )
      usage << " [" << unnamed_options << "]...";

  wrap_cstr ( wrapped, strlen("Usage: "), 2, usage.str() );

  // now deal with modes
  if (has_modes && long_help) {
      const ModeOptMap &modeoptmap = getModeOptMap();

      for (ModeOptMap::const_iterator map_it = modeoptmap.begin(); map_it != modeoptmap.end(); ++map_it) {
          string mode_line; // a mode alternative in the usage string
          gengetopt_option_list::const_iterator opt_it;
          usage.str(""); // reset the usage string buffer

		  // otherwise the config.h package constant will be used
		  if (gengetopt_package)
			  usage << gengetopt_package;

          for (opt_it = map_it->second.begin(); opt_it != map_it->second.end(); ++opt_it) {
              if (((*opt_it)->required) && !((*opt_it)->hidden)) {
                  generate_option_usage_string(*opt_it, usage);
              }
          }

          for (opt_it = map_it->second.begin(); opt_it != map_it->second.end(); ++opt_it) {
              if (!((*opt_it)->required) && !((*opt_it)->hidden)) {
                  generate_option_usage_string(*opt_it, usage);
              }
          }

          wrap_cstr ( mode_line, strlen("  or : "), 2, usage.str() );
          wrapped += "\\n  or : ";
          wrapped += mode_line;
      }
  }

  return wrapped;
}

static void
generate_help_desc_print(ostream &stream,
                         unsigned int desc_column,
                         const char *descript, const char *defval,
                         const string &values,
                         const string &show_required_string)
{
  string desc;
  string desc_with_default = descript;

  if (defval || values.size()) {
      desc_with_default += "  (";

      if (values.size()) {
        desc_with_default += "possible values=";
        desc_with_default += values;
        if (defval)
          desc_with_default += " ";
      }

      if (defval) {
        desc_with_default += "default=";
        desc_with_default += defval;
      }

      desc_with_default += ")";
  }

  if (show_required_string != "")
    desc_with_default += " " + show_required_string;

  wrap_cstr ( desc, desc_column, 2, desc_with_default );

  stream << desc;
}


void
CmdlineParserCreator::generate_help_option_print_from_lists(ostream &stream,
        unsigned int indentation, OptionHelpList *full_option_list,
        OptionHelpList *option_list, const std::string &target_array,
        const std::string &source_array) {
    print_help_string_gen_class print_gen;

    // the index into the help arrays
    int i = 0, full_i = 0;
    // num of help strings
    int help_num = 0;

    print_gen.set_target(target_array);
    print_gen.set_from(source_array);
    print_gen.set_shared(true);
    print_gen.set_last(false);

    OptionHelpList::const_iterator it = option_list->begin();
    OptionHelpList::const_iterator it2 = full_option_list->begin();
    // the second list is surely longer so we scan that one
    for (; it != option_list->end() && it2 != full_option_list->end(); ++it2)
    {
        if (*it == *it2) {
            // when the two strings are the same it means that's a non-hidden
            // option, so we share it with the full help array
            ostringstream converted_int;
            converted_int << i;

            // the index into the help array
            print_gen.set_index(converted_int.str());

            converted_int.str("");
            converted_int << full_i;

            // the index into the full help array
            print_gen.set_full_index(converted_int.str());
            print_gen.generate_print_help_string(stream, indentation);

            ++help_num;
            ++i;
            ++it;
        }
        ++full_i;
    }

    ostringstream converted_int;
    converted_int << help_num;

    // the final 0
    print_gen.set_last(true);
    print_gen.set_index(converted_int.str());
    print_gen.generate_print_help_string(stream, indentation);

    // we increment it to store the final 0
    converted_int.str("");
    converted_int << ++help_num;

    set_help_string_num(converted_int.str());

}

void
CmdlineParserCreator::generate_help_option_print(ostream &stream,
                                                 unsigned int indentation)
{
    OptionHelpList *option_list = generate_help_option_list();

    if (!c_source_gen_class::has_hidden && !c_source_gen_class::has_details) {
        print_help_string_gen_class print_gen;
        print_gen.set_shared(false);

        // simple help generation
        for (OptionHelpList::const_iterator it = option_list->begin();
        it != option_list->end(); ++it)
        {
            print_gen.set_helpstring(*it);
            print_gen.generate_print_help_string(stream, indentation);
        }
    } else {
        // in order to avoid generating the same help string twice, and thus
        // to save memory, in case of hidden options (or details), we try to share most
        // of the strings with the full help array
        OptionHelpList *full_option_list = generate_help_option_list(true, true);

        generate_help_option_print_from_lists
        (stream, indentation, full_option_list, option_list,
                c_source_gen_class::args_info + "_help",
                (c_source_gen_class::has_details ?
                        c_source_gen_class::args_info + "_detailed_help" :
                            c_source_gen_class::args_info + "_full_help"));

        delete full_option_list;
    }

    delete option_list;
}

void
CmdlineParserCreator::generate_full_help_option_print(ostream &stream,
        unsigned int indentation)
{
    // generate also hidden options
    OptionHelpList *option_list = generate_help_option_list(true);

    if (!c_source_gen_class::has_details) {
        print_help_string_gen_class print_gen;
        print_gen.set_shared(false);

        for (OptionHelpList::const_iterator it = option_list->begin();
        it != option_list->end(); ++it)
        {
            print_gen.set_helpstring(*it);
            print_gen.generate_print_help_string(stream, indentation);
        }
    } else {
        // in order to avoid generating the same help string twice, and thus
        // to save memory, in case of options with details, we try to share most
        // of the strings with the full help array
        OptionHelpList *full_option_list = generate_help_option_list(true, true);

        generate_help_option_print_from_lists
        (stream, indentation, full_option_list, option_list,
                c_source_gen_class::args_info + "_full_help",
                c_source_gen_class::args_info + "_detailed_help");

        delete full_option_list;
    }

    delete option_list;
}

void
CmdlineParserCreator::generate_detailed_help_option_print(ostream &stream,
        unsigned int indentation)
{
    // generate also hidden options and details
    OptionHelpList *option_list = generate_help_option_list(true, true);

    print_help_string_gen_class print_gen;
    print_gen.set_shared(false);

    for (OptionHelpList::const_iterator it = option_list->begin();
         it != option_list->end(); ++it)
    {
        print_gen.set_helpstring(*it);
        print_gen.generate_print_help_string(stream, indentation);
    }

    delete option_list;
}

void
CmdlineParserCreator::generate_init_args_info(ostream &stream, unsigned int indentation)
{
    struct gengetopt_option * opt;
    init_args_info_gen_class init_args_info_gen;
    int i = 0;
    ostringstream index;

    string help_string = c_source_gen_class::args_info;

    if (c_source_gen_class::has_details) {
        help_string += "_detailed_help";
    } else if (c_source_gen_class::has_hidden) {
        help_string += "_full_help";
    } else {
        help_string += "_help";
    }
    init_args_info_gen.set_help_strings(help_string);

    const char *current_section = 0, *current_group = 0, *current_mode = 0;

    // we have to skip section description references (that appear in the help vector)
    foropt {
        index.str("");

        if (opt->section) {
          if (!current_section || (strcmp(current_section, opt->section) != 0)) {
            // a different section reference, skip it
            current_section = opt->section;
            ++i;

            if (opt->section_desc) {
              // section description takes another line, thus we have to skip this too
              ++i;
            }
          }
        }

        // skip group desc
        if (opt->group_value) {
            if (!current_group || strcmp(current_group, opt->group_value) != 0) {
                current_group = opt->group_value;
                ++i;
            }
        }

        // skip mode desc
        if (opt->mode_value) {
            if (!current_mode || strcmp(current_mode, opt->mode_value) != 0) {
                current_mode = opt->mode_value;
                ++i;
            }
        }

        // also skip the text before
        if (opt->text_before)
            ++i;

        index << i++;

        init_args_info_gen.set_var_arg(opt->var_arg);
        init_args_info_gen.set_num(index.str());

        if (opt->multiple) {
            init_args_info_gen.set_multiple(true);
            init_args_info_gen.set_min(opt->multiple_min);
            init_args_info_gen.set_max(opt->multiple_max);
        } else {
            init_args_info_gen.set_multiple(false);
        }

        init_args_info_gen.generate_init_args_info(stream, indentation);

        // skip the details
        if (opt->details)
            ++i;

        // skip the text after
        if (opt->text_after)
            ++i;

    }
}

void CmdlineParserCreator::generate_custom_getopt(ostream &stream, unsigned int indentation)
{
    custom_getopt_gen_gen_class customgetopt;

    customgetopt.generate_custom_getopt_gen (stream, indentation);
}

const string
CmdlineParserCreator::generate_purpose()
{
  string wrapped_purpose;

  if (gengetopt_purpose != NULL)
    {
      wrap_cstr(wrapped_purpose, 0, 0, gengetopt_purpose);
    }

  return wrapped_purpose;
}

const string
CmdlineParserCreator::generate_versiontext()
{
  string wrapped_versiontext;

  if (gengetopt_versiontext != NULL)
    {
	  wrap_cstr(wrapped_versiontext, 0, 0, gengetopt_versiontext);
	}

  return wrapped_versiontext;
}

const string
CmdlineParserCreator::generate_description()
{
  string wrapped_description;

  if (gengetopt_description != NULL)
    {
      wrap_cstr(wrapped_description, 0, 0, gengetopt_description);
    }

  return wrapped_description;
}


OptionHelpList *
CmdlineParserCreator::generate_help_option_list(bool generate_hidden, bool generate_details)
{
  OptionHelpList *option_list = new OptionHelpList;

  unsigned long desc_col;
  struct gengetopt_option * opt;

  int           type_len;
  const char   *type_str;
  ostringstream stream;

  // if we want to generate details then we will also generate hidden options
  if (strict_hidden)
      generate_hidden = false;
  else if (generate_details)
      generate_hidden = true;

  /* calculate columns */
  desc_col = 0;
  foropt {
    // if (opt->hidden && !generate_hidden)
    //    continue;
    // when computing columns, we also consider hidden_options, so that
    // the --help and --full-help will be aligned just the same
    // IMPORTANT: this is also crucial due to how the help string array
    // is built starting from the full-help string array:
    // we iterate over the two lists of options and check whether the
    // corresponding strings are the same; thus, the help strings must
    // have the same space alignments, otherwise they're not equal

    unsigned int width = 2 + 4 + 2;  // ws + "-a, " + ws

    width += strlen (opt->long_opt) + 2;  // "--"

    if ((opt->type != ARG_FLAG) &&
        (opt->type != ARG_NO))
      {
        if (opt->type_str)
          type_str = opt->type_str;
        else
          type_str = arg_names[opt->type];
        type_len = strlen(type_str);

        width += type_len + 1;        // "="

        if (opt->arg_is_optional)
          width += 2; // "[" and "]"
      }

    if (width > desc_col)
      desc_col = width;
  }

  if (desc_col > MAX_STARTING_COLUMN)
    desc_col = MAX_STARTING_COLUMN;

  /* print justified options */
  char *prev_group = 0;
  char *prev_mode = 0;
  char *curr_section = 0;
  bool first_option = true;

  foropt
    {
      // if the option is hidden and has no text, avoid printing a section
      // containing only hidden options
      if (opt->section &&
              (!curr_section || strcmp (curr_section, opt->section)) &&
              (!opt->hidden || generate_hidden ||
                      opt->text_before || opt->text_after))
      {
          curr_section = opt->section;

          ostringstream sec_string;

          if (! first_option)
              sec_string << "\\n";

          sec_string << opt->section << ":" ;

          string wrapped_def;
          wrap_cstr(wrapped_def, 0, 0, sec_string.str());
          option_list->push_back(wrapped_def);

          if (opt->section_desc)
          {
              string wrapped_desc ( 2, ' ');
              wrap_cstr ( wrapped_desc, 2, 0, opt->section_desc );

              option_list->push_back(wrapped_desc);
          }
      }

      if (opt->group_value &&
              (! prev_group || strcmp (opt->group_value, prev_group) != 0))
      {
          string group_string = "\\n Group: ";
          string wrapped_desc;

          if (opt->group_desc && strlen (opt->group_desc))
          {
              wrapped_desc = "\\n  ";
              wrap_cstr (wrapped_desc, 2, 0, opt->group_desc);
          }

          group_string += opt->group_value + wrapped_desc;

          option_list->push_back (group_string);

          prev_group = opt->group_value;
      }

      if (opt->mode_value &&
              (! prev_mode || strcmp (opt->mode_value, prev_mode) != 0))
      {
          string mode_string = "\\n Mode: ";
          string wrapped_desc;

          if (opt->mode_desc && strlen (opt->mode_desc))
          {
              wrapped_desc = "\\n  ";
              wrap_cstr (wrapped_desc, 2, 0, opt->mode_desc);
          }

          mode_string += opt->mode_value + wrapped_desc;

          option_list->push_back (mode_string);

          prev_mode = opt->mode_value;
      }

      // a possible description to be printed before this option
      if (opt->text_before)
      {
          string wrapped_desc;
          wrap_cstr ( wrapped_desc, 0, 0, opt->text_before);

          option_list->push_back(wrapped_desc);
      }

      if (!opt->hidden || generate_hidden) {
          first_option = false;
          const char * def_val = NULL;
          string def_str = "`";

          ostringstream option_stream;

          if (opt->type == ARG_FLAG || opt->type == ARG_NO)
          {
              def_val = NULL;

              if (opt->short_opt)
                  option_stream << "  -" << opt->short_opt << ", ";
              else
                  option_stream << "      ";

              option_stream << "--" << opt->long_opt;

              if (opt->type == ARG_FLAG)
                  def_val = opt->flagstat ? "on" : "off";
          }
          else
          {
              def_val = NULL;

              if (opt->type_str)
                  type_str = opt->type_str;
              else
                  type_str = arg_names[opt->type];

              type_len = strlen(type_str);

              if (opt->short_opt)
              {
                  option_stream << "  -" << opt->short_opt << ", ";
              }
              else
              {
                  option_stream << "      ";
              }

              bool arg_optional = opt->arg_is_optional;
              option_stream << "--" << opt->long_opt
              << (arg_optional ? "[" : "")
              << "=" << type_str
              << (arg_optional ? "]" : "");

              if (opt->default_string)
              {
                  def_str += opt->default_string;
                  def_str += "'";
                  def_val = def_str.c_str();
              }
          }

          const string &option_string = option_stream.str();
          stream << option_string;
          const char *opt_desc = opt->desc;

          if ((option_string.size() >= MAX_STARTING_COLUMN) ||
                  (desc_col <= option_string.size()))
          {
              string indentation (MAX_STARTING_COLUMN, ' ');
              stream << "\\n" << indentation;
          }
          else
          {
              string indentation (desc_col - option_string.size(), ' ');
              stream << indentation;
          }

          generate_help_desc_print(stream, desc_col, opt_desc, def_val,
                  (opt->acceptedvalues ? opt->acceptedvalues->toString() : ""),
                  (opt->required && show_required_string != "" ? show_required_string : ""));

          option_list->push_back(stream.str());
          stream.str("");
      }

      // before the text, we generate details if we need to and the option isn't hidden
      if (opt->details && generate_details && (!opt->hidden || generate_hidden)) {
          string wrapped_desc ( 2, ' ');
          // details are indented
          wrap_cstr ( wrapped_desc, 2, 0, opt->details);

          option_list->push_back(wrapped_desc);
      }

      // a possible description to be printed after this option
      if (opt->text_after)
      {
          string wrapped_desc;
          wrap_cstr ( wrapped_desc, 0, 0, opt->text_after);

          option_list->push_back(wrapped_desc);
      }
    }

  return option_list;
}

template <typename Collection>
void generate_counter_init(const Collection &collection, const string &name, ostream &stream, unsigned int indentation)
{
    string indent_str (indentation, ' ');
    typename Collection::const_iterator end = collection.end();

    for ( typename Collection::const_iterator idx = collection.begin(); idx != end; ++idx)
    {
        stream << indent_str;
        stream << ARGS_STRUCT << "->" << canonize_name (idx->first) << "_" <<
            name << "_counter = 0 ;";
        stream << endl;
    }
}

void
CmdlineParserCreator::generate_given_init(ostream &stream,
                                          unsigned int indentation)
{
  struct gengetopt_option * opt;
  string indent_str (indentation, ' ');
  clear_given_gen_class clear_given;
  clear_given.set_arg_struct(ARGS_STRUCT);

  /* now we initialize "given" fields */
  foropt
    {
      stream << indent_str;
      clear_given.set_var_arg(opt->var_arg);
      clear_given.set_group(opt->multiple && opt->group_value);
      clear_given.generate_clear_given(stream);
    }

  // for group counter initialization
  generate_counter_init(gengetopt_groups, "group", stream, indentation);

  // for mode counter initialization
  generate_counter_init(gengetopt_modes, "mode", stream, indentation);
}

void
CmdlineParserCreator::generate_reset_groups(ostream &stream, unsigned int indentation)
{
  struct gengetopt_option * opt;
  string indent_str (indentation, ' ');
  ostringstream body;
  reset_group_gen_class reset_group;
  clear_given_gen_class clear_given;
  clear_given.set_arg_struct(ARGS_STRUCT);

  reset_group.set_args_info (c_source_gen_class::args_info);

  groups_collection_t::const_iterator end = gengetopt_groups.end();
  for ( groups_collection_t::const_iterator idx = gengetopt_groups.begin();
        idx != end; ++idx)
    {
      body.str ("");
      bool found_option = false;

      foropt
      {
        if (opt->group_value && strcmp(opt->group_value, idx->first.c_str()) == 0)
          {
            /* now we reset "given" fields */
            stream << indent_str;
            clear_given.set_var_arg(opt->var_arg);
            clear_given.set_group(opt->multiple && opt->group_value);
            clear_given.generate_clear_given(body);

            free_option (opt, body, indentation);
            found_option = true;
          }
      }

      if (found_option)
        {
          reset_group.set_name (canonize_name (idx->first));
          reset_group.set_body (body.str ());
          reset_group.generate_reset_group (stream);
        }
    }
}

void
CmdlineParserCreator::free_option(struct gengetopt_option *opt,
                                  ostream &stream, unsigned int indentation)
{
  if (opt->type == ARG_NO)
    return;

  if (opt->type != ARG_FLAG)
    {
      if (opt->multiple)
        {
          free_multiple_gen_class free_multiple;
          free_multiple.set_has_string_type(opt->type == ARG_STRING);
          free_multiple.set_structure (ARGS_STRUCT);

          free_multiple.set_opt_var (opt->var_arg);
          free_multiple.generate_free_multiple
            (stream, indentation);
        }
      else
        {
          free_string_gen_class free_string;
          free_string.set_has_string_type(opt->type == ARG_STRING);
          free_string.set_structure (ARGS_STRUCT);

          free_string.set_opt_var (opt->var_arg);
          free_string.generate_free_string (stream, indentation);
        }
    }
}

void
CmdlineParserCreator::generate_list_def(ostream &stream, unsigned int indentation)
{
  struct gengetopt_option * opt;
  string indent_str (indentation, ' ');
  multiple_opt_list_gen_class multiple_opt_list;

  /* define linked-list structs for multiple options */
  foropt
    {
      if (opt->multiple)
        {
          if (opt->type)
            {
              stream << indent_str;
              multiple_opt_list.set_arg_name (opt->var_arg);
              multiple_opt_list.generate_multiple_opt_list (stream, indentation);
              stream << endl;
            }
        }
    }
}

void
CmdlineParserCreator::generate_multiple_fill_array(ostream &stream, unsigned int indentation)
{
  struct gengetopt_option * opt;
  string indent_str (indentation, ' ');
  multiple_fill_array_gen_class filler;

  /* copy linked list into the array */
  foropt
    {
      if (opt->multiple && opt->type)
        {
          stream << indent_str;
          filler.set_option_var_name (opt->var_arg);
          filler.set_arg_type(arg_type_constants[opt->type]);
          filler.set_type (arg_types_names[opt->type]);
          string default_string = "0";
          if (opt->default_string) {
              if (opt->type == ARG_STRING)
                  default_string = string("\"") + opt->default_string + "\"";
              else if (opt->type == ARG_ENUM)
                  default_string = from_value_to_enum(opt->var_arg, opt->default_string);
              else
                  default_string = opt->default_string;
          }
          filler.set_default_value (default_string);

          filler.generate_multiple_fill_array (stream, indentation);

          stream << endl;
        }
    }
}

void
CmdlineParserCreator::generate_update_multiple_given(ostream &stream, unsigned int indentation)
{
  if (! has_multiple_options())
    return;

  string indent_str (indentation, ' ');

  stream << endl;
  stream << indent_str;

  update_given_gen_class update_given_gen;
  struct gengetopt_option * opt;

  foropt
    {
      if (opt->multiple)
        {
          update_given_gen.set_option_var_name (opt->var_arg);
          update_given_gen.generate_update_given (stream, indentation);
        }
    }
}

void
CmdlineParserCreator::generate_check_modes(ostream &stream, unsigned int indentation)
{
    // no need to check for conflict if there's only one mode
    if (gengetopt_modes.size() < 2)
        return;

    string indent_str (indentation, ' ');

    stream << endl;
    stream << indent_str;

    const ModeOptionMap &modeoptionmap = getModeOptionMap();

    check_modes_gen_class check_modes_gen;

    // now we check each mode options against every other mode options:
    // the first one with the other n-1, the second one with the other n-2, etc.
    ModeOptionMap::const_iterator map_it1, map_it2;
    for (ModeOptionMap::const_iterator map_it = modeoptionmap.begin(); map_it != modeoptionmap.end(); ++map_it) {
        map_it1 = map_it;
        ++map_it;
        if (map_it == modeoptionmap.end())
            break;
        for (map_it2 = map_it; map_it2 != modeoptionmap.end(); ++map_it2) {
            const string mode1 = canonize_name(map_it1->first);
            const string mode2 = canonize_name(map_it2->first);

            check_modes_gen.set_mode1_name(mode1);
            check_modes_gen.set_mode2_name(mode2);

            ostringstream mode1_given, mode2_given, mode1_options, mode2_options;

            std::for_each(map_it1->second.begin(), map_it1->second.end(), pair_print_f<OptionValueElem>(mode1_given, mode1_options));
            std::for_each(map_it2->second.begin(), map_it2->second.end(), pair_print_f<OptionValueElem>(mode2_given, mode2_options));

            check_modes_gen.set_mode1_given_fields(mode1_given.str());
            check_modes_gen.set_mode1_options(mode1_options.str());
            check_modes_gen.set_mode2_given_fields(mode2_given.str());
            check_modes_gen.set_mode2_options(mode2_options.str());

            check_modes_gen.generate_check_modes(stream, indentation);
        }
        map_it = map_it1;
    }
}

void
CmdlineParserCreator::generate_clear_arg(ostream &stream, unsigned int indentation)
{
  struct gengetopt_option * opt;
  clear_arg_gen_class cleararg;

  /* now we initialize value fields */
  foropt
    {
      if (opt->type == ARG_NO)
        continue;

      cleararg.set_name(opt->var_arg);
      cleararg.set_suffix("arg");
      cleararg.set_value("NULL");
      cleararg.set_has_orig(opt->type != ARG_FLAG);
      cleararg.set_has_arg(false);

      if (opt->multiple && opt->type)
        {
          cleararg.set_has_arg(true);
        }
      else if (opt->type == ARG_STRING)
        {
          cleararg.set_has_arg(true);
          if (opt->default_given)
            cleararg.set_value
                ("gengetopt_strdup (\"" + string(opt->default_string) +
                "\")");
        }
      else if (opt->type == ARG_FLAG)
        {
          cleararg.set_has_arg(true);
          cleararg.set_suffix("flag");
          cleararg.set_value(opt->flagstat ? "1" : "0");
        }
      else if (opt->type == ARG_ENUM)
      {
        // initialize enum arguments to -1 (unless they have a default)
        cleararg.set_has_arg(true);
        if (opt->default_given)
            cleararg.set_value(from_value_to_enum(opt->var_arg, opt->default_string));
        else
            cleararg.set_value(string(opt->var_arg) + "__NULL");
      }
      else if (opt->default_given)
        {
          cleararg.set_has_arg(true);
          cleararg.set_value(opt->default_string);
        }

      cleararg.generate_clear_arg(stream, indentation);
    }
}

void
CmdlineParserCreator::generate_long_option_struct(ostream &stream,
                                                  unsigned int indentation)
{
  string indent_str (indentation, ' ');
  struct gengetopt_option * opt;

  foropt
    {
      stream << indent_str;

      stream << "{ \"" << opt->long_opt << "\",\t"
             << (opt->type == ARG_NO || opt->type == ARG_FLAG ? 0 :
                 (opt->arg_is_optional ? 2 : 1))
             << ", NULL, ";

      if (opt->short_opt)
        stream << "\'" << opt->short_opt << "\'";
      else
        stream << "0";

      stream << " }," << endl;
    }
}

string
CmdlineParserCreator::generate_getopt_string()
{
  struct gengetopt_option * opt;
  ostringstream built_getopt_string;

  foropt
    if (opt->short_opt)
      {
        built_getopt_string << opt->short_opt <<
          (opt->type == ARG_NO || opt->type == ARG_FLAG ? "" : ":");
        built_getopt_string <<
          (opt->arg_is_optional ? ":" : "");
      }

  return built_getopt_string.str ();
}

void
CmdlineParserCreator::generate_handle_no_short_option(ostream &stream,
                                                      unsigned int indentation)
{
  handle_options(stream, indentation, false);
}

void
CmdlineParserCreator::generate_handle_option(ostream &stream,
                                             unsigned int indentation)
{
  handle_options(stream, indentation, true);
}

void
CmdlineParserCreator::handle_options(ostream &stream, unsigned int indentation, bool has_short)
{
  struct gengetopt_option * opt;
  generic_option_gen_class option_gen;
  string indent_str (indentation, ' ');
  bool first = true;

  option_gen.set_has_short_option (has_short);

  // by default we handle '?' case in the switch
  // unless the user defined a short option as ?
  set_handle_question_mark(true);

  foropt
    {
      if (opt->short_opt == '?')
          set_handle_question_mark(false);

      if ((has_short && opt->short_opt) || (!has_short && !opt->short_opt))
        {
          if (has_short || first)
            stream << indent_str;

          option_gen.set_option_comment (opt->desc);
          option_gen.set_long_option (opt->long_opt);
          option_gen.set_short_option(opt->short_opt ? string (1, opt->short_opt) : "-");
          option_gen.set_option_var_name (opt->var_arg);
          option_gen.set_final_instructions("");

          if (!no_help && ((opt->short_opt == HELP_SHORT_OPT &&
                  strcmp(opt->long_opt, HELP_LONG_OPT) == 0)
                  || strcmp(opt->long_opt, HELP_LONG_OPT) == 0
                  || strcmp(opt->long_opt, FULL_HELP_LONG_OPT) == 0
                  || strcmp(opt->long_opt, DETAILED_HELP_LONG_OPT) == 0)) {
              bool full_help = (strcmp(opt->long_opt, FULL_HELP_LONG_OPT) == 0);
              bool detailed_help = (strcmp(opt->long_opt, DETAILED_HELP_LONG_OPT) == 0);
              if (no_handle_help) {
                    // we use the final_instructions parameter to call the free function
                    // and to return 0
                    const string final_instructions =
                    parser_function_name +
                    string("_free (&local_args_info);\nreturn 0;");

                    option_gen.set_final_instructions(final_instructions);

                    if (full_help) {
                        option_gen.set_long_option (FULL_HELP_LONG_OPT);
                        option_gen.set_option_comment (FULL_HELP_OPT_DESCR);
                    } else if (detailed_help) {
                        option_gen.set_long_option (DETAILED_HELP_LONG_OPT);
                        option_gen.set_option_comment (DETAILED_HELP_OPT_DESCR);
                    } else {
                        option_gen.set_long_option (HELP_LONG_OPT);
                        option_gen.set_short_option (HELP_SHORT_OPT_STR);
                        option_gen.set_option_comment (HELP_OPT_DESCR);
                    }
                    //option_gen.set_has_short_option (!full_help);
              } else {
                  handle_help_gen_class help_gen;
                  help_gen.set_parser_name (parser_function_name);
                  help_gen.set_full_help(full_help);
                  help_gen.set_detailed_help(detailed_help);
                  help_gen.set_short_opt(opt->short_opt == HELP_SHORT_OPT);
                  help_gen.generate_handle_help (stream, indentation);
                  stream << endl;
                  stream << endl;
                  continue;
              }
          }

          if (!no_version && ((opt->short_opt == VERSION_SHORT_OPT && strcmp(opt->long_opt, VERSION_LONG_OPT) == 0)
                  || strcmp(opt->long_opt, VERSION_LONG_OPT) == 0)) {
              if (no_handle_version) {
                  option_gen.set_long_option (VERSION_LONG_OPT);
                  option_gen.set_short_option (VERSION_SHORT_OPT_STR);
                  option_gen.set_option_comment (VERSION_OPT_DESCR);
                  //option_gen.set_has_short_option (true);

                  // we use the final_instrauctions parameter to call the free function
                  // and to return 0
                  const string final_instructions =
                      parser_function_name +
                      string("_free (&local_args_info);\nreturn 0;");

                  option_gen.set_final_instructions(final_instructions);
              } else {
                  handle_version_gen_class version_gen;
                  version_gen.set_parser_name (parser_function_name);
                  version_gen.set_short_opt (opt->short_opt == VERSION_SHORT_OPT);
                  version_gen.generate_handle_version (stream, indentation);
                  stream << endl;
                  stream << endl;
                  continue;
              }
          }

          if (opt->acceptedvalues != 0)
              option_gen.set_possible_values (OPTION_VALUES_NAME(opt->var_arg));
          else
              option_gen.set_possible_values ("0");

          string default_string = "0";
          if (opt->default_string)
              default_string = string("\"") + opt->default_string + "\"";
          option_gen.set_default_value (default_string);

          option_gen.set_arg_type(arg_type_constants[opt->type]);

          if (opt->group_value) {
              option_gen.set_group_var_name (canonize_name (opt->group_value));
              option_gen.set_option_has_group(true);
          } else
              option_gen.set_option_has_group(false);

          if (opt->mode_value) {
              // we reuse the variable group_var_name also for modes
              option_gen.set_group_var_name (canonize_name (opt->mode_value));
              option_gen.set_option_has_mode(true);
          } else
              option_gen.set_option_has_mode(false);

          option_gen.set_option_has_type(opt->type != 0);

          if (opt->multiple) {
              option_gen.set_multiple(true);
              option_gen.set_structure (string (opt->var_arg) + "_list");
          } else {
              option_gen.set_multiple(false);
              option_gen.set_structure (ARGS_STRUCT);
          }

          option_gen.generate_generic_option (stream, indentation);

          if (has_short)
            {
              stream << endl;
            }

          if (first && !has_short)
            {
              first = false;
              option_gen.set_gen_else ("else ");
            }
        }
    }

  if (! first && !has_short) // something has been generated
    {
      generateBreak(stream, indentation);
      stream << endl;
    }
}

#define GROUP_REQUIRED_COMPARISON "!="
#define GROUP_NOT_REQUIRED_COMPARISON ">"
#define GROUP_REQUIRED_MESSAGE "One"
#define GROUP_NOT_REQUIRED_MESSAGE "At most one"

void
CmdlineParserCreator::generate_handle_group(ostream &stream,
                                            unsigned int indentation)
{
  group_option_gen_class opt_gen;
  string indent_str (indentation, ' ');
  opt_gen.set_package_var_name (EXE_NAME);

  opt_gen.set_Comparison_rule(GROUP_NOT_REQUIRED_COMPARISON " 1");

  groups_collection_t::const_iterator end = gengetopt_groups.end();
  for ( groups_collection_t::const_iterator idx = gengetopt_groups.begin();
        idx != end; ++idx)
    {
      stream << indent_str;
      opt_gen.set_group_name (idx->first);
      opt_gen.set_group_var_name (canonize_name (idx->first));
      if (idx->second.required)
        {
          opt_gen.set_number_required(GROUP_REQUIRED_MESSAGE);
        }
      else
        {
          opt_gen.set_number_required(GROUP_NOT_REQUIRED_MESSAGE);
        }

      opt_gen.generate_group_option (stream, indentation);
      stream << endl;
    }
}

void
CmdlineParserCreator::generate_handle_required(ostream &stream,
                                               unsigned int indentation)
{
  struct gengetopt_option * opt;
  required_option_gen_class opt_gen;
  opt_gen.set_package_var_name ("prog_name");

  /* write test for required options or for multiple options
     (occurrence number check) */
  foropt
    if ( opt->required || opt->multiple )
      {
        if (opt->mode_value) {
            opt_gen.set_mode_condition("args_info->" +
                    canonize_name(opt->mode_value) + "_mode_counter && ");
        } else {
            opt_gen.set_mode_condition("");
        }

        // build the option command line representation
        ostringstream req_opt;
        req_opt << "'--" << opt->long_opt << "'";
        if (opt->short_opt)
          req_opt << " ('-" << opt->short_opt << "')";

        opt_gen.set_option_var_name (opt->var_arg);
        opt_gen.set_option_descr (req_opt.str ());

        // if the option is required this is the standard check
        if (opt->required) {
          opt_gen.set_checkrange(false);

          opt_gen.generate_required_option (stream, indentation);
        }

        // if the option is multiple we generate also the
        // occurrence range check
        if (opt->multiple) {
          opt_gen.set_checkrange(true);

          opt_gen.generate_required_option (stream, indentation);
        }

        // notice that the above ifs are not mutually exclusive:
        // a multiple option can have a range check without being
        // required.
      }

  // now generate the checks for required group options
  group_option_gen_class group_opt_gen;
  group_opt_gen.set_package_var_name ("prog_name");

  group_opt_gen.set_Comparison_rule("== 0");
  group_opt_gen.set_number_required(GROUP_REQUIRED_MESSAGE);

  groups_collection_t::const_iterator end = gengetopt_groups.end();
  for ( groups_collection_t::const_iterator idx = gengetopt_groups.begin();
        idx != end; ++idx)
  {
    if (idx->second.required)
    {
      group_opt_gen.set_group_name (idx->first);
      group_opt_gen.set_group_var_name (canonize_name (idx->first));

      group_opt_gen.generate_group_option (stream, indentation);
      stream << endl;
    }
  }
}

void
CmdlineParserCreator::generate_handle_dependencies(ostream &stream,
                                               unsigned int indentation)
{
  struct gengetopt_option * opt;
  dependant_option_gen_class opt_gen;
  opt_gen.set_package_var_name ("prog_name");
  string indent_str (indentation, ' ');

  /* write test for required options */
  foropt
    if ( opt->dependon )
      {
        stream << indent_str;

        ostringstream req_opt;
        req_opt << "'--" << opt->long_opt << "'";
        if (opt->short_opt)
          req_opt << " ('-" << opt->short_opt << "')";

        opt_gen.set_option_var_name (opt->var_arg);
        opt_gen.set_dep_option (canonize_name(opt->dependon));
        opt_gen.set_option_descr (req_opt.str ());
        opt_gen.set_dep_option_descr (opt->dependon);

        opt_gen.generate_dependant_option (stream, indentation);

        stream << endl;
      }
}

template <typename Collection>
void generate_counters(const Collection &collection, const string &name, ostream &stream, unsigned int indentation)
{
    group_counter_gen_class counter_gen;
    string indent_str (indentation, ' ');

    counter_gen.set_name(name);

    typename Collection::const_iterator end = collection.end();
    for ( typename Collection::const_iterator idx = collection.begin(); idx != end; ++idx) {
        stream << indent_str;
        counter_gen.set_group_name (canonize_name (idx->first));
        counter_gen.generate_group_counter (stream, indentation);
        stream << endl;
    }
}

void
CmdlineParserCreator::generate_group_counters(ostream &stream,
                                              unsigned int indentation)
{
    generate_counters(gengetopt_groups, "group", stream, indentation);
}

void
CmdlineParserCreator::generate_mode_counters(ostream &stream,
                                              unsigned int indentation)
{
    // we can reuse group counter gen class also for modes
    generate_counters(gengetopt_modes, "mode", stream, indentation);
}

int
CmdlineParserCreator::generate_source ()
{
  /* ****************************************************** */
  /* ********************************************** C FILE  */
  /* ****************************************************** */

  set_usage_string (generate_usage_string ());
  set_getopt_string (generate_getopt_string ());

  string output_source = c_filename;

  if (src_output_dir.size())
      output_source = src_output_dir + "/" + output_source;
  else if (output_dir.size())
      output_source = output_dir + "/" + output_source;

  ofstream *output_file = open_fstream (output_source.c_str());
  generate_c_source (*output_file);
  output_file->close ();
  delete output_file;

  return 0;
}

void
CmdlineParserCreator::generate_free(ostream &stream,
                                    unsigned int indentation)
{
  struct gengetopt_option * opt;

  foropt
    {
      free_option (opt, stream, indentation);
    }
}

void
CmdlineParserCreator::generate_list_free(ostream &stream,
                                         unsigned int indentation)
{
  struct gengetopt_option * opt;

  if (! has_multiple_options())
    return;

  free_list_gen_class free_list;

  foropt
    {
      if (opt->multiple && opt->type) {
        free_list.set_list_name(opt->var_arg);
        free_list.set_string_list(opt->type == ARG_STRING);
        free_list.generate_free_list(stream, indentation);
      }
    }
}

void
CmdlineParserCreator::generate_file_save_loop(ostream &stream, unsigned int indentation)
{
  struct gengetopt_option * opt;

  file_save_multiple_gen_class file_save_multiple;
  file_save_gen_class file_save;

  const string suffix = "_orig";
  const string suffix_given = "_given";

  foropt {
    if (opt->multiple) {
      file_save_multiple.set_has_arg(opt->type != ARG_NO);
      file_save_multiple.set_opt_var(opt->var_arg);
      file_save_multiple.set_opt_name(opt->long_opt);
      file_save_multiple.set_values
          ((opt->acceptedvalues ? OPTION_VALUES_NAME(opt->var_arg) : "0"));

      file_save_multiple.generate_file_save_multiple(stream, indentation);
    } else {
      file_save.set_opt_name(opt->long_opt);
      file_save.set_given(opt->var_arg + suffix_given);
      file_save.set_values
          ((opt->acceptedvalues ? OPTION_VALUES_NAME(opt->var_arg) : "0"));

      if (opt->type != ARG_NO && opt->type != ARG_FLAG) {
        file_save.set_arg(opt->var_arg + suffix + (opt->multiple ? " [i]" : ""));
      } else {
        file_save.set_arg("");
      }
      file_save.generate_file_save(stream, indentation);
    }
  }
}


