#pragma once

#include "view_object.h"

#include <memory>

namespace maps {
namespace wiki {
namespace views {

class Condition
{
public:
    virtual ~Condition() = default;

    virtual std::string sqlExpression() const = 0;
};

class OidsCondition : public Condition
{
public:
    explicit OidsCondition(TOIds oids);

    std::string sqlExpression() const override;

private:
    TOIds oids_;
};

class AnyOfDomainAttributesCondition : public Condition
{
public:
    AnyOfDomainAttributesCondition(Transaction& txn, StringSet ids);

    std::string sqlExpression() const override;

protected:
    Transaction& txn_;
    StringSet attrIds_;
};

class DomainAttributeCondition : public Condition
{
public:
    DomainAttributeCondition(Transaction& txn, const std::string& id, const std::string& value);

    std::string sqlExpression() const override;

protected:
    Transaction& txn_;
    std::string id_;
    std::string value_;
};

class NoneOfDomainAttributesCondition : public Condition
{
public:
    NoneOfDomainAttributesCondition(Transaction& txn, StringSet ids);

    std::string sqlExpression() const override;

protected:
    Transaction& txn_;
    StringSet attrIds_;
};

class AllOfServiceAttributesCondition : public Condition
{
public:
    AllOfServiceAttributesCondition(Transaction& txn, StringMap values);

    std::string sqlExpression() const override;

protected:
    Transaction& txn_;
    StringMap attrValues_;
};

class NoneOfServiceAttributesCondition : public Condition
{
public:
    NoneOfServiceAttributesCondition(Transaction& txn, StringSet ids);

    std::string sqlExpression() const override;

protected:
    Transaction& txn_;
    StringSet attrIds_;
};


class CategoriesCondition : public AnyOfDomainAttributesCondition
{
public:
    CategoriesCondition(Transaction& txn, const StringSet& ids);
};

class GeometryCondition : public Condition
{
protected:
    GeometryCondition() = default;
};

class CoveredByGeometryCondition : public GeometryCondition
{
public:
    CoveredByGeometryCondition(Transaction& txn, Geom geom);

    std::string sqlExpression() const override;

private:
    Transaction& txn_;
    Geom geom_;
};

class IntersectsGeometryCondition : public GeometryCondition
{
public:
    IntersectsGeometryCondition(Transaction& txn, Geom geom);

    std::string sqlExpression() const override;

private:
    Transaction& txn_;
    Geom geom_;
};

class WithinGeometryCondition : public GeometryCondition
{
public:
    WithinGeometryCondition(Transaction& txn, Geom geom, double distance);

    std::string sqlExpression() const override;

private:
    Transaction& txn_;
    const Geom geom_;
    const double distance_;
};


class EnvelopeGeometryCondition : public GeometryCondition
{
public:
    explicit EnvelopeGeometryCondition(geos::geom::Envelope envelope)
        : envelope_(std::move(envelope))
    {}

    std::string sqlExpression() const override;

private:
    geos::geom::Envelope envelope_;
};

class GenericSqlCondition : public Condition
{
public:
    explicit GenericSqlCondition(std::string sql);

    std::string sqlExpression() const override { return sql_; }

private:
    std::string sql_;
};


class ObjectsQuery
{
public:
    static const std::string VIEW_TABLE_ALIAS;
    enum class Mode {
        Objects,
        ContourObjects
    };

    ViewObjects exec(
        Transaction& work,
        TBranchId branchId,
        boost::optional<size_t> limit = boost::optional<size_t>()) const;

    template <typename Cond>
    void addCondition(Cond&& cond)
    {
        conds_.emplace_back(new Cond(std::forward<Cond>(cond)));
    }

    void addCondition(std::unique_ptr<Condition> cond)
    {
        conds_.emplace_back(std::move(cond));
    }

    std::string whereClause() const;
    bool hasGeometry() const;

    void setMode(Mode mode) { mode_ = mode; }

private:
    std::vector<std::unique_ptr<Condition>> conds_;
    Mode mode_ = Mode::Objects;
};

} // namespace views
} // namespace wiki
} // namespace maps
