#include <cassert>
#include "argparse_formatter.hpp"
#include "argparse_util.hpp"

#include "argparse.hpp"

namespace argparse {
    constexpr size_t OPTION_HELP_SLACK = 2;
    std::string INDENT = "  ";
    std::string USAGE_PREFIX = "usage: ";

    std::string long_option_str(const Argument& argument);
    std::string short_option_str(const Argument& argument);
    std::string determine_metavar(const Argument& argument);
    /*
     * DefaultFormatter
     */
    DefaultFormatter::DefaultFormatter(size_t option_name_width, size_t total_width)
        : option_name_width_(option_name_width)
        , total_width_(total_width)
        , parser_(nullptr)
        {}

    void DefaultFormatter::set_parser(ArgumentParser* parser) {
        parser_ = parser;
    }

    std::string DefaultFormatter::format_usage() const {
        if (!parser_) throw ArgParseError("parser not initialized in help formatter");


        std::stringstream ss;
        ss << USAGE_PREFIX << parser_->prog();

        int num_unshown_options = 0;
        for (const auto& group : parser_->argument_groups()) {
            auto args = group.arguments();
            for(const auto& arg : args) {

                if(arg->show_in() != ShowIn::USAGE_AND_HELP) {
                    num_unshown_options++;
                    continue;
                }

                ss << " ";

                if (!arg->required()) {
                    ss << "[";
                }

                auto short_opt = short_option_str(*arg);
                if (!short_opt.empty()) {
                    ss << short_opt; 
                } else {
                    ss << long_option_str(*arg);
                }

                if (!arg->required()) {
                    ss << "]";
                }
            }
        }
        if (num_unshown_options > 0) {
            ss << " [OTHER_OPTIONS ...]";
        }

        size_t prefix_len = USAGE_PREFIX.size();

        std::stringstream wrapped_ss;
        bool first = true;
        for(const auto& line : wrap_width(ss.str(), total_width_ - prefix_len, {" [", " -"})) {
            if(!first) {
                //pass
                wrapped_ss << std::string(prefix_len, ' ');
            }
            wrapped_ss << line;
            first = false;
        }
        wrapped_ss << "\n";

        return wrapped_ss.str();
    }

    std::string DefaultFormatter::format_description() const {
        if (!parser_) throw ArgParseError("parser not initialized in help formatter");

        std::stringstream ss;
        ss << "\n";
        for(auto& line : wrap_width(parser_->description(), total_width_)) {
            ss << line;
        }
        ss << "\n";
        return ss.str();
    }

    std::string DefaultFormatter::format_arguments() const {
        if (!parser_) throw ArgParseError("parser not initialized in help formatter");

        std::stringstream ss;

        for (const auto& group : parser_->argument_groups()) {
            auto args = group.arguments();
            if (args.size() > 0) {
                ss << "\n";
                ss << group.name() << ":" << "\n";
                for (const auto& arg : args) {
                    std::stringstream arg_ss;
                    arg_ss << std::boolalpha;


                    auto long_opt_descr = long_option_str(*arg);
                    auto short_opt_descr = short_option_str(*arg);

                    //name/option
                    arg_ss << INDENT;
                    if (!short_opt_descr.empty()) {
                        arg_ss << short_opt_descr;
                    }
                    if (!long_opt_descr.empty()) {
                        if (!short_opt_descr.empty()) {
                            arg_ss << ", ";
                        }
                        arg_ss << long_opt_descr;
                    }

                    size_t pos = arg_ss.str().size();

                    if (pos + OPTION_HELP_SLACK > option_name_width_) {
                        //If the option name is too long, wrap the help 
                        //around to a new line
                        arg_ss << "\n";
                        pos = 0;
                    }
                    
                    //Argument help
                    auto help_lines = wrap_width(arg->help(), total_width_ - option_name_width_);
                    for (auto& line : help_lines) {
                        //Pad out the help
                        assert(pos <= option_name_width_);
                        arg_ss << std::string(option_name_width_ - pos, ' ');

                        //Print a wrapped line
                        arg_ss << line;
                        pos = 0;
                    }

                    //Default
                    if (!arg->default_value().empty()) {
                        if(!arg->help().empty()) {
                            arg_ss << " ";
                        }
                        arg_ss << "(Default: " << arg->default_value() << ")";
                    }
                    arg_ss << "\n";
                    ss << arg_ss.str();
                }
                if (!group.epilog().empty()) {
                    ss << "\n";

                    auto epilog_lines = wrap_width(group.epilog(), total_width_ - INDENT.size());
                    for (auto& line : epilog_lines) {
                        ss << INDENT << line;
                    }
                    ss << "\n";
                }
            }
        }

        return ss.str();
    }

    std::string DefaultFormatter::format_epilog() const {
        if (!parser_) throw ArgParseError("parser not initialized in help formatter");

        std::stringstream ss;
        ss << "\n";
        for(auto& line : wrap_width(parser_->epilog(), total_width_)) {
            ss << line;
        }
        ss << "\n";
        return ss.str();
    }

    std::string DefaultFormatter::format_version() const {
        if (!parser_) throw ArgParseError("parser not initialized in help formatter");
        return parser_->version() + "\n";
    }

    /*
     * Utilities
     */
    std::string long_option_str(const Argument& argument) {
        auto long_opt = argument.long_option();
        if(argument.nargs() != '0' && !argument.positional()) {
            long_opt += + " " + determine_metavar(argument);
        }
        return long_opt;
    }

    std::string short_option_str(const Argument& argument) {
        auto short_opt = argument.short_option();
        if(!short_opt.empty()) {
            if(argument.nargs() != '0' && !argument.positional()) {
                short_opt += " " + determine_metavar(argument);
            }
            return short_opt;
        } else {
            return "";
        }
    }

    std::string determine_metavar(const Argument& arg) {

        std::string base_metavar = arg.metavar();
        if (!arg.choices().empty()) {
            //We allow choices to override the default metavar
            std::stringstream choices_ss;
            choices_ss << "{";
            bool first = true;
            for(const auto& choice : arg.choices()) {
                if (!first) {
                    choices_ss << ", ";
                }
                choices_ss << choice;
                first = false;
            }
            choices_ss << "}";
            base_metavar = choices_ss.str();
        }

        std::string metavar;
        if (arg.nargs() == '0' || arg.positional()) {
            //empty
            metavar = "";
        } else if (arg.nargs() == '1') {
            metavar = base_metavar;
        } else if (arg.nargs() == '?') {
            metavar = "[" + base_metavar + "]";
        } else if (arg.nargs() == '+') {
            metavar = base_metavar + " [" + base_metavar + " ...]";
        } else if (arg.nargs() == '*') {
            metavar = "[" + base_metavar + " [" + base_metavar + " ...]]";
        } else {
            assert(false);
        }
        return metavar;
    }

} //namespace
