#pragma once

#include <yandex/maps/wiki/revision/branch.h>
#include <yandex/maps/wiki/revision/common.h>
#include <yandex/maps/wiki/revision/range.h>

#include <maps/libs/common/include/exception.h>

#include <ctime>
#include <memory>
#include <type_traits>
#include <utility>

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

class Context;

class FilterExpr
{
public:
    virtual ~FilterExpr();

    virtual std::string evalQuery(Context&) const = 0;
};

// Filter expression that is always true
class True: public FilterExpr
{
    std::string evalQuery(Context&) const override;
};

// Filter expression that is always false
class False: public FilterExpr
{
    std::string evalQuery(Context&) const override;
};

class BinaryFilterExpr: public FilterExpr
{
public:
    enum Operation {And = 0, Or};

    BinaryFilterExpr(Operation op, FilterExpr* leftExpr, FilterExpr* rightExpr);

    BinaryFilterExpr(
        Operation op,
        std::shared_ptr<FilterExpr> leftExpr,
        std::shared_ptr<FilterExpr> rightExpr);

    BinaryFilterExpr(BinaryFilterExpr&& expr) noexcept : op_(expr.op_)
    {
        leftExpr_.swap(expr.leftExpr_);
        rightExpr_.swap(expr.rightExpr_);
    }

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

    Operation op() const { return op_; }
    const std::shared_ptr<FilterExpr>& leftExpr() const { return leftExpr_; }
    const std::shared_ptr<FilterExpr>& rightExpr() const { return rightExpr_; }

private:
    Operation op_;
    std::shared_ptr<FilterExpr> leftExpr_;
    std::shared_ptr<FilterExpr> rightExpr_;
};


class NegativeFilterExpr: public FilterExpr
{
public:
    explicit NegativeFilterExpr(FilterExpr* expr);
    explicit NegativeFilterExpr(std::shared_ptr<FilterExpr> expr);
    NegativeFilterExpr(NegativeFilterExpr&& expr) noexcept { expr_.swap(expr.expr_); }

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

    const std::shared_ptr<FilterExpr>& expr() const { return expr_; }

private:
    std::shared_ptr<FilterExpr> expr_;
};


class AttrFilterExpr: public FilterExpr
{
public:
    enum Operation {Defined = 0, Equal, In, Like};

    AttrFilterExpr(Operation op, const std::string& name, const std::string& value);
    AttrFilterExpr(Operation op, const std::string& name, const std::string& value, const std::string& attrColumn);

    AttrFilterExpr(
        Operation op,
        const std::string& name,
        const ConstRange<std::string>& values);

    AttrFilterExpr(
        Operation op,
        const std::string& name,
        const ConstRange<std::string>& values,
        const std::string& attrColumn);

    virtual ~AttrFilterExpr();

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

    Operation op() const;
    const std::string& name() const;
    const std::vector<std::string>& values() const;

private:
    class Impl;
    std::shared_ptr<Impl> impl_;
};

class BigintAttrFilterExpr: public FilterExpr
{
public:
    enum Operation {Less, LessEqual, Equal, GreaterEqual, Greater, In};

    BigintAttrFilterExpr(Operation op, const std::string& name, int64_t value);

    BigintAttrFilterExpr(
        Operation op,
        const std::string& name,
        const ConstRange<int64_t>& values);

    virtual ~BigintAttrFilterExpr();

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

    Operation op() const;
    const std::string& name() const;
    const std::vector<int64_t>& values() const;

private:
    class Impl;
    std::shared_ptr<Impl> impl_;
};

class AttrsFilterExpr: public FilterExpr
{
public:
    enum class DefinedOperation {And = 0, Or};

    AttrsFilterExpr(DefinedOperation op, const ConstRange<std::string>& names);
    AttrsFilterExpr(DefinedOperation op, const ConstRange<std::string>& names, const std::string& attrsColumn);

    virtual ~AttrsFilterExpr();

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

    DefinedOperation op() const;
    const std::vector<std::string>& names() const;

private:
    class Impl;
    std::shared_ptr<Impl> impl_;
};


class GeomFilterExpr: public FilterExpr
{
public:
    enum class Operation
    {
        Defined = 0,
        Intersects = 1,
        IntersectsPoints =      1 << 1,
        IntersectsLinestrings = 1 << 2,
        IntersectsPolygons =    1 << 3
    };

    explicit GeomFilterExpr(Operation op);

    GeomFilterExpr(Operation op, double lon1, double lat1, double lon2, double lat2);

    virtual ~GeomFilterExpr();

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

private:
    class Impl;
    std::shared_ptr<Impl> impl_;
};

GeomFilterExpr::Operation operator & (GeomFilterExpr::Operation op1, GeomFilterExpr::Operation op2);
GeomFilterExpr::Operation operator | (GeomFilterExpr::Operation op1, GeomFilterExpr::Operation op2);

class TableAttrFilterExpr: public FilterExpr
{
public:
    enum Operation {In, IsNull, NotNull, Equal, NotEqual, True, False,
                    Greater, GreaterEqual, Less, LessEqual,
                    IsZero, NotZero, Between};

    TableAttrFilterExpr(
        Operation op,
        const std::string& tablePrefix,
        const std::string& name,
        DBID value = 0);

    TableAttrFilterExpr(
        Operation op,
        const std::string& tablePrefix,
        const std::string& name,
        const ConstRange<DBID>& values);


    virtual ~TableAttrFilterExpr();

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

    Operation op() const;
    const std::string& name() const;
    const std::vector<DBID>& values() const;

private:
    class Impl;
    std::shared_ptr<Impl> impl_;
};


class TableDateTimeFilterExpr: public FilterExpr
{
public:
    enum Operation {Equal, NotEqual,
                    Greater, GreaterEqual, Less, LessEqual};

    TableDateTimeFilterExpr(
        Operation op,
        const std::string& tablePrefix,
        const std::string& name,
        time_t value);

    virtual ~TableDateTimeFilterExpr();

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

private:
    class Impl;
    std::shared_ptr<Impl> impl_;
};


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

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

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

    BigintAttrFilterExpr equals(int64_t value) const {
        return BigintAttrFilterExpr(
                BigintAttrFilterExpr::Operation::Equal, name_, value);
    }

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

    BigintAttrFilterExpr operator == (int64_t value) const {
        return equals(value);
    }

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

    BigintAttrFilterExpr in(const ConstRange<int64_t>& values) const {
        return BigintAttrFilterExpr(BigintAttrFilterExpr::Operation::In, name_, values);
    }

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

    BigintAttrFilterExpr operator < (int64_t value) const {
        return BigintAttrFilterExpr(
                BigintAttrFilterExpr::Operation::Less, name_, value);
    }

    BigintAttrFilterExpr operator <= (int64_t value) const {
        return BigintAttrFilterExpr(
                BigintAttrFilterExpr::Operation::LessEqual, name_, value);
    }

    BigintAttrFilterExpr operator >= (int64_t value) const {
        return BigintAttrFilterExpr(
                BigintAttrFilterExpr::Operation::GreaterEqual, name_, value);
    }

    BigintAttrFilterExpr operator > (int64_t value) const {
        return BigintAttrFilterExpr(
                BigintAttrFilterExpr::Operation::Greater, name_, value);
    }

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

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

private:
    std::string name_;
};

class CommitAttribute
{
public:
    explicit CommitAttribute(const std::string& 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;

    static AttrsFilterExpr definedAll(const ConstRange<std::string>& names);

    static AttrsFilterExpr definedAny(const ConstRange<std::string>& names);

private:
    class Impl;
    std::shared_ptr<Impl> impl_;

};


class Geom
{
public:
    static GeomFilterExpr defined()
    {
        return GeomFilterExpr(GeomFilterExpr::Operation::Defined);
    }

    static GeomFilterExpr intersects(
            GeomFilterExpr::Operation operation,
            double lon1, double lat1,
            double lon2, double lat2)
    {
        REQUIRE(operation != GeomFilterExpr::Operation::Defined, "Bad intersection type");

        return GeomFilterExpr(operation, lon1, lat1, lon2, lat2);
    }

    static GeomFilterExpr intersects(
            double lon1, double lat1,
            double lon2, double lat2)
    {
        return intersects(GeomFilterExpr::Operation::Intersects, lon1, lat1, lon2, lat2);
    }

    static GeomFilterExpr intersectsPoints(
            double lon1, double lat1,
            double lon2, double lat2)
    {
        return intersects(GeomFilterExpr::Operation::IntersectsPoints, lon1, lat1, lon2, lat2);
    }

    static GeomFilterExpr intersectsLinestrings(
            double lon1, double lat1,
            double lon2, double lat2)
    {
        return intersects(GeomFilterExpr::Operation::IntersectsLinestrings, lon1, lat1, lon2, lat2);
    }

    static GeomFilterExpr intersectsPolygons(
            double lon1, double lat1,
            double lon2, double lat2)
    {
        return intersects(GeomFilterExpr::Operation::IntersectsPolygons, lon1, lat1, lon2, lat2);
    }
};


class MetaAttr
{
public:
    MetaAttr(std::string tablePrefix, std::string name)
        : tablePrefix_(std::move(tablePrefix))
        , name_(std::move(name))
    {}

    TableAttrFilterExpr isZero() const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::IsZero, tablePrefix_, name_);
    }

    TableAttrFilterExpr isNotZero() const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::NotZero, tablePrefix_, name_);
    }

    TableAttrFilterExpr in(const ConstRange<DBID>& values) const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::In, tablePrefix_, name_, values);
    }

    TableAttrFilterExpr isNull() const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::IsNull, tablePrefix_, name_);
    }

    TableAttrFilterExpr isNotNull() const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::NotNull, tablePrefix_, name_);
    }

    TableAttrFilterExpr isTrue() const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::True, tablePrefix_, name_);
    }

    TableAttrFilterExpr isFalse() const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::False, tablePrefix_, name_);
    }

    TableAttrFilterExpr operator == (DBID value) const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::Equal, tablePrefix_, name_, value);
    }

    TableAttrFilterExpr operator != (DBID value) const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::NotEqual, tablePrefix_, name_, value);
    }

    TableAttrFilterExpr operator < (DBID value) const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::Less, tablePrefix_, name_, value);
    }

    TableAttrFilterExpr operator <= (DBID value) const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::LessEqual, tablePrefix_, name_, value);
    }

    TableAttrFilterExpr operator > (DBID value) const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::Greater, tablePrefix_, name_, value);
    }

    TableAttrFilterExpr operator >= (DBID value) const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::GreaterEqual, tablePrefix_, name_, value);
    }

    TableAttrFilterExpr between(const ConstRange<DBID>& values) const
    {
        return TableAttrFilterExpr(
            TableAttrFilterExpr::Operation::Between, tablePrefix_, name_, values);
    }

private:
    std::string tablePrefix_;
    std::string name_;
};


class MetaDateTime
{
public:
    MetaDateTime(std::string tablePrefix, std::string name)
        : tablePrefix_(std::move(tablePrefix))
        , name_(std::move(name))
    {}

    TableDateTimeFilterExpr operator == (time_t value)
    {
        return TableDateTimeFilterExpr(
            TableDateTimeFilterExpr::Operation::Equal, tablePrefix_, name_, value);
    }

    TableDateTimeFilterExpr operator != (time_t value)
    {
        return TableDateTimeFilterExpr(
            TableDateTimeFilterExpr::Operation::NotEqual, tablePrefix_, name_, value);
    }

    TableDateTimeFilterExpr operator < (time_t value)
    {
        return TableDateTimeFilterExpr(
            TableDateTimeFilterExpr::Operation::Less, tablePrefix_, name_, value);
    }

    TableDateTimeFilterExpr operator <= (time_t value)
    {
        return TableDateTimeFilterExpr(
            TableDateTimeFilterExpr::Operation::LessEqual, tablePrefix_, name_, value);
    }

    TableDateTimeFilterExpr operator > (time_t value)
    {
        return TableDateTimeFilterExpr(
            TableDateTimeFilterExpr::Operation::Greater, tablePrefix_, name_, value);
    }

    TableDateTimeFilterExpr operator >= (time_t value)
    {
        return TableDateTimeFilterExpr(
            TableDateTimeFilterExpr::Operation::GreaterEqual, tablePrefix_, name_, value);
    }

private:
    std::string tablePrefix_;
    std::string name_;
};


template <typename ExpA, typename ExpB>
typename std::enable_if<std::is_base_of<FilterExpr, ExpA>::value,
    typename std::enable_if<std::is_base_of<FilterExpr, ExpB>::value,
        BinaryFilterExpr>::type>::type operator&& (ExpA expA, ExpB expB)
{
    return BinaryFilterExpr(
        BinaryFilterExpr::Operation::And,
        new ExpA(std::move(expA)), new ExpB(std::move(expB)));
}


template <typename ExpA, typename ExpB>
typename std::enable_if<std::is_base_of<FilterExpr, ExpA>::value,
    typename std::enable_if<std::is_base_of<FilterExpr, ExpB>::value,
        BinaryFilterExpr>::type>::type operator|| (ExpA expA, ExpB expB)
{
    return BinaryFilterExpr(
        BinaryFilterExpr::Operation::Or,
        new ExpA(std::move(expA)), new ExpB(std::move(expB)));
}


template <typename Expr>
typename std::enable_if<std::is_base_of<FilterExpr, Expr>::value,
    NegativeFilterExpr>::type operator! (Expr expr)
{
    return NegativeFilterExpr(new Expr(std::move(expr)));
}

// Proxy wrapper for any const reference filter
// Example.
// Input: const FilterExpr& filter
// Using: auto invFilter = !ProxyReferenceFilterExpr(filter);
//
class ProxyReferenceFilterExpr : public FilterExpr
{
public:
    explicit ProxyReferenceFilterExpr(const FilterExpr& filter)
        : filter_(filter)
    {}

    std::string evalQuery(Context& context) const override
    {
        return filter_.evalQuery(context);
    }

    const FilterExpr& filter() const { return filter_; }

private:
    const FilterExpr& filter_;
};

class ProxyFilterExpr : public FilterExpr
{
public:
    explicit ProxyFilterExpr(std::shared_ptr<FilterExpr> filter)
        : filter_(std::move(filter))
    {}

    template <typename Expr,
        typename = typename std::enable_if<
            std::is_base_of<FilterExpr, Expr>::value>::type>
    ProxyFilterExpr(Expr expr)
        : filter_(new Expr(std::move(expr)))
    {}

    template <typename Expr,
        typename = typename std::enable_if<
            std::is_base_of<FilterExpr, Expr>::value>::type>
    void operator &= (Expr expr)
    {
        filter_ = std::make_shared<BinaryFilterExpr>(
            BinaryFilterExpr::Operation::And,
            filter_,
            std::make_shared<Expr>(std::move(expr)));
    }

    template <typename Expr,
        typename = typename std::enable_if<
            std::is_base_of<FilterExpr, Expr>::value>::type>
    void operator |= (Expr expr)
    {
        filter_ = std::make_shared<BinaryFilterExpr>(
           BinaryFilterExpr::Operation::Or,
           filter_,
           std::make_shared<Expr>(std::move(expr)));
    }

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

    const std::shared_ptr<FilterExpr>& filter() const { return filter_; }

private:
    std::shared_ptr<FilterExpr> filter_;
};

class ObjRevAttr: public MetaAttr
{
public:
    explicit ObjRevAttr(const std::string& name);

    static ObjRevAttr objectId();
    static ObjRevAttr commitId();
    static ObjRevAttr prevCommitId();
    static ObjRevAttr nextCommitId();

    static ObjRevAttr masterObjectId();
    static ObjRevAttr slaveObjectId();

    static ProxyFilterExpr isDeleted();
    static ProxyFilterExpr isNotDeleted();

    static ProxyFilterExpr isRegularObject();
    static ProxyFilterExpr isNotRegularObject();

    static ProxyFilterExpr isRelation();
    static ProxyFilterExpr isNotRelation();
};

class CommitAttr: public MetaAttr
{
public:
    explicit CommitAttr(const std::string& name);

    static CommitAttr id();

    static CommitAttr stableBranchId();

    static ProxyFilterExpr isDraft();
    static ProxyFilterExpr isApproved();
    static ProxyFilterExpr isTrunk();
    static ProxyFilterExpr isVisible(const Branch&);
};

class CommitCreationTime : public MetaDateTime
{
public:
    CommitCreationTime();
};

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