#ifndef INCLUDED_BOBCAT_ARG_
#define INCLUDED_BOBCAT_ARG_

//      Singleton Class built around getopt() and getopt_long() (3)

#include <getopt.h>

#include <string>
#include <vector>

#include <bobcat/exception>

namespace FBB
{

struct ArgTypes__
{
    using StrVect =  std::vector<std::string>;

    enum Type                           // Argument types
    {
        NoArg,
        Required,
        Optional,
        AsCharOption
    };
};

class LongOption__: public ArgTypes__       // in Arg: aka 'LongOption'
{
    friend class Arg__;
    friend class ArgConfig;

    std::string d_name;
    Type        d_type;
    int         d_optionChar;

    public:
        explicit LongOption__(char const *name);
        LongOption__(char const *name, Type type);
        LongOption__(char const *name, int optionChar);

        std::string const &longName() const;    // .f
        int optionChar() const;                 // .f
        Type type() const;                      // .f
};

class Arg__;
class Arg: public ArgTypes__
{
    friend class ArgConfig;         // accesses the constructors and
                                    // longOption()

    Arg__ *d_ptr;                   // pointer to the implementation

    static Arg *s_arg;

    static char const s_alreadyInitialized[];

    protected:
        static char s_accept[];

    public:
        Arg(Arg const &other) = delete;
        ~Arg();

        using LongOption =  FBB::LongOption__;
        using OptVect =  std::vector<std::pair<int, std::string>>;


            // the following initialize members return non-const
            // Arg references for backward compatibility: all Arg 
            // members are const members so the non-const Arg & 
            // returning initialize members in fact return non-
            // modifyable objects.

        static Arg &initialize(char const *optstring, int argc,         // 1
                                     char **argv);

        static Arg &initialize(int accept,                              // 2
                               char const *optstring, int argc, char **argv);

        static Arg const &initialize(char const *accept,                // 3
                               char const *optstring, int argc, char **argv);

        static Arg &initialize(char const *optstring,                   // 4
                                LongOption const *const begin,
                                LongOption const *const end,
                                int argc, char **argv);

        static Arg &initialize(int accept, char const *optstring,       // 5
                                LongOption const *const begin,
                                LongOption const *const end,
                                int argc, char **argv);

        static Arg const &initialize(char const *accept,                // 6
                                char const *optstring,
                                LongOption const *const begin,
                                LongOption const *const end,
                                int argc, char **argv);

        static Arg &instance();

        std::string const &argv0() const;

        std::string const &basename() const;

        void help() const;

        size_t nArgs() const;
        size_t beyondDashes() const;

        std::vector<std::string> const &args() const;
        std::vector<std::string>::const_iterator begin() const;
        std::vector<std::string>::const_iterator end() const;

            // total number of specified short (and combined long) options
        size_t nOptions() const;
            // total numer of long-only options specified
        size_t nLongOptions() const;

        size_t option(int option) const;                                // 1
        size_t option(std::string const &optchars) const;               // 2
        size_t option(size_t idx,                                       
                        std::string *value, int option) const;          // 3
        size_t option(size_t *idx,                                      
                        std::string *value, int option) const;          // 4
        size_t option(size_t idx, std::string *value,                   
                        char const *longOption) const;                  // 5
        size_t option(size_t *idx, std::string *value,
                char const *longOption) const;                          // 6
        size_t option(std::string *value, int optChar) const;           // 7.f
        size_t option(std::string *value, char const *longOption)       // 8.f
                                                              const;
        char const *operator[](size_t idx) const;

        void versionHelp(void (*usage)(std::string const &progname),
            char const *version, size_t minArgs, int helpFlag = 'h',
            int versionFlag = 'v') const;

        char const **argPointers() const;                               // 2
        char const **argPointers();                                     // 3

    protected:
        Arg(char const *accept, char const *optstring,                  // 1
            LongOption const *const begin, LongOption const *const end,
            int argc, char **argv);

    private:
        static Arg &init(char const *accept, char const *optstring,
                               LongOption const *const begin,
                               LongOption const *const end,
                               int argc, char **argv);
};

inline std::string const &LongOption__::longName() const
{
    return d_name;
}
inline LongOption__::Type LongOption__::type() const
{
    return d_type;
}
inline int LongOption__::optionChar() const
{
    return d_optionChar;
}
inline size_t Arg::option(std::string *value, int optChar) const
{
    return option(static_cast<size_t>(0), value, optChar);
}
inline size_t Arg::option(std::string *value, char const *longOption) const
{
    return option(0UL, value, longOption);
}

} // FBB

#endif
