/*
 * Copyright (c) 2013-2021 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * This software is available to you under a choice of one of two
 * licenses.  You may choose to be licensed under the terms of the GNU
 * General Public License (GPL) Version 2, available from the file
 * COPYING in the main directory of this source tree, or the
 * OpenIB.org BSD license below:
 *
 *     Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *      - Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *
 *      - Redistributions in binary form must reproduce the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef _OPT_PARSER_H_
#define _OPT_PARSER_H_

#include <stdlib.h>
#include <list>
#include <vector>
#include <map>
#include <string>
using namespace std;

/******************************************************/
typedef enum
{
    // Returns: 0 - OK / 1 - parsing error / 2 - OK but need to exit / 3 - some other error
    PARSE_OK = 0,
    PARSE_ERROR = 1,
    PARSE_OK_WITH_EXIT = 3,
    PARSE_ERROR_SHOW_USAGE = 4
} ParseStatus;

typedef struct option_ifc
{
    string option_name;     // must be valid
    char option_short_name; // can be used as short option, if space than no short flag
    string option_value;    // if "" has no argument
    string description;     // will be display in usage
    int numOfSpaces;
    bool hidden; // if hidden than will not be diaplayed in regular usage
    bool is_mandatory;
} option_ifc_t;

typedef struct optional_help_section_data
{
    // format is:
    // <str1>: <str2> (with alignments)
    string name; // section's name
    string str1; // left string
    string str2; // right string
} optional_sec_data_t;

typedef vector<struct option_ifc> vec_option_t;
typedef vector<pair<string, vector<struct optional_help_section_data> > > optional_sections_t;

class CommandLineRequester
{
protected:
    // members
    vec_option_t options;
    string name;
    string description;
    optional_sections_t optional_sections_vec;

    // methods
    string BuildOptStr(option_ifc_t& opt);
    string FormatUsageStr(string str, int str_type_len, int ident_size, int is_not_description, bool is_left_side);
    string GetUsageOptionalSection(vector<struct optional_help_section_data> section_vec);
    string GetUsageOptional1Str(vector<struct optional_help_section_data>::iterator it);
    string GetUsageOptional2Str(vector<struct optional_help_section_data>::iterator it);

public:
    // methods
    CommandLineRequester(string req_name) : name(req_name) {}
    virtual ~CommandLineRequester() {}
    inline void setToolName(string req_name) { name = req_name; }
    inline void AddOptions(option_ifc_t options[], int arr_size)
    {
        for (int i = 0; i < arr_size; ++i)
            this->options.push_back(options[i]);
    }
    inline void AddOptions(string option_name,
                           char option_short_name,
                           string option_value,
                           string description,
                           bool hidden = false,
                           bool is_mandatory = false,
                           int numOfSpaces = 0)
    {
        option_ifc_t opt;
        opt.option_name = option_name;
        opt.option_short_name = option_short_name;
        opt.option_value = option_value;
        opt.description = description;
        opt.numOfSpaces = numOfSpaces;
        opt.hidden = hidden;
        opt.is_mandatory = is_mandatory;
        this->options.push_back(opt);
    }
    inline void AddDescription(string desc) { this->description = desc; }

    inline vec_option_t& GetOptions() { return this->options; }
    inline string& GetName() { return this->name; }

    // Add the data str1 and/or str2 to the optional help section "section_name".
    // If "section_name" does not exist, don't worry, it would be created."
    void AddOptionalSectionData(string section_name = "", string str1 = "", string str2 = "");

    string GetUsageSynopsis(bool hidden_options = false);
    string GetUsageDescription();
    string GetUsageOptions(bool hidden_options = false);
    string GetUsageOptionalSections(vector<string> excluded_sections = vector<string>());

    virtual ParseStatus HandleOption(string name, string value) = 0;
};

/******************************************************/
typedef list<CommandLineRequester*> list_p_command_line_req;
typedef map<char, string> map_char_str;
typedef map<string, CommandLineRequester*> map_str_p_command_line_req;

class CommandLineParser
{
private:
    // members
    list_p_command_line_req p_requesters_list;
    map_char_str short_opt_to_long_opt;
    map_str_p_command_line_req long_opt_to_req_map;

    string name;
    string last_error;
    string last_unknown_options;

    // methods
    void SetLastError(const char* fmt, ...);

public:
    // methods
    CommandLineParser(string parser_name) : name(parser_name), last_error(""), last_unknown_options("") {}
    ~CommandLineParser() {}

    inline const char* GetErrDesc() { return this->last_error.c_str(); }
    inline const char* GetUnknownOptions() { return this->last_unknown_options.c_str(); }
    inline void setParserName(string parser_name) { name = parser_name; }
    int AddRequester(CommandLineRequester* p_req); // if multiple option than fail

    ParseStatus ParseOptions(int argc,
                             char** argv,
                             bool to_ignore_unknown_options = false,
                             list_p_command_line_req* p_ignored_requesters_list = NULL);

    string GetSynopsis(bool hidden_options = false);
    string GetUsage(bool hidden_options = false, vector<string> excluded_sections = vector<string>());
};

#endif /* not defined _OPT_PARSER_H_ */
