#pragma once

#include <iostream>
#include <util/generic/ptr.h>
#include <util/generic/vector.h>
#include <util/generic/string.h>
#include <util/generic/yexception.h>

#include <wmconsole/version3/protos/queries2.pb.h>
#include <wmconsole/version3/protos/querygroups.pb.h>
#include <wmconsole/version3/wmcutil/string.h>

namespace NWebmaster {

#define HAS_TEXT_CONSTRAINT    0x01
#define HAS_FLOAT_CONSTRAINT   0x02

struct TExpressionAND;
struct TExpressionOR;
struct TExpressionNOT;
typedef TExpressionAND TExpressionFILTER;

struct TOperatorLT;
struct TOperatorLE;
struct TOperatorGT;
struct TOperatorGE;
struct TOperatorRE;

struct TIndicatorQuery;
struct TIndicatorUrl;
struct TIndicatorShows;
struct TIndicatorClicks;
struct TIndicatorCTR;
struct TIndicatorAvgShowPos;
struct TIndicatorAvgClickPos;

template<typename T>
struct TTypeId {
    typedef T Type;
};

typedef const TTypeId<TIndicatorQuery>& TIndicatorQueryTId;
typedef const TTypeId<TIndicatorUrl>& TIndicatorUrlTId;
typedef const TTypeId<TIndicatorShows>& TIndicatorShowsTId;
typedef const TTypeId<TIndicatorClicks>& TIndicatorClicksTId;
typedef const TTypeId<TIndicatorCTR>& TIndicatorCTRTId;
typedef const TTypeId<TIndicatorAvgShowPos>& TIndicatorAvgShowPosTId;
typedef const TTypeId<TIndicatorAvgClickPos>& TIndicatorAvgClickPosTId;

struct TData {
    struct TDataInternal {
        TDataInternal(float shows, float clicks, float position)
            : Shows(shows)
            , Clicks(clicks)
            , Position(position)
        {
        }

    public:
        float Shows;
        float Clicks;
        float Position;
    };

    TData(const NWebmaster::proto::queries2::QueryMessage &msg)
        : Query(msg.corrected_query())
        , Url(msg.url())
    {
        ResetDefaultValues();
        LoadNumericData(msg);
        CalcDefaultAvgPosition();
    }

    TData(const NWebmaster::proto::queries2::QueryBatchMessage &msg) {
        ResetDefaultValues();

        if (msg.queries_size() > 0) {
            Query = msg.queries(0).corrected_query();
            Url = msg.queries(0).url();
        }

        for (int i = 0; i < msg.queries_size(); i++) {
            LoadNumericData(msg.queries(i));
        }

        CalcDefaultAvgPosition();
    }

private:
    void ResetDefaultValues() {
        DefaultClicks = 0;
        DefaultShows = 0;
        DefaultCTR = 0;
        DefaultAvgClickPosition = Max<float>();
        DefaultAvgShowPosition = Max<float>();
        DefaultAccClicksPosition = 0;
        DefaultAccShowsPosition = 0;
    }

    void CalcDefaultAvgPosition() {
        if (DefaultClicks > 0) {
            DefaultAvgClickPosition = DefaultAccClicksPosition / DefaultClicks;
        }

        if (DefaultShows > 0) {
            DefaultCTR = DefaultClicks / DefaultShows;
            DefaultAvgShowPosition = DefaultAccShowsPosition / DefaultShows;
        }
    }

    void LoadNumericData(const NWebmaster::proto::queries2::QueryMessage &msg) {
        using namespace NWebmaster::proto::queries2;

        for (int i = 0; i < msg.reports_by_region_size(); i++) {
            const QueryRegionInfo &qri = msg.reports_by_region(i);
            for (int e = 0; e < qri.position_info_size(); e++) {
                const QueryPositionInfo &qpi = qri.position_info(e);

                float c = qpi.clicks_count();
                float s = qpi.shows_count();
                float p = qpi.position();
                DefaultClicks += c;
                DefaultShows += s;
                DefaultAccClicksPosition += p * c;
                DefaultAccShowsPosition += p * s;

                Records.push_back(
                    TDataInternal(s, c, p)
                );
            }
        }
    }

    inline bool CheckPosition(const TDataInternal &data, int fromPosition, int untilPosition) const {
        return (data.Position >= fromPosition && data.Position < untilPosition);
    }

public:
    inline const TString& GetQuery() const {
        if (Query.empty()) {
            ythrow yexception() << "TData::Query is not set";
        }
        return Query;
    }

    inline const TString& GetUrl() const {
        if (Url.empty()) {
            ythrow yexception() << "TData::Url is not set";
        }
        return Url;
    }

    inline float GetClicks() const {
        return DefaultClicks;
    }

    inline float GetShows() const {
        return DefaultShows;
    }

    inline float GetCTR() const {
        return DefaultCTR;
    }

    inline float GetAvgClickPosition() const {
        return DefaultAvgClickPosition;
    }

    inline float GetAvgShowPosition() const {
        return DefaultAvgShowPosition;
    }

    inline float GetClicks(int fromPosition, int untilPosition) const {
        float clicks = 0;

        for (const TDataInternal &data : Records) {
            if (CheckPosition(data, fromPosition, untilPosition)) {
                clicks += data.Clicks;
            }
        }

        return clicks;
    }

    inline float GetShows(int fromPosition, int untilPosition) const {
        float shows = 0;

        for (const TDataInternal &data : Records) {
            if (CheckPosition(data, fromPosition, untilPosition)) {
                shows += data.Shows;
            }
        }

        return shows;
    }

    inline float GetCTR(int fromPosition, int untilPosition) const {
        float shows = 0;
        float clicks = 0;

        for (const TDataInternal &data : Records) {
            if (CheckPosition(data, fromPosition, untilPosition)) {
                shows += data.Shows;
                clicks += data.Clicks;
            }
        }

        if (shows > 0) {
            return clicks / shows;
        }

        return 0;
    }

    inline float GetAvgClickPosition(int fromPosition, int untilPosition) const {
        float clicks = 0;
        float weightAcc = 0;

        for (const TDataInternal &data : Records) {
            if (CheckPosition(data, fromPosition, untilPosition) && data.Clicks > 0) {
                clicks += data.Clicks;
                weightAcc += (data.Clicks * data.Position);
            }
        }

        if (clicks > 0) {
            return weightAcc / clicks;
        }

        return Max<float>();
    }

    inline float GetAvgShowPosition(int fromPosition, int untilPosition) const {
        float shows = 0;
        float weightAcc = 0;

        for (const TDataInternal &data : Records) {
            if (CheckPosition(data, fromPosition, untilPosition) && data.Shows > 0) {
                shows += data.Shows;
                weightAcc += (data.Shows * data.Position);
            }
        }

        if (shows > 0) {
            return weightAcc / shows;
        }

        return Max<float>();
    }

private:
    TString Query;
    TString Url;

    float DefaultClicks;
    float DefaultShows;
    float DefaultCTR;
    float DefaultAvgClickPosition;
    float DefaultAvgShowPosition;
    float DefaultAccClicksPosition;
    float DefaultAccShowsPosition;

    TVector<TDataInternal> Records;
};

template<typename T, typename D>
inline bool Compare(const D&, const D&) {
    throw yexception() << "Not implemented " << Y_FUNC_SIGNATURE;
}

template<>
inline bool Compare<TOperatorRE>(const TString &lhs, const TString &rhs) {
    return lhs.find(rhs) != TString::npos;
}

template<>
inline bool Compare<TOperatorLE>(const float &lhs, const float &rhs) {
    return lhs <= rhs;
}

template<>
inline bool Compare<TOperatorLT>(const float &lhs, const float &rhs) {
    return lhs < rhs;
}

template<>
inline bool Compare<TOperatorGE>(const float &lhs, const float &rhs) {
    return lhs >= rhs;
}

template<>
inline bool Compare<TOperatorGT>(const float &lhs, const float &rhs) {
    return lhs > rhs;
}

static TString Fix(TString str) {
    str = NUtils::ReplaceAll(str, ".*\\Q", "");
    str = NUtils::ReplaceAll(str, "\\E.*", "");
    return str;
}

struct TConstraint {
    TConstraint(const NWebmaster::proto::querygroups::QueryFilter &queryFilter)
        : TextValue(Fix(queryFilter.filter_text()))
        , FloatValue(queryFilter.filter_value())
        , PositionFrom(-1)
        , PositionUntil(Max<int>())
    {
        if (queryFilter.has_indicator_from_position()) {
            PositionFrom = queryFilter.indicator_from_position();
        }

        if (queryFilter.has_indicator_until_position()) {
            PositionUntil = queryFilter.indicator_until_position();
        }
    }

public:
    typedef TSimpleSharedPtr<TConstraint> Ptr;
    virtual bool Accepted(const TData& data) const = 0;
    virtual ~TConstraint() { }

public:
    TString TextValue;
    float FloatValue;
    int PositionFrom;
    int PositionUntil;
};

template<typename TOperator, typename TIndicator>
struct TConstraintImpl : public TConstraint {
    TConstraintImpl(const NWebmaster::proto::querygroups::QueryFilter &queryFilter)
        : TConstraint(queryFilter)
    {
    }

    inline bool Accepted(const TData& data) const override {
        return AcceptedImpl(data, IndicatorTypeId);
    }

private:
    inline bool AcceptedImpl(const TData& data, TIndicatorQueryTId) const {
        return Compare<TOperator>(data.GetQuery(), TextValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorUrlTId) const {
        return Compare<TOperator>(data.GetUrl(), TextValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorShowsTId) const {
        return Compare<TOperator>(data.GetShows(), FloatValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorClicksTId) const {
        return Compare<TOperator>(data.GetClicks(), FloatValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorCTRTId) const {
        return Compare<TOperator>(data.GetCTR(), FloatValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorAvgShowPosTId) const {
        return Compare<TOperator>(data.GetAvgShowPosition(), FloatValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorAvgClickPosTId) const {
        return Compare<TOperator>(data.GetAvgClickPosition(), FloatValue);
    }

private:
    TTypeId<TIndicator> IndicatorTypeId;
};

template<typename TOperator, typename TIndicator>
struct TConstraintBoundedImpl : public TConstraint {
    TConstraintBoundedImpl(const NWebmaster::proto::querygroups::QueryFilter &queryFilter)
        : TConstraint(queryFilter)
    {
    }

    inline bool Accepted(const TData& data) const override {
        return AcceptedImpl(data, IndicatorTypeId);
    }

private:
    inline bool AcceptedImpl(const TData& data, TIndicatorQueryTId) const {
        return Compare<TOperator>(data.GetQuery(), TextValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorUrlTId) const {
        return Compare<TOperator>(data.GetUrl(), TextValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorShowsTId) const {
        return Compare<TOperator>(data.GetShows(PositionFrom, PositionUntil), FloatValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorClicksTId) const {
        return Compare<TOperator>(data.GetClicks(PositionFrom, PositionUntil), FloatValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorCTRTId) const {
        return Compare<TOperator>(data.GetCTR(PositionFrom, PositionUntil), FloatValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorAvgShowPosTId) const {
        return Compare<TOperator>(data.GetAvgShowPosition(PositionFrom, PositionUntil), FloatValue);
    }

    inline bool AcceptedImpl(const TData& data, TIndicatorAvgClickPosTId) const {
        return Compare<TOperator>(data.GetAvgClickPosition(PositionFrom, PositionUntil), FloatValue);
    }

private:
    TTypeId<TIndicator> IndicatorTypeId;
};

struct TFilter {
    typedef TSimpleSharedPtr<TFilter> Ptr;

    TFilter(TConstraint::Ptr constraint)
        : Constraint(constraint)
        , Flags(0)
    {
    }

    virtual ~TFilter() { }
    virtual bool Accepted(const TData &data) = 0;
    virtual void Child(Ptr child) {
        Filter.push_back(child);
    }

    bool HasTextConstraints() const {
        return Flags & HAS_TEXT_CONSTRAINT;
    }

    bool HasFloatConstraints() const {
        return Flags & HAS_FLOAT_CONSTRAINT;
    }

    bool HasBothConstraints() const {
        return HasTextConstraints()
            && HasFloatConstraints();
    }

    bool Empty() const {
        return !Constraint && Filter.empty();
    }

public:
    std::vector<Ptr> Filter;
    TConstraint::Ptr Constraint;
    ui32 Flags;
};

template<typename T>
struct TFilterImpl : public TFilter
{
};

template<>
struct TFilterImpl<TExpressionAND> : public TFilter {
    TFilterImpl(TConstraint::Ptr constraint)
        : TFilter(constraint)
    {
    }

    bool Accepted(const TData &data) override {
        bool result = !Filter.empty(); //default positive

        if (Constraint) {
            result = Constraint->Accepted(data);
        }

        for (Ptr child : Filter) {
            if (child->Empty()) {
                continue;
            }

            result = result && child->Accepted(data);
        }

        return result;
    }
};

template<>
struct TFilterImpl<TExpressionOR> : public TFilter {
    TFilterImpl(TConstraint::Ptr constraint)
        : TFilter(constraint)
    {
    }

    bool Accepted(const TData &data) override {
        bool result = false; //default negative

        if (Constraint) {
            result = Constraint->Accepted(data);
        }

        for (Ptr child : Filter) {
            if (child->Empty()) {
                continue;
            }

            result = result || child->Accepted(data);
        }

        return result;
    }
};

template<>
struct TFilterImpl<TExpressionNOT> : public TFilter {
    TFilterImpl(TConstraint::Ptr constraint)
        : TFilter(constraint)
    {
    }

    bool Accepted(const TData &data) override {
        bool result = !Filter.empty(); //default positive

        if (Constraint) {
            result = !Constraint->Accepted(data);
        }

        for (Ptr child : Filter) {
            if (child->Empty()) {
                continue;
            }

            result = result && !child->Accepted(data);
        }

        return result;
    }
};

TFilter::Ptr BuildTextFilter(const NWebmaster::proto::querygroups::FilterExpression &expression);
TFilter::Ptr BuildFloatFilter(const NWebmaster::proto::querygroups::FilterExpression &expression);
TFilter::Ptr BuildFullFilter(const NWebmaster::proto::querygroups::FilterExpression &expression);

} //namespace NWebmaster
