#include <yandex/maps/wiki/common/pg_connection_string.h>

#include <libpq-fe.h>

#include <sstream>
#include <memory>

namespace maps::wiki::common  {

class PGConnectionString::Impl {
public:
    explicit Impl(const std::string& connString)
    {
        std::unique_ptr<PQconninfoOption, std::function<void(PQconninfoOption*)>>
            connectionOptions(
                PQconninfoParse(connString.c_str(), NULL),
                [&](PQconninfoOption* ptr)
                {
                    if (ptr) {
                        PQconninfoFree(ptr);
                    }
                });
            std::map<std::string, std::string> options;
            for (auto optionPtr = connectionOptions.get(); optionPtr && optionPtr->keyword; ++optionPtr) {
            if (optionPtr->val) {
                options_.insert({std::string(optionPtr->keyword), std::string(optionPtr->val)});
            }
        }
    }

    std::string
    optionValue(const std::string& optionName) const
    {
        auto it = options_.find(optionName);
        return
            it != options_.end()
            ? it->second
            : std::string {};
    }

    bool
    isSet(const std::string& optionName) const
    {
        auto it = options_.find(optionName);
        return it != options_.end() && !it->second.empty();
    }

    void
    setOptionValue(const std::string& optionName, const std::string& optionValue)
    {
        options_[optionName] = optionValue;
    }

    std::string
    str() const
    {
        std::ostringstream os;
        for (const auto& pair :  options_) {
            if (pair.second.empty()) {
                continue;
            }
            os << " " << pair.first << "=" << escapeValue(pair.second);
        }
        return os.str();
    }

private:
    static std::string
    escapeValue(const std::string& value) 
    {
        std::string escaped;
        escaped.reserve(value.size());
        bool hasSpaces = false;
        for (const auto& c : value) {
            if (c == ' ') {
                hasSpaces = true;
            }
            if (c == '\'' || c == '\\') {
                escaped.push_back('\\');
            }
            escaped.push_back(c);
        }
        return hasSpaces
            ? "'" + escaped +"'"
            : escaped;
    }

    std::map<std::string, std::string> options_;
};//Impl


PGConnectionString::PGConnectionString(const std::string& connString)
    : impl_(new PGConnectionString::Impl(connString))
{
}

std::string
PGConnectionString::optionValue(const std::string& optionName) const
{
    return impl_->optionValue(optionName);
}

bool
PGConnectionString::isSet(const std::string& optionName) const
{
    return impl_->isSet(optionName);
}

void
PGConnectionString::setOptionValue(const std::string& optionName, const std::string& optionValue)
{
    impl_->setOptionValue(optionName, optionValue);
}

std::string
PGConnectionString::str() const
{
    return impl_->str();
}

COPYABLE_PIMPL_DEFINITIONS(PGConnectionString);

} // namespace maps::wiki::common
