#pragma once

#include <memory>
#include <sstream>
#include <string>

namespace maps {
namespace wiki {
namespace tasks_ng {

class Query
{
public:
    Query()
        : os_(std::make_shared<std::ostringstream>())
    {}

    Query& count(std::string_view fields = std::string_view("1"))
    {
        *os_ << "SELECT COUNT(" << fields << ")";
        return *this;
    }

    Query& select(std::string_view fields)
    {
        *os_ << "SELECT " << fields;
        return *this;
    }

    Query& select(const Query& t)
    {
        return select(t.str());
    }

    Query& from(std::string_view tableName = std::string_view())
    {
        *os_ << " FROM " << tableName;
        return *this;
    }

    Query& where(std::string_view t = std::string_view())
    {
        *os_ << " WHERE " << t;
        return *this;
    }

    Query& where(const Query& t)
    {
        return where(t.str());
    }

    Query& insertInto(std::string_view tableName)
    {
        *os_ << "INSERT INTO " << tableName << " ";
        return *this;
    }

    Query& columns(std::string_view str)
    {
        *os_ << "(" << str << ")";
        return *this;
    }

    Query& columns(const Query& t)
    {
        return columns(t.str());
    }

    Query& values(const Query& t = Query())
    {
        *os_ << " VALUES ";
        auto str = t.str();
        if (!str.empty()) {
            return columns(str);
        }
        return *this;
    }

    Query& update(std::string_view tableName)
    {
        *os_ << "UPDATE " << tableName;
        return *this;
    }

    Query& set(std::string_view str)
    {
        *os_ << " SET " << str;
        return *this;
    }

    Query& set(const Query& t)
    {
        return set(t.str());
    }

    Query& orderBy(std::string_view fields)
    {
        *os_ << " ORDER BY " << fields << " ";
        return *this;
    }

    Query& offset(size_t value)
    {
        *os_ << " OFFSET " << value << " ";
        return *this;
    }

    Query& limit(size_t value)
    {
        *os_ << " LIMIT " << value;
        return *this;
    }

    Query& returning(std::string_view fields)
    {
        *os_ << " RETURNING " << fields;
        return *this;
    }

    template <typename T>
    Query& operator << (const T& t)
    {
        *os_ << t;
        return *this;
    }

    Query& append(std::string_view str)
    {
        *os_ << str;
        return *this;
    }

    Query& append(const Query& t)
    {
        return append(t.str());
    }

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

private:
    std::shared_ptr<std::ostringstream> os_;
};

template <>
inline Query& Query::operator << (const Query& t)
{
    return append(t);
}

} // namespace tasks_ng
} // namespace wiki
} // namespace maps
