#include "last_getopt_parse_result.h"

namespace NLastGetopt {
    const TOptParseResult* TOptsParseResult::FindParseResult(const TdVec& vec, const TOpt* opt) {
        for (const auto& r : vec) {
            if (r.OptPtr() == opt)
                return &r;
        }
        return nullptr;
    }

    const TOptParseResult* TOptsParseResult::FindOptParseResult(const TOpt* opt, bool includeDefault) const {
        const TOptParseResult* r = FindParseResult(Opts_, opt);
        if (nullptr == r && includeDefault)
            r = FindParseResult(OptsDef_, opt);
        return r;
    }

    const TOptParseResult* TOptsParseResult::FindLongOptParseResult(const TString& name, bool includeDefault) const {
        return FindOptParseResult(&Parser_->Opts_->GetLongOption(name), includeDefault);
    }

    const TOptParseResult* TOptsParseResult::FindCharOptParseResult(char c, bool includeDefault) const {
        return FindOptParseResult(&Parser_->Opts_->GetCharOption(c), includeDefault);
    }

    bool TOptsParseResult::Has(const TOpt* opt, bool includeDefault) const {
        Y_ASSERT(opt);
        return FindOptParseResult(opt, includeDefault) != nullptr;
    }

    bool TOptsParseResult::Has(const TString& name, bool includeDefault) const {
        return FindLongOptParseResult(name, includeDefault) != nullptr;
    }

    bool TOptsParseResult::Has(char c, bool includeDefault) const {
        return FindCharOptParseResult(c, includeDefault) != nullptr;
    }

    const char* TOptsParseResult::Get(const TOpt* opt, bool includeDefault) const {
        Y_ASSERT(opt);
        const TOptParseResult* r = FindOptParseResult(opt, includeDefault);
        if (!r || r->Empty()) {
            try {
                throw TUsageException() << "option " << opt->ToShortString() << " is unspecified";
            } catch (...) {
                HandleError();
                // unreachable
                throw;
            }
        } else {
            return r->Back();
        }
    }

    const char* TOptsParseResult::GetOrElse(const TOpt* opt, const char* defaultValue) const {
        Y_ASSERT(opt);
        const TOptParseResult* r = FindOptParseResult(opt);
        if (!r || r->Empty()) {
            return defaultValue;
        } else {
            return r->Back();
        }
    }

    const char* TOptsParseResult::Get(const TString& name, bool includeDefault) const {
        return Get(&Parser_->Opts_->GetLongOption(name), includeDefault);
    }

    const char* TOptsParseResult::Get(char c, bool includeDefault) const {
        return Get(&Parser_->Opts_->GetCharOption(c), includeDefault);
    }

    const char* TOptsParseResult::GetOrElse(const TString& name, const char* defaultValue) const {
        if (!Has(name))
            return defaultValue;
        return Get(name);
    }

    const char* TOptsParseResult::GetOrElse(char c, const char* defaultValue) const {
        if (!Has(c))
            return defaultValue;
        return Get(c);
    }

    TOptParseResult& TOptsParseResult::OptParseResult() {
        const TOpt* opt = Parser_->CurOpt();
        Y_ASSERT(opt);
        TdVec& opts = Parser_->IsExplicit() ? Opts_ : OptsDef_;
        if (Parser_->IsExplicit()) // default options won't appear twice
            for (auto& it : opts)
                if (it.OptPtr() == opt)
                    return it;
        opts.push_back(TOptParseResult(opt));
        return opts.back();
    }

    TString TOptsParseResult::GetProgramName() const {
        return Parser_->ProgramName_;
    }

    void TOptsParseResult::PrintUsage(IOutputStream& os) const {
        Parser_->Opts_->PrintUsage(Parser_->ProgramName_, os);
    }

    size_t TOptsParseResult::GetFreeArgsPos() const {
        return Parser_->Pos_;
    }

    TVector<TString> TOptsParseResult::GetFreeArgs() const {
        TVector<TString> v;
        for (size_t i = GetFreeArgsPos(); i < Parser_->Argc_; ++i) {
            v.push_back(Parser_->Argv_[i]);
        }
        return v;
    }

    size_t TOptsParseResult::GetFreeArgCount() const {
        return Parser_->Argc_ - GetFreeArgsPos();
    }

    void TOptsParseResult::Init(const TOpts* options, int argc, const char** argv) {
        try {
            Parser_.Reset(new TOptsParser(options, argc, argv));
            while (Parser_->Next()) {
                TOptParseResult& r = OptParseResult();
                r.AddValue(Parser_->CurValOrOpt().data());
            }

            Y_ENSURE(options);
            const auto freeArgs = GetFreeArgs();
            for (size_t i = 0; i < freeArgs.size(); ++i) {
                if (i >= options->ArgBindings_.size()) {
                    break;
                }

                options->ArgBindings_[i](freeArgs[i]);
            }
        } catch (...) {
            HandleError();
        }
    }

    void TOptsParseResult::HandleError() const {
        Cerr << CurrentExceptionMessage() << Endl;
        if (Parser_.Get()) { // parser initializing can fail (and we get here, see Init)
            if (Parser_->Opts_->FindLongOption("help") != nullptr) {
                Cerr << "Try '" << Parser_->ProgramName_ << " --help' for more information." << Endl;
            } else {
                PrintUsage();
            }
        }
        exit(1);
    }

    void TOptsParseResultException::HandleError() const {
        throw;
    }

}