#include <yandex/maps/wiki/revision/filters.h>
#include <yandex/maps/wiki/revision/branch.h>
#include <maps/libs/common/include/exception.h>

#include "sql_strings.h"
#include "context.h"
#include "query_generator.h"

#include <set>
#include <sstream>
#include <utility>
#include <vector>

namespace maps::wiki::revision::filters {

namespace {

const std::string GEOMETRY_ID_FIELD_DEFINED =
    sql::alias::OBJECT_REVISION + "." + sql::col::GEOMETRY_ID + " > 0";

const std::string GEOMETRY_TYPE_POINT      = "ST_Point";
const std::string GEOMETRY_TYPE_LINESTRING = "ST_LineString";
const std::string GEOMETRY_TYPE_POLYGON    = "ST_Polygon";

const std::string COMMIT_ATTRIBUTES = sql::alias::COMMIT + "." + sql::col::ATTRIBUTES;

template <typename T>
Treatment paramTreatment();

template <>
Treatment paramTreatment<DBID>()
    { return Treatment::Direct; }

// also used for time_t
template <>
Treatment paramTreatment<int64_t>()
    { return Treatment::Direct; }

template <>
Treatment paramTreatment<std::string>()
    { return Treatment::String; }

template <typename T>
class Values {
public:
    explicit Values(const T& value)
        : values_(1, value)
    {}

    explicit Values(const ConstRange<T>& range)
    {
        REQUIRE(!range.empty(), "filter expression has an empty values vector");

        auto iter = range.iterator();
        for (const T* valuePtr = iter->next(); valuePtr;
            valuePtr = iter->next()) {
            values_.push_back(*valuePtr);
        }
    }

    size_t size() const { return values_.size(); }

    std::string sqlOne(Context& context) const
    {
        REQUIRE(values_.size() == 1, "wrong operation with multiple values");
        return context.addParam(paramTreatment<T>(), values_.front());
    }

    std::string sqlArray(Context& context) const
    {
        return sqlAll(context, Representation::Array);
    }


    std::string sqlIn(Context& context) const
    {
        if (values_.size() == 1) {
            return "=" + sqlOne(context);
        }
        return " IN " + sqlAll(context, Representation::Set);
    }

    std::string sqlBetween(Context& context) const
    {
        REQUIRE(values_.size() == 2, "two values expected");
        std::ostringstream os;
        os << " BETWEEN "
           << context.addParam(paramTreatment<T>(), values_.front())
           << " AND "
           << context.addParam(paramTreatment<T>(), values_.back());
        return os.str();
    }

    std::set<T> toSet() const { return std::set<T>(values_.begin(), values_.end()); }

    const std::vector<T>& values() const { return values_; }

private:
    enum class Representation { Set, Array };

    std::string sqlAll(Context& context, Representation representation) const
    {
        std::ostringstream os;
        os << (representation == Representation::Array ? "ARRAY[" : "(");
        bool first = true;
        for (const auto& value : values_) {
            if (first) {
                first = false;
            } else {
                os << ',';
            }
            os << context.addParam(paramTreatment<T>(), value);
        }
        os << (representation == Representation::Array ? "]" : ")");
        return os.str();
    }

    std::vector<T> values_;
};

void
checkBinaryExpressions(const FilterExpr* leftExpr, const FilterExpr* rightExpr)
{
    REQUIRE(leftExpr, "invalid left expression");
    REQUIRE(rightExpr, "invalid right expression");
}

void
checkNegativeExpression(const FilterExpr* expr)
{
    REQUIRE(expr, "invalid negative expression");
}

} // namespace

FilterExpr::~FilterExpr() = default;

std::string True::evalQuery(Context&) const
{
    return "TRUE";
}

std::string False::evalQuery(Context&) const
{
    return "FALSE";
}

BinaryFilterExpr::BinaryFilterExpr(
    Operation op, FilterExpr* leftExpr, FilterExpr* rightExpr)
    : op_(op)
    , leftExpr_(leftExpr)
    , rightExpr_(rightExpr)
{
    checkBinaryExpressions(leftExpr, rightExpr);
}

BinaryFilterExpr::BinaryFilterExpr(
    Operation op,
    std::shared_ptr<FilterExpr> leftExpr,
    std::shared_ptr<FilterExpr> rightExpr)
    : op_(op)
{
    checkBinaryExpressions(leftExpr.get(), rightExpr.get());
    leftExpr_.swap(leftExpr);
    rightExpr_.swap(rightExpr);
}


NegativeFilterExpr::NegativeFilterExpr(FilterExpr* expr)
    : expr_(expr)
{
    checkNegativeExpression(expr);
}

NegativeFilterExpr::NegativeFilterExpr(std::shared_ptr<FilterExpr> expr)
{
    checkNegativeExpression(expr.get());
    expr_.swap(expr);
}


std::string
BinaryFilterExpr::evalQuery(Context& context) const
{
    const auto& opStr = op_ == Operation::And ?
        sql::op::LOGICAL_AND :
        sql::op::LOGICAL_OR;
    auto leftQuery = leftExpr_->evalQuery(context);
    auto rightQuery = rightExpr_->evalQuery(context);
    return "((" + leftQuery + ") " + opStr +
        " (" + rightQuery + "))";
}


std::string
NegativeFilterExpr::evalQuery(Context& context) const
{
    return "NOT (" + expr_->evalQuery(context) + ")";
}


class AttrFilterExpr::Impl: public FilterExpr
{
public:
    Impl(
        AttrFilterExpr::Operation op,
        std::string name,
        const std::string& value,
        std::string attrColumn)
        : op_(op)
        , name_(std::move(name))
        , values_(value)
        , attrColumn_(std::move(attrColumn))
    {}

    Impl(
        AttrFilterExpr::Operation op,
        std::string name,
        const ConstRange<std::string>& values,
        std::string attrColumn)
        : op_(op)
        , name_(std::move(name))
        , values_(values)
        , attrColumn_(std::move(attrColumn))
    {
        REQUIRE(!values.empty(), "empty values in attrs filter");
    }

    std::string evalQuery(Context&) const override;

    Operation op() const { return op_; }
    const std::string& name() const { return name_; }
    const std::vector<std::string>& values() const { return values_.values(); }

private:
    AttrFilterExpr::Operation op_;
    std::string name_;
    Values<std::string> values_;
    std::string attrColumn_;
};

std::string
AttrFilterExpr::Impl::evalQuery(Context& context) const
{
    const auto& attrsField = attrColumn_.empty() ? context.attrsField() : attrColumn_;

    switch (op_) {
        case Operation::Defined :
            return attrsField + " ? " + context.quote(name_);

        case Operation::Equal :
            return attrsField + " -> " + context.quote(name_) +
                   " = " + values_.sqlOne(context);

        case Operation::In :
            return attrsField + " -> " + context.quote(name_) +
                   values_.sqlIn(context);

        case Operation::Like :
            return attrsField + " -> " + context.quote(name_) +
                   " LIKE " + values_.sqlOne(context);
    }

    throw maps::LogicError("Unreachable code");
}


AttrFilterExpr::AttrFilterExpr(
    Operation op, const std::string& name, const std::string& value)
    : impl_(new Impl(op, name, value, {}))
{}

AttrFilterExpr::AttrFilterExpr(
    Operation op, const std::string& name, const std::string& value, const std::string& attrColumn)
    : impl_(new Impl(op, name, value, attrColumn))
{}

AttrFilterExpr::AttrFilterExpr(
    Operation op, const std::string& name,
    const ConstRange<std::string>& values)
    : impl_(new Impl(op, name, values, {}))
{}

AttrFilterExpr::AttrFilterExpr(
    Operation op, const std::string& name,
    const ConstRange<std::string>& values,
    const std::string& attrColumn)
    : impl_(new Impl(op, name, values, attrColumn))
{}

AttrFilterExpr::~AttrFilterExpr() = default;

std::string
AttrFilterExpr::evalQuery(Context& context) const
{
    return impl_->evalQuery(context);
}

AttrFilterExpr::Operation AttrFilterExpr::op() const
{
    return impl_->op();
}

const std::string& AttrFilterExpr::name() const
{
    return impl_->name();
}

const std::vector<std::string>& AttrFilterExpr::values() const
{
    return impl_->values();
}


class BigintAttrFilterExpr::Impl : public FilterExpr
{
public:
    Impl(BigintAttrFilterExpr::Operation op, std::string name, int64_t value)
        : op_(op)
        , name_(std::move(name))
        , values_(value)
    {}

    Impl(
        BigintAttrFilterExpr::Operation op,
        std::string name,
        const ConstRange<int64_t>& values)
        : op_(op)
        , name_(std::move(name))
        , values_(values)
    {
        REQUIRE(!values.empty(), "empty values in attrs filter");
    }

    std::string evalQuery(Context&) const override;

    BigintAttrFilterExpr::Operation op() const { return op_; }
    const std::string& name() const { return name_; }
    const std::vector<int64_t>& values() const { return values_.values(); }

private:
    BigintAttrFilterExpr::Operation op_;
    std::string name_;
    Values<int64_t> values_;
};

BigintAttrFilterExpr::BigintAttrFilterExpr(
        Operation op, const std::string& name, int64_t value)
    : impl_(new Impl(op, name, value))
{}

BigintAttrFilterExpr::BigintAttrFilterExpr(
    Operation op, const std::string& name,
    const ConstRange<int64_t>& values)
    : impl_(new Impl(op, name, values))
{}

BigintAttrFilterExpr::~BigintAttrFilterExpr() = default;

std::string
BigintAttrFilterExpr::evalQuery(Context& context) const
{
    return impl_->evalQuery(context);
}

std::string
BigintAttrFilterExpr::Impl::evalQuery(Context& context) const
{
    const auto& attrBigintVal = "("
        + context.attrsField() + "->" + context.quote(name_)
        + ")::bigint";

    switch (op_) {
        case Operation::Less: return attrBigintVal + " < " + values_.sqlOne(context);
        case Operation::LessEqual: return attrBigintVal + " <= " + values_.sqlOne(context);
        case Operation::Equal: return attrBigintVal + " = " + values_.sqlOne(context);
        case Operation::GreaterEqual: return attrBigintVal + " >= " + values_.sqlOne(context);
        case Operation::Greater: return attrBigintVal + " > " + values_.sqlOne(context);
        case Operation::In: return attrBigintVal + values_.sqlIn(context);
    }

    throw maps::LogicError("Unreachable code");
}

BigintAttrFilterExpr::Operation BigintAttrFilterExpr::op() const
{
    return impl_->op();
}

const std::string& BigintAttrFilterExpr::name() const
{
    return impl_->name();
}

const std::vector<int64_t>& BigintAttrFilterExpr::values() const
{
    return impl_->values();
}

class AttrsFilterExpr::Impl: public FilterExpr
{
public:
    Impl(AttrsFilterExpr::DefinedOperation op,
         const ConstRange<std::string>& names,
         std::string attrsColumn)
        : op_(op)
        , names_(names)
        , attrsColumn_(std::move(attrsColumn))
    {
        REQUIRE(!names.empty(), "empty names in attrs filter");
        ASSERT(op_ == DefinedOperation::And || op_ == DefinedOperation::Or);
    }

    std::string evalQuery(Context&) const override;

    DefinedOperation op() const { return op_; }
    const std::vector<std::string>& names() const { return names_.values(); }

private:
    AttrsFilterExpr::DefinedOperation op_;
    Values<std::string> names_;
    std::string attrsColumn_;
};

std::string
AttrsFilterExpr::Impl::evalQuery(Context& context) const
{
    const auto& attrsField = attrsColumn_.empty() ? context.attrsField() : attrsColumn_;

    if (names_.size() == 1) {
        return attrsField + " ? " + names_.sqlOne(context);
    }

    if (op_ == DefinedOperation::And) {
        return attrsField + " ?& " + names_.sqlArray(context);
    } else {
        return attrsField + " ?| " + names_.sqlArray(context);
    }
}

AttrsFilterExpr::AttrsFilterExpr(
    DefinedOperation op, const ConstRange<std::string>& names)
    : impl_(new Impl(op, names, {}))
{}

AttrsFilterExpr::AttrsFilterExpr(
    DefinedOperation op, const ConstRange<std::string>& names, const std::string& attrsColumn)
    : impl_(new Impl(op, names, attrsColumn))
{}

AttrsFilterExpr::~AttrsFilterExpr() = default;

AttrsFilterExpr::DefinedOperation
AttrsFilterExpr::op() const
{
    return impl_->op();
}

const std::vector<std::string>&
AttrsFilterExpr::names() const
{
    return impl_->names();
}

std::string
AttrsFilterExpr::evalQuery(Context& context) const
{
    return impl_->evalQuery(context);
}

class GeomData
{
public:
    GeomData(double lon1, double lat1, double lon2, double lat2)
        : lon1_(lon1)
        , lat1_(lat1)
        , lon2_(lon2)
        , lat2_(lat2)
    {}

    std::string evalGeometry(Context& context) const
    {
        std::stringstream ss;
        ss.precision(std::numeric_limits<double>::max_digits10);

        ss << "ST_SetSRID(ST_MakeBox2D(";
        ss <<  "ST_MakePoint(";
        ss <<   addParam(context, lon1_) << ",";
        ss <<   addParam(context, lat1_) << "),";
        ss <<  "ST_MakePoint(";
        ss <<   addParam(context, lon2_) << ",";
        ss <<   addParam(context, lat2_) << ")),";
        ss << sql::val::GEOMETRY_SRID << ")";

        return ss.str();
    }

private:
    static std::string addParam(Context& context, double value)
    {
        return context.addParam(Treatment::Direct, value);
    }

    double lon1_;
    double lat1_;
    double lon2_;
    double lat2_;
};

GeomFilterExpr::Operation operator & (GeomFilterExpr::Operation op1, GeomFilterExpr::Operation op2)
{
    return GeomFilterExpr::Operation(static_cast<int>(op1) & static_cast<int>(op2));
}

GeomFilterExpr::Operation operator | (GeomFilterExpr::Operation op1, GeomFilterExpr::Operation op2)
{
    return GeomFilterExpr::Operation(static_cast<int>(op1) | static_cast<int>(op2));
}

class GeomFilterExpr::Impl: public FilterExpr
{
public:
    explicit Impl(GeomFilterExpr::Operation op)
        : op_(op)
    {
        const auto intersectsAll =
            Operation::IntersectsPoints |
            Operation::IntersectsLinestrings |
            Operation::IntersectsPolygons;

        auto enabled = [&](Operation op, Operation mask)
        {
            return (op & mask) == mask;
        };

        if (enabled(op, intersectsAll) || enabled(op, Operation::Intersects)) {
            return;
        }

        if (enabled(op, Operation::IntersectsPoints)) {
            geometryTypes_.push_back(GEOMETRY_TYPE_POINT);
        }
        if (enabled(op, Operation::IntersectsLinestrings)) {
            geometryTypes_.push_back(GEOMETRY_TYPE_LINESTRING);
        }
        if (enabled(op, Operation::IntersectsPolygons)) {
            geometryTypes_.push_back(GEOMETRY_TYPE_POLYGON);
        }
    }

    template <typename T>
    void initData(const T& geomData)
    {
        std::unique_ptr<GeomData>(new T(geomData)).swap(data_);
    }

    std::string evalQuery(Context& context) const override
    {
        if (op_ == Operation::Defined) {
            return GEOMETRY_ID_FIELD_DEFINED;
        }

        ASSERT(data_);

        const auto& geomField = sql::col::CONTENTS;
        auto subquery =
            "SELECT " + sql::col::ID +
            " FROM " + sql::table::GEOMETRY +
            " WHERE " + geomField + " && " + data_->evalGeometry(context);
        if (!geometryTypes_.empty()) {
            Values<std::string> types(geometryTypes_);
            subquery += " AND ST_GeometryType(" + geomField + ")" + types.sqlIn(context);
        }

        return "(" + GEOMETRY_ID_FIELD_DEFINED + " AND " +
            sql::alias::OBJECT_REVISION + "." + sql::col::GEOMETRY_ID +
            " IN (" + subquery + "))";
    }

private:
    const GeomFilterExpr::Operation op_;
    std::unique_ptr<GeomData> data_;
    std::vector<std::string> geometryTypes_;
};


GeomFilterExpr::GeomFilterExpr(Operation op)
    : impl_(new Impl(op))
{
    ASSERT(op == Operation::Defined);
}

GeomFilterExpr::GeomFilterExpr(
        Operation op,
        double lon1, double lat1, double lon2, double lat2)
    : impl_(new Impl(op))
{
    ASSERT(op != Operation::Defined);
    impl_->initData(GeomData(lon1, lat1, lon2, lat2));
}

GeomFilterExpr::~GeomFilterExpr() = default;

std::string
GeomFilterExpr::evalQuery(Context& context) const
{
    return impl_->evalQuery(context);
}


class TableAttrFilterExpr::Impl: public FilterExpr
{
public:
    Impl(
        TableAttrFilterExpr::Operation op,
        std::string tableAttr,
        const DBID value)
        : op_(op)
        , tableAttr_(std::move(tableAttr))
        , values_(value)
    {}

    Impl(
        TableAttrFilterExpr::Operation op,
        std::string tableAttr,
        const ConstRange<DBID>& values)
        : op_(op)
        , tableAttr_(std::move(tableAttr))
        , values_(values)
    {}

    std::string evalQuery(Context&) const override;

    Operation op() const { return op_; }
    const std::string& name() const { return tableAttr_; }
    const std::vector<DBID>& values() const { return values_.values(); }

private:
    TableAttrFilterExpr::Operation op_;
    std::string tableAttr_;
    Values<DBID> values_;
};

std::string
TableAttrFilterExpr::Impl::evalQuery(Context& context) const
{
    switch (op_) {
        case Operation::In :
            return QueryGenerator::buildFilterByAttrValues(tableAttr_, values_.toSet());

        case Operation::IsZero :
            return tableAttr_ + " = 0";

        case Operation::NotZero :
            return tableAttr_ + " <> 0";

        case Operation::IsNull :
            return tableAttr_ + " IS NULL";

        case Operation::NotNull :
            return tableAttr_ + " IS NOT NULL";

        case Operation::Equal :
            return tableAttr_ + " = " + values_.sqlOne(context);

        case Operation::NotEqual :
            return tableAttr_ + " <> " + values_.sqlOne(context);

        case Operation::True :
            return tableAttr_;

        case Operation::False :
            return "NOT " + tableAttr_;

        case Operation::Greater :
            return tableAttr_ + " > " + values_.sqlOne(context);

        case Operation::GreaterEqual :
            return tableAttr_ + " >= " + values_.sqlOne(context);

        case Operation::Less :
            return tableAttr_ + " < " + values_.sqlOne(context);

        case Operation::LessEqual :
            return tableAttr_ + " <= " + values_.sqlOne(context);

        case Operation::Between :
            return tableAttr_ + values_.sqlBetween(context);
    }
    throw maps::LogicError("Unreachable code");
}


TableAttrFilterExpr::TableAttrFilterExpr(
    Operation op,
    const std::string& tablePrefix,
    const std::string& name,
    DBID value)
    : impl_(new Impl(op, tablePrefix + name, value))
{}

TableAttrFilterExpr::TableAttrFilterExpr(
    Operation op,
    const std::string& tablePrefix,
    const std::string& name,
    const ConstRange<DBID>& values)
    : impl_(new Impl(op, tablePrefix + name, values))
{}

TableAttrFilterExpr::~TableAttrFilterExpr() = default;

std::string
TableAttrFilterExpr::evalQuery(Context& context) const
{
    return impl_->evalQuery(context);
}

TableAttrFilterExpr::Operation TableAttrFilterExpr::op() const
{
    return impl_->op();
}

const std::string& TableAttrFilterExpr::name() const
{
    return impl_->name();
}

const std::vector<DBID>& TableAttrFilterExpr::values() const
{
    return impl_->values();
}


class TableDateTimeFilterExpr::Impl : public FilterExpr
{
public:
    Impl(
        TableDateTimeFilterExpr::Operation op,
        std::string tableAttr,
        time_t value)
        : op_(op)
        , tableAttr_(std::move(tableAttr))
        , value_(value)
    {}

    std::string evalQuery(Context&) const override;

private:
    TableDateTimeFilterExpr::Operation op_;
    std::string tableAttr_;
    Values<time_t> value_;
};

std::string
TableDateTimeFilterExpr::Impl::evalQuery(Context& context) const
{
    auto timestamp = " to_timestamp(" + value_.sqlOne(context) + ") ";

    switch (op_) {
        case Operation::Equal :
            return tableAttr_ + " = " + timestamp;

        case Operation::NotEqual :
            return tableAttr_ + " <> " + timestamp;

        case Operation::Greater :
            return tableAttr_ + " > " + timestamp;

        case Operation::GreaterEqual :
            return tableAttr_ + " >= " + timestamp;

        case Operation::Less :
            return tableAttr_ + " < " + timestamp;

        case Operation::LessEqual :
            return tableAttr_ + " <= " + timestamp;
    }
    throw maps::LogicError("Unreachable code");
}

TableDateTimeFilterExpr::TableDateTimeFilterExpr(
    Operation op,
    const std::string& tablePrefix,
    const std::string& name,
    time_t value)
    : impl_(new Impl(op, tablePrefix + name, value))
{}

TableDateTimeFilterExpr::~TableDateTimeFilterExpr() = default;

std::string
TableDateTimeFilterExpr::evalQuery(Context& context) const
{
    return impl_->evalQuery(context);
}


ObjRevAttr::ObjRevAttr(const std::string& name)
    : MetaAttr(sql::alias::OBJECT_REVISION + ".", name)
{}

ObjRevAttr
ObjRevAttr::objectId()
{
    return ObjRevAttr(sql::col::OBJECT_ID);
}

ObjRevAttr
ObjRevAttr::commitId()
{
    return ObjRevAttr(sql::col::COMMIT_ID);
}

ObjRevAttr
ObjRevAttr::prevCommitId()
{
    return ObjRevAttr(sql::col::PREV_COMMIT_ID);
}

ObjRevAttr
ObjRevAttr::nextCommitId()
{
    return ObjRevAttr(sql::col::NEXT_COMMIT_ID);
}

ObjRevAttr
ObjRevAttr::masterObjectId()
{
    return ObjRevAttr(sql::col::MASTER_OBJECT_ID);
}

ObjRevAttr
ObjRevAttr::slaveObjectId()
{
    return ObjRevAttr(sql::col::SLAVE_OBJECT_ID);
}

ProxyFilterExpr
ObjRevAttr::isDeleted()
{
    return ObjRevAttr(sql::col::IS_DELETED).isTrue();
}

ProxyFilterExpr
ObjRevAttr::isNotDeleted()
{
    return ObjRevAttr(sql::col::IS_DELETED).isFalse();
}

ProxyFilterExpr
ObjRevAttr::isRegularObject()
{
    return masterObjectId().isZero();
}

ProxyFilterExpr
ObjRevAttr::isNotRegularObject()
{
    return masterObjectId().isNotZero();
}

ProxyFilterExpr
ObjRevAttr::isRelation()
{
    return slaveObjectId().isNotZero();
}

ProxyFilterExpr
ObjRevAttr::isNotRelation()
{
    return slaveObjectId().isZero();
}


class CommitStateExpr : public FilterExpr
{
public:
    enum class Operation {Equal, NotEqual};

    CommitStateExpr(Operation op, const std::string& commitStateStr)
    {
        std::ostringstream os;
        os << "(" << sql::col::STATE << (op == Operation::Equal ? "=" : "!=")
           << "'" << commitStateStr << "')";
        os.str().swap(query_);
    }

    std::string evalQuery(Context&) const override
    {
        return query_;
    }

private:
    std::string query_;
};


CommitAttr::CommitAttr(const std::string& name)
    : MetaAttr(sql::alias::COMMIT + ".", name)
{}

CommitAttr
CommitAttr::id()
{
    return CommitAttr(sql::col::ID);
}

CommitAttr
CommitAttr::stableBranchId()
{
    return CommitAttr(sql::col::STABLE_BRANCH_ID);
}

ProxyFilterExpr
CommitAttr::isDraft()
{
    return CommitStateExpr(CommitStateExpr::Operation::Equal, sql::val::COMMIT_STATE_DRAFT);
}

ProxyFilterExpr
CommitAttr::isApproved()
{
    return CommitStateExpr(CommitStateExpr::Operation::Equal, sql::val::COMMIT_STATE_APPROVED);
}


ProxyFilterExpr
CommitAttr::isTrunk()
{
    return CommitAttr(sql::col::IS_TRUNK).isTrue();
}

ProxyFilterExpr
CommitAttr::isVisible(const Branch& branch)
{
    if (branch.type() == BranchType::Trunk) {
        return isTrunk();
    } else if (branch.type() == BranchType::Approved) {
        return isApproved() && isTrunk();
    } else {
        return (isTrunk() && stableBranchId() < branch.id())
            || (stableBranchId() == branch.id());
    }
}

CommitCreationTime::CommitCreationTime()
    : MetaDateTime(sql::alias::COMMIT + ".", sql::col::CREATED_AT)
{}

class CommitAttribute::Impl
{
public:
    explicit Impl(std::string name)
        : name_(std::move(name)) {}

    AttrFilterExpr defined() const;

    AttrFilterExpr equals(const std::string& value) const;

    AttrFilterExpr operator == (const std::string& value) const;

    AttrFilterExpr in(const ConstRange<std::string>& values) const;

    AttrFilterExpr like(const std::string& value) const;

private:
    std::string name_;
};

CommitAttribute::CommitAttribute(const std::string& name)
    : impl_(new Impl(name))
{}

AttrFilterExpr
CommitAttribute::defined() const
{
    return impl_->defined();
}

AttrFilterExpr
CommitAttribute::Impl::defined() const
{
    return AttrFilterExpr(AttrFilterExpr::Operation::Defined, name_, std::string(),
            COMMIT_ATTRIBUTES);
}

AttrFilterExpr
CommitAttribute::equals(const std::string& value) const
{
    return impl_->equals(value);
}

AttrFilterExpr
CommitAttribute::Impl::equals(const std::string& value) const
{
    return AttrFilterExpr(AttrFilterExpr::Operation::Equal, name_, value,
            COMMIT_ATTRIBUTES);
}

AttrFilterExpr
CommitAttribute::operator == (const std::string& value) const
{
    return impl_->equals(value);
}

AttrFilterExpr
CommitAttribute::Impl::operator == (const std::string& value) const
{
    return equals(value);
}

AttrFilterExpr
CommitAttribute::in(const ConstRange<std::string>& values) const
{
    return impl_->in(values);
}

AttrFilterExpr
CommitAttribute::Impl::in(const ConstRange<std::string>& values) const
{
    return AttrFilterExpr(AttrFilterExpr::Operation::In, name_,
            values, COMMIT_ATTRIBUTES);
}

AttrFilterExpr
CommitAttribute::like(const std::string& value) const
{
    return impl_->like(value);
}

AttrFilterExpr
CommitAttribute::Impl::like(const std::string& value) const
{
    return AttrFilterExpr(AttrFilterExpr::Operation::Like, name_,
            value, COMMIT_ATTRIBUTES);
}

AttrsFilterExpr
CommitAttribute::definedAll(const ConstRange<std::string>& names)
{
    return AttrsFilterExpr(AttrsFilterExpr::DefinedOperation::And, names,
        COMMIT_ATTRIBUTES);
}

AttrsFilterExpr
CommitAttribute::definedAny(const ConstRange<std::string>& names) {
    return AttrsFilterExpr(AttrsFilterExpr::DefinedOperation::Or, names,
        COMMIT_ATTRIBUTES);
}

} // namespace maps::wiki::revision::filters
