#include "magic_strings.h"
#include "sql.h"
#include <yandex/maps/wiki/mds_dataset/filter.h>
#include <yandex/maps/wiki/mds_dataset/export_metadata.h>
#include <yandex/maps/wiki/common/string_utils.h>

#include <utility>
#include <vector>

namespace maps {
namespace wiki {
namespace mds_dataset {

typedef pqxx::transaction_base Txn;

template <typename Self>
class Filter<Self>::Impl
{
public:
    Impl(Txn& txn) : txn_(txn) {}
    ~Impl() = default;

    std::string toSql() const
    {
        std::ostringstream clause;

        if (!clauses_.empty()) {
            clause << "WHERE " << common::join(clauses_, "\nAND ");
        }
        return clause.str();
    }

    std::vector<std::string> clauses_;
    Txn& txn_;
};

template <typename Self>
using Traits = sql::Traits<typename Self::MetadataType>;

template <typename Self>
Filter<Self>::Filter(Txn& txn)
    : pimpl_(new Filter<Self>::Impl(txn))
{}

template <typename Self>
Filter<Self>::~Filter()
{}

template <typename Self>
Self& Filter<Self>::byId(const DatasetID& id)
{
    std::ostringstream clause;
    clause << Traits<Self>::TABLE_META << "." << sql::col::ID
           << " = " << pimpl_->txn_.quote(id);
    addClause(clause.str());
    return *static_cast<Self*>(this);
}

template <typename Self>
Self& Filter<Self>::byStatus(DatasetStatus status)
{
    std::ostringstream clause;
    clause << Traits<Self>::TABLE_META << "." << sql::col::STATUS
           << " = " << static_cast<int>(status);
    addClause(clause.str());

    return *static_cast<Self*>(this);
}

template <typename Self>
Self& Filter<Self>::byRegion(const Region& region)
{
    std::ostringstream clause;
    clause << Traits<Self>::TABLE_META << "." << sql::col::REGION
           << " = " << pimpl_->txn_.quote(region);
    addClause(clause.str());

    return *static_cast<Self*>(this);
}

template <typename Self>
Self& Filter<Self>::createdAfter(Timestamp created)
{
    std::ostringstream clause;
    clause << Traits<Self>::TABLE_META << "." << sql::col::CREATED << " > "
           << sql::func::TO_TIMESTAMP << "(" << timestampToSec(created) << ")";
    addClause(clause.str());
    return *static_cast<Self*>(this);
}

template <typename Self>
Self& Filter<Self>::createdBefore(Timestamp created)
{
    std::ostringstream clause;
    clause << Traits<Self>::TABLE_META << "." << sql::col::CREATED << " < "
           << sql::func::TO_TIMESTAMP << "(" << timestampToSec(created) << ")";
    addClause(clause.str());
    return *static_cast<Self*>(this);
}

template <typename Self>
std::string Filter<Self>::toSql() const
{
    return pimpl_->toSql();
}

template <typename Self>
void Filter<Self>::addClause(std::string clause)
{
    pimpl_->clauses_.push_back(std::move(clause));
}


ExportFilter::ExportFilter(Txn& txn) : Filter(txn)
{}

ExportFilter::~ExportFilter()
{}

ExportFilter& ExportFilter::bySubset(Subset subset)
{
    std::ostringstream clause;
    clause << sql::tbl::EXPORT_META << "." << sql::col::SUBSET
           << " = " << static_cast<int>(subset);
    addClause(clause.str());
    return *this;
}

ExportFilter& ExportFilter::tested(bool isTested)
{
    std::ostringstream clause;
    clause << sql::tbl::EXPORT_META << "." << sql::col::TESTED
           << " = " << (isTested ? "true" : "false");
    addClause(clause.str());
    return *this;
}

// Explicit instantiation
template class Filter<ExportFilter>;

} // mds_dataset
} // wiki
} // maps
