#ifndef INCLUDED_BOBCAT_ARG_
#define INCLUDED_BOBCAT_ARG_

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

#include <getopt.h>

#include <memory>
#include <string>
#include <vector>

#include <bobcat/exception>

namespace FBB
{

class ArgTypes__
{
    protected:
                                // struct option: cf. 'man getopt'
                                // struct option 
                                // {
                                //     char const *name;
                                //     int         has_arg;    == Type, below
                                //     int        *flag;
                                //     int         val;
                                // };

        using OptStruct =  struct option;
        using StringVector =  std::vector<std::string>;

    public:
        enum Type                           // Argument types, values as
        {                                   // in 'man getopt_long'
            None        = 0,
            Required    = 1,
            Optional    = 2,
            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
};

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

    Arg__ *d_ptr;                   // pointer to the implementation

    static std::unique_ptr<Arg> s_arg;      // points to the Arg Singleton
    static char const s_alreadyInitialized[];

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

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

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

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

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

        static Arg &initialize(int accept, char const *optstring, // 4
                                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;   // 1.f
                                                                // 2.f
        size_t option(std::string *value, char const *longOption) 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();                             // 1.cc
        char const **argPointers() const;                       // 2.cc

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

inline std::string const &LongOption__::longName() const
{
    return d_name;
}
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(static_cast<size_t>(0), value, longOption);
}

} // FBB

#endif
