#include <boost/algorithm/string/join.hpp>
#include <mbgl/style/expression/collator.hpp>
#include <mbgl/style/expression/compound_expression.hpp>
#include <mbgl/style/expression/check_subtype.hpp>
#include <mbgl/style/expression/util.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
#include <mbgl/math/log2.hpp>
#include <mbgl/util/i18n.hpp>
#include <mbgl/util/ignore.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/platform.hpp>
#include <mbgl/util/variant.hpp>

#include <mapbox/eternal.hpp>

#include <cmath>
#include <limits>

namespace mbgl {
namespace style {
namespace expression {

/*
    Represents the parameter list for an expression that takes an arbitrary
    number of arguments (of a specific type).
*/
struct VarargsType { type::Type type; };

bool operator==(const VarargsType& lhs, const VarargsType& rhs) {
    return lhs.type == rhs.type;
}

template <typename T>
struct Varargs : std::vector<T> {
    template <class... Args>
    explicit Varargs(Args&&... args) : std::vector<T>(std::forward<Args>(args)...) {}
};

namespace detail {
// Base class for the Signature<Fn> structs that are used to determine
// each CompoundExpression definition's type::Type data from the type of its
// "evaluate" function.
struct SignatureBase {
    using Args = std::vector<std::unique_ptr<Expression>>;

    SignatureBase(type::Type result_, variant<std::vector<type::Type>, VarargsType> params_, std::string name_) :
        result(std::move(result_)),
        params(std::move(params_)),
        name(std::move(name_))
    {}
    virtual ~SignatureBase() = default;

    virtual EvaluationResult apply(const EvaluationContext&, const Args&) const = 0;

    type::Type result;
    variant<std::vector<type::Type>, VarargsType> params;
    std::string name;
};

/*
    The Signature<Fn> structs are wrappers around an "evaluate()" function whose
    purpose is to extract the necessary Type data from the evaluate function's
    type.  There are three key (partial) specializations:

    Signature<R (Params...)>:
    Wraps a simple evaluate function (const T0&, const T1&, ...) -> Result<U>

    Signature<R (const Varargs<T>&)>:
    Wraps an evaluate function that takes an arbitrary number of arguments (via
    a Varargs<T>, which is just an alias for std::vector).

    Signature<R (const EvaluationContext&, Params...)>:
    Wraps an evaluate function that needs to access the expression evaluation
    parameters in addition to its subexpressions, i.e.,
    (const EvaluationParams&, const T0&, const T1&, ...) -> Result<U>.  Needed
    for expressions like ["zoom"], ["get", key], etc.

    In each of the above evaluate signatures, T0, T1, etc. are the types of
    the successfully evaluated subexpressions.
*/
template <class, class Enable = void>
struct Signature;

// Simple evaluate function (const T0&, const T1&, ...) -> Result<U>
template <class R, class... Params>
struct Signature<R (Params...)> : SignatureBase {
    Signature(R (*evaluate_)(Params...), const std::string& name_)
        : SignatureBase(valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
                        std::vector<type::Type>{valueTypeToExpressionType<std::decay_t<Params>>()...},
                        name_),
          evaluate(evaluate_) {}

    EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const override {
        return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{});
    }

    R (*evaluate)(Params...);

private:
    template <std::size_t ...I>
    EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const {
        std::array<Value, sizeof...(Params)> evaluated;
        for (std::size_t i = 0; i < sizeof...(Params); ++i) {
            const EvaluationResult evaluatedArg = args.at(i)->evaluate(evaluationParameters);
            if (!evaluatedArg) return evaluatedArg.error();
            evaluated[i] = *evaluatedArg;
        }
        const R value = evaluate(*fromExpressionValue<std::decay_t<Params>>(evaluated[I])...);
        if (!value) return value.error();
        return *value;
    }
};

// Varargs evaluate function (const Varargs<T>&) -> Result<U>
template <class R, typename T>
struct Signature<R (const Varargs<T>&)> : SignatureBase {
    Signature(R (*evaluate_)(const Varargs<T>&), const std::string& name_)
        : SignatureBase(valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
                        VarargsType{valueTypeToExpressionType<T>()},
                        name_),
          evaluate(evaluate_) {}

    EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const override {
        Varargs<T> evaluated;
        evaluated.reserve(args.size());
        for (const auto& arg : args) {
            const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters);
            if(!evaluatedArg) return evaluatedArg.error();
            evaluated.push_back(*fromExpressionValue<std::decay_t<T>>(*evaluatedArg));
        }
        const R value = evaluate(evaluated);
        if (!value) return value.error();
        return *value;
    }

    R (*evaluate)(const Varargs<T>&);
};

// Evaluate function needing parameter access,
// (const EvaluationParams&, const T0&, const T1&, ...) -> Result<U>
template <class R, class... Params>
struct Signature<R (const EvaluationContext&, Params...)> : SignatureBase {
    Signature(R (*evaluate_)(const EvaluationContext&, Params...), const std::string& name_)
        : SignatureBase(valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
                        std::vector<type::Type>{valueTypeToExpressionType<std::decay_t<Params>>()...},
                        name_),
          evaluate(evaluate_) {}

    EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const override {
        return applyImpl(evaluationParameters, args, std::index_sequence_for<Params...>{});
    }

private:
    template <std::size_t ...I>
    EvaluationResult applyImpl(const EvaluationContext& evaluationParameters, const Args& args, std::index_sequence<I...>) const {
        std::array<Value, sizeof...(Params)> evaluated;
        for (std::size_t i = 0; i < sizeof...(Params); ++i) {
            const EvaluationResult evaluatedArg = args.at(i)->evaluate(evaluationParameters);
            if (!evaluatedArg) return evaluatedArg.error();
            evaluated[i] = *evaluatedArg;
        }
        const R value = evaluate(evaluationParameters, *fromExpressionValue<std::decay_t<Params>>(evaluated[I])...);
        if (!value) return value.error();
        return *value;
    }

    R (*evaluate)(const EvaluationContext&, Params...);
};

// Evaluate function needing EvaluationContext and Varargs
// (const EvaluationContext&, const Varargs<T>&) -> Result<U>
template <class R, typename T>
struct Signature<R (const EvaluationContext&, const Varargs<T>&)> : SignatureBase {
    Signature(R (*evaluate_)(const EvaluationContext&, const Varargs<T>&), const std::string& name_)
        : SignatureBase(valueTypeToExpressionType<std::decay_t<typename R::Value>>(),
                        VarargsType{valueTypeToExpressionType<T>()},
                        name_),
          evaluate(evaluate_) {}

    EvaluationResult apply(const EvaluationContext& evaluationParameters, const Args& args) const override {
        Varargs<T> evaluated;
        evaluated.reserve(args.size());
        for (const auto& arg : args) {
            const EvaluationResult evaluatedArg = arg->evaluate(evaluationParameters);
            if(!evaluatedArg) return evaluatedArg.error();
            evaluated.push_back(*fromExpressionValue<std::decay_t<T>>(*evaluatedArg));
        }
        const R value = evaluate(evaluationParameters, evaluated);
        if (!value) return value.error();
        return *value;
    }

    R (*evaluate)(const EvaluationContext&, const Varargs<T>&);
};

// Machinery to pull out function type from lambdas.
template <class, class = void>
struct SignatureType;

template <class T, class R, class... Params>
struct SignatureType<R (T::*)(Params...) const>
{ using Type = R (Params...);  };

template <class Lambda>
struct SignatureType<Lambda, std::enable_if_t<std::is_class<Lambda>::value>>
{ using Type = typename SignatureType<decltype(&Lambda::operator())>::Type; };

template <typename Fn>
static std::unique_ptr<detail::SignatureBase> makeSignature(std::string name, Fn evaluateFunction) {
    return std::make_unique<Signature<typename SignatureType<Fn>::Type>>(evaluateFunction, std::move(name));
}

} // namespace detail

Value featureIdAsExpressionValue(const EvaluationContext& params) {
    assert(params.feature);
    auto id = params.feature->getID();
    if (id.is<NullValue>()) return Null;
    return id.match([](const auto& idid) {
        return toExpressionValue(mbgl::Value(idid));
    });
};

optional<Value> featurePropertyAsExpressionValue(const EvaluationContext& params, const std::string& key) {
    assert(params.feature);
    auto property = params.feature->getValue(key);
    return property ? toExpressionValue(*property) : optional<Value>();
};

optional<std::string> featureTypeAsString(FeatureType type) {
    switch(type) {
    case FeatureType::Point:
        return optional<std::string>("Point");
    case FeatureType::LineString:
        return optional<std::string>("LineString");
    case FeatureType::Polygon:
        return optional<std::string>("Polygon");
    case FeatureType::Unknown:
        return optional<std::string>("Unknown");
    default:
        return {};
    }
};

optional<double> featurePropertyAsDouble(const EvaluationContext& params, const std::string& key) {
    assert(params.feature);
    auto property = params.feature->getValue(key);
    if (!property) return {};
    return property->match([](double value) { return value; },
                           [](uint64_t value) -> optional<double> { return {static_cast<double>(value)}; },
                           [](int64_t value) -> optional<double> { return {static_cast<double>(value)}; },
                           [](const auto&) -> optional<double> { return {}; });
};

optional<std::string> featurePropertyAsString(const EvaluationContext& params, const std::string& key) {
    assert(params.feature);
    auto property = params.feature->getValue(key);
    if (!property) return {};
    return property->match([](std::string value) { return value; },
                           [](const auto&) { return optional<std::string>(); });
};

optional<double> featureIdAsDouble(const EvaluationContext& params) {
    assert(params.feature);
    auto id = params.feature->getID();
    return id.match([](double value) { return value; },
                    [](uint64_t value) -> optional<double> { return {static_cast<double>(value)}; },
                    [](int64_t value) -> optional<double> { return {static_cast<double>(value)}; },
                    [](const auto&) -> optional<double> { return {}; });
};

optional<std::string> featureIdAsString(const EvaluationContext& params) {
    assert(params.feature);
    auto id = params.feature->getID();
    return id.match([](std::string value) { return value; }, [](const auto&) { return optional<std::string>(); });
};

const auto& eCompoundExpression() {
    static auto signature = detail::makeSignature("e", []() -> Result<double> { return 2.718281828459045; });
    return signature;
}

const auto& piCompoundExpression() {
    static auto signature = detail::makeSignature("pi", []() -> Result<double> { return 3.141592653589793; });
    return signature;
}

const auto& ln2CompoundExpression() {
    static auto signature = detail::makeSignature("ln2", []() -> Result<double> { return 0.6931471805599453; });
    return signature;
}

const auto& typeofCompoundExpression() {
    static auto signature = detail::makeSignature("typeof", [](const Value& v) -> Result<std::string> { return toString(typeOf(v)); });
    return signature;
}

const auto& toRgbaCompoundExpression() {
    static auto signature = detail::makeSignature("to-rgba", [](const Color& color) -> Result<std::array<double, 4>> {
        return color.toArray();
    });
    return signature;
}

const auto& rgbaCompoundExpression() {
    static auto signature = detail::makeSignature("rgba", [](double r, double g, double b, double a) { return rgba(r, g, b, a); });
    return signature;
}

const auto& rgbCompoundExpression() {
    static auto signature = detail::makeSignature("rgb", [](double r, double g, double b) { return rgba(r, g, b, 1.0f); });
    return signature;
}

const auto& zoomCompoundExpression() {
    static auto signature = detail::makeSignature("zoom", [](const EvaluationContext& params) -> Result<double> {
        if (!params.zoom) {
            return EvaluationError {
                "The 'zoom' expression is unavailable in the current evaluation context."
            };
        }
        return *(params.zoom);
    });
    return signature;
}

const auto& heatmapDensityCompoundExpression() {
    static auto signature = detail::makeSignature("heatmap-density", [](const EvaluationContext& params) -> Result<double> {
        if (!params.colorRampParameter) {
            return EvaluationError {
                "The 'heatmap-density' expression is unavailable in the current evaluation context."
            };
        }
        return *(params.colorRampParameter);
    });
    return signature;
}

const auto& lineProgressCompoundExpression() {
    static auto signature = detail::makeSignature("line-progress", [](const EvaluationContext& params) -> Result<double> {
        if (!params.colorRampParameter) {
            return EvaluationError {
                "The 'line-progress' expression is unavailable in the current evaluation context."
            };
        }
        return *(params.colorRampParameter);
    });
    return signature;
}

const auto& accumulatedCompoundExpression() {
    const static auto signature = detail::makeSignature("accumulated", [](const EvaluationContext& params) -> Result<Value> {
        if (!params.accumulated) {
            return EvaluationError {
                "The 'accumulated' expression is unavailable in the current evaluation context."
            };
        }
        return Value(toExpressionValue(*params.accumulated));
    });
    return signature;
}

const auto& hasContextCompoundExpression() {
    static auto signature = detail::makeSignature("has", [](const EvaluationContext& params, const std::string& key) -> Result<bool> {
        if (!params.feature) {
            return EvaluationError {
                "Feature data is unavailable in the current evaluation context."
            };
        }

        return static_cast<bool>(params.feature->getValue(key));
    });
    return signature;
}

const auto& hasObjectCompoundExpression() {
    static auto signature = detail::makeSignature("has", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<bool> {
        return object.find(key) != object.end();
    });
    return signature;
}

const auto& getContextCompoundExpression() {
    static auto signature = detail::makeSignature("get", [](const EvaluationContext& params, const std::string& key) -> Result<Value> {
        if (!params.feature) {
            return EvaluationError {
                "Feature data is unavailable in the current evaluation context."
            };
        }

        auto propertyValue = params.feature->getValue(key);
        if (!propertyValue) {
            return Null;
        }
        return Value(toExpressionValue(*propertyValue));
    });
    return signature;
}

const auto& getObjectCompoundExpression() {
    static auto signature = detail::makeSignature("get", [](const std::string& key, const std::unordered_map<std::string, Value>& object) -> Result<Value> {
        if (object.find(key) == object.end()) {
            return Null;
        }
        return object.at(key);
    });
    return signature;
}

const auto& propertiesCompoundExpression() {
    static auto signature = detail::makeSignature("properties", [](const EvaluationContext& params) -> Result<std::unordered_map<std::string, Value>> {
        if (!params.feature) {
            return EvaluationError {
                "Feature data is unavailable in the current evaluation context."
            };
        }
        std::unordered_map<std::string, Value> result;
        const PropertyMap& properties = params.feature->getProperties();
        result.reserve(properties.size());
        for (const auto& entry : properties) {
            result[entry.first] = toExpressionValue(entry.second);
        }
        return result;
    });
    return signature;
}

const auto& geometryTypeCompoundExpression() {
    static auto signature = detail::makeSignature("geometry-type", [](const EvaluationContext& params) -> Result<std::string> {
        if (!params.feature) {
            return EvaluationError {
                "Feature data is unavailable in the current evaluation context."
            };
        }

        auto type = params.feature->getType();
        if (type == FeatureType::Point) {
            return "Point";
        } else if (type == FeatureType::LineString) {
            return "LineString";
        } else if (type == FeatureType::Polygon) {
            return "Polygon";
        } else {
            return "Unknown";
        }
    });
    return signature;
}

const auto& idCompoundExpression() {
    static auto signature = detail::makeSignature("id", [](const EvaluationContext& params) -> Result<Value> {
        if (!params.feature) {
            return EvaluationError {
                "Feature data is unavailable in the current evaluation context."
            };
        }

        auto id = params.feature->getID();
        return id.match(
            [](const auto& idValue) {
                return toExpressionValue(mbgl::Value(idValue));
            }
        );
    });
    return signature;
}

const auto& plusCompoundExpression() {
    static auto signature = detail::makeSignature("+", [](const Varargs<double>& args) -> Result<double> {
        double sum = 0.0f;
        for (auto arg : args) {
            sum += arg;
        }
        return sum;
    });
    return signature;
}

const auto& minusCompoundExpression() {
    static auto signature = detail::makeSignature("-", [](double a, double b) -> Result<double> { return a - b; });
    return signature;
}

const auto& negateCompoundExpression() {
    static auto signature = detail::makeSignature("-", [](double a) -> Result<double> { return -a; });
    return signature;
}

const auto& multiplyCompoundExpression() {
    static auto signature = detail::makeSignature("*", [](const Varargs<double>& args) -> Result<double> {
        double prod = 1.0f;
        for (auto arg : args) {
            prod *= arg;
        }
        return prod;
    });
    return signature;
}

const auto& divideCompoundExpression() {
    static auto signature = detail::makeSignature("/", [](double a, double b) -> Result<double> {
        if (b == 0) {
            if (a == 0) return std::numeric_limits<double>::quiet_NaN();
            double inf = std::numeric_limits<double>::infinity();
            if (a > 0) return inf;
            if (a < 0) return -inf;
        }
        return a / b;
    });
    return signature;
}

const auto& modCompoundExpression() {
    static auto signature = detail::makeSignature("%", [](double a, double b) -> Result<double> { return fmod(a, b); });
    return signature;
}

const auto& powCompoundExpression() {
    static auto signature = detail::makeSignature("^", [](double a, double b) -> Result<double> { return pow(a, b); });
    return signature;
}

const auto& sqrtCompoundExpression() {
    static auto signature = detail::makeSignature("sqrt", [](double x) -> Result<double> { return sqrt(x); });
    return signature;
}

const auto& log10CompoundExpression() {
    static auto signature = detail::makeSignature("log10", [](double x) -> Result<double> { return log10(x); });
    return signature;
}

const auto& lnCompoundExpression() {
    static auto signature = detail::makeSignature("ln", [](double x) -> Result<double> { return log(x); });
    return signature;
}

const auto& log2CompoundExpression() {
    static auto signature = detail::makeSignature("log2", [](double x) -> Result<double> { return util::log2(x); });
    return signature;
}

const auto& sinCompoundExpression() {
    static auto signature = detail::makeSignature("sin", [](double x) -> Result<double> { return sin(x); });
    return signature;
}

const auto& cosCompoundExpression() {
    static auto signature = detail::makeSignature("cos", [](double x) -> Result<double> { return cos(x); });
    return signature;
}

const auto& tanCompoundExpression() {
    static auto signature = detail::makeSignature("tan", [](double x) -> Result<double> { return tan(x); });
    return signature;
}

const auto& asinCompoundExpression() {
    static auto signature = detail::makeSignature("asin", [](double x) -> Result<double> { return asin(x); });
    return signature;
}

const auto& acosCompoundExpression() {
    static auto signature = detail::makeSignature("acos", [](double x) -> Result<double> { return acos(x); });
    return signature;
}

const auto& atanCompoundExpression() {
    static auto signature = detail::makeSignature("atan", [](double x) -> Result<double> { return atan(x); });
    return signature;
}

const auto& minCompoundExpression() {
    static auto signature = detail::makeSignature("min", [](const Varargs<double>& args) -> Result<double> {
        double result = std::numeric_limits<double>::infinity();
        for (double arg : args) {
            result = fmin(arg, result);
        }
        return result;
    });
    return signature;
}

const auto& maxCompoundExpression() {
    static auto signature = detail::makeSignature("max", [](const Varargs<double>& args) -> Result<double> {
        double result = -std::numeric_limits<double>::infinity();
        for (double arg : args) {
            result = fmax(arg, result);
        }
        return result;
    });
    return signature;
}

const auto& roundCompoundExpression() {
    static auto signature = detail::makeSignature("round", [](double x) -> Result<double> { return ::round(x); });
    return signature;
}

const auto& floorCompoundExpression() {
    static auto signature = detail::makeSignature("floor", [](double x) -> Result<double> { return std::floor(x); });
    return signature;
}

const auto& ceilCompoundExpression() {
    static auto signature = detail::makeSignature("ceil", [](double x) -> Result<double> { return std::ceil(x); });
    return signature;
}

const auto& absCompoundExpression() {
    static auto signature = detail::makeSignature("abs", [](double x) -> Result<double> { return std::abs(x); });
    return signature;
}

const auto& notCompoundExpression() {
    static auto signature = detail::makeSignature("!", [](bool e) -> Result<bool> { return !e; });
    return signature;
}

const auto& isSupportedScriptCompoundExpression() {
    static auto signature = detail::makeSignature("is-supported-script", [](const std::string& x) -> Result<bool> {
        return util::i18n::isStringInSupportedScript(x);
    });
    return signature;
}

const auto& upcaseCompoundExpression() {
    static auto signature = detail::makeSignature("upcase", [](const std::string& input) -> Result<std::string> {
        return platform::uppercase(input);
    });
    return signature;
}

const auto& downcaseCompoundExpression() {
    static auto signature = detail::makeSignature("downcase", [](const std::string& input) -> Result<std::string> {
        return platform::lowercase(input);
    });
    return signature;
}

const auto& concatCompoundExpression() {
    static auto signature = detail::makeSignature("concat", [](const Varargs<Value>& args) -> Result<std::string> {
        std::string s;
        for (const Value& arg : args) {
            s += toString(arg);
        }
        return s;
    });
    return signature;
}

const auto& resolvedLocaleCompoundExpression() {
    static auto signature = detail::makeSignature("resolved-locale", [](const Collator& collator) -> Result<std::string> {
        return collator.resolvedLocale();
    });
    return signature;
}

const auto& errorCompoundExpression() {
    static auto signature = detail::makeSignature("error", [](const std::string& input) -> Result<type::ErrorType> {
        return EvaluationError { input };
    });
    return signature;
}

const auto& featureStateCompoundExpression() {
    static auto signature = detail::makeSignature(
        "feature-state", [](const EvaluationContext& params, const std::string& key) -> Result<Value> {
            mbgl::Value state;
            if (params.featureState != nullptr) {
                auto it = params.featureState->find(key);
                if (it != params.featureState->end()) {
                    state = mbgl::Value(it->second);
                }
            }
            return toExpressionValue(state);
        });
    return signature;
}

// Legacy Filters
const auto& filterEqualsCompoundExpression() {
    static auto signature = detail::makeSignature("filter-==", [](const EvaluationContext& params, const std::string& key, const Value &lhs) -> Result<bool> {
        const auto rhs = featurePropertyAsExpressionValue(params, key);
        return rhs ? lhs == *rhs : false;
    });
    return signature;
}

const auto& filterIdEqualsCompoundExpression() {
    static auto signature = detail::makeSignature("filter-id-==", [](const EvaluationContext& params, const Value &lhs) -> Result<bool> {
        return lhs == featureIdAsExpressionValue(params);
    });
    return signature;
}

const auto& filterTypeEqualsCompoundExpression() {
    static auto signature = detail::makeSignature("filter-type-==", [](const EvaluationContext& params, const std::string &lhs) -> Result<bool> {
        if (!params.feature) return false;
        return featureTypeAsString(params.feature->getType()) == lhs;
    });
    return signature;
}

const auto& filterLessThanNumberCompoundExpression() {
    static auto signature = detail::makeSignature("filter-<", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> {
        auto rhs = featurePropertyAsDouble(params, key);
        return rhs ? rhs < lhs : false;
    });
    return signature;
}

const auto& filterLessThanStringCompoundExpression() {
    static auto signature = detail::makeSignature(
        "filter-<",
        [](const EvaluationContext& params, const std::string& key, const std::string& lhs) -> Result<bool> {
            auto rhs = featurePropertyAsString(params, key);
            return rhs ? rhs < lhs : false;
        });
    return signature;
}

const auto& filterIdLessThanNumberCompoundExpression() {
    static auto signature = detail::makeSignature("filter-id-<", [](const EvaluationContext& params, double lhs) -> Result<bool> {
        auto rhs = featureIdAsDouble(params);
        return rhs ? rhs < lhs : false;
    });
    return signature;
}

const auto& filterIdLessThanStringCompoundExpression() {
    static auto signature = detail::makeSignature(
        "filter-id-<", [](const EvaluationContext& params, const std::string& lhs) -> Result<bool> {
            auto rhs = featureIdAsString(params);
            return rhs ? rhs < lhs : false;
        });
    return signature;
}

const auto& filterMoreThanNumberCompoundExpression() {
    static auto signature = detail::makeSignature("filter->", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> {
        auto rhs = featurePropertyAsDouble(params, key);
        return rhs ? rhs > lhs : false;
    });
    return signature;
}

const auto& filterMoreThanStringCompoundExpression() {
    static auto signature = detail::makeSignature(
        "filter->",
        [](const EvaluationContext& params, const std::string& key, const std::string& lhs) -> Result<bool> {
            auto rhs = featurePropertyAsString(params, key);
            return rhs ? rhs > lhs : false;
        });
    return signature;
}

const auto& filterIdMoreThanNumberCompoundExpression() {
    static auto signature = detail::makeSignature("filter-id->", [](const EvaluationContext& params, double lhs) -> Result<bool> {
        auto rhs = featureIdAsDouble(params);
        return rhs ? rhs > lhs : false;
    });
    return signature;
}

const auto& filterIdMoreThanStringCompoundExpression() {
    static auto signature = detail::makeSignature(
        "filter-id->", [](const EvaluationContext& params, const std::string& lhs) -> Result<bool> {
            auto rhs = featureIdAsString(params);
            return rhs ? rhs > lhs : false;
        });
    return signature;
}

const auto& filterLessOrEqualThanNumberCompoundExpression() {
    static auto signature = detail::makeSignature("filter-<=", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> {
        auto rhs = featurePropertyAsDouble(params, key);
        return rhs ? rhs <= lhs : false;
    });
    return signature;
}

const auto& filterLessOrEqualThanStringCompoundExpression() {
    static auto signature = detail::makeSignature(
        "filter-<=",
        [](const EvaluationContext& params, const std::string& key, const std::string& lhs) -> Result<bool> {
            auto rhs = featurePropertyAsString(params, key);
            return rhs ? rhs <= lhs : false;
        });
    return signature;
}

const auto& filterIdLessOrEqualThanNumberCompoundExpression() {
    static auto signature = detail::makeSignature("filter-id-<=", [](const EvaluationContext& params, double lhs) -> Result<bool> {
        auto rhs = featureIdAsDouble(params);
        return rhs ? rhs <= lhs : false;
    });
    return signature;
}

const auto& filterIdLessOrEqualThanStringCompoundExpression() {
    static auto signature = detail::makeSignature(
        "filter-id-<=", [](const EvaluationContext& params, const std::string& lhs) -> Result<bool> {
            auto rhs = featureIdAsString(params);
            return rhs ? rhs <= lhs : false;
        });
    return signature;
}

const auto& filterGreaterOrEqualThanNumberCompoundExpression() {
    static auto signature = detail::makeSignature("filter->=", [](const EvaluationContext& params, const std::string& key, double lhs) -> Result<bool> {
        auto rhs = featurePropertyAsDouble(params, key);
        return rhs ? rhs >= lhs : false;
    });
    return signature;
}

const auto& filterGreaterOrEqualThanStringCompoundExpression() {
    static auto signature = detail::makeSignature(
        "filter->=",
        [](const EvaluationContext& params, const std::string& key, const std::string& lhs) -> Result<bool> {
            auto rhs = featurePropertyAsString(params, key);
            return rhs ? rhs >= lhs : false;
        });
    return signature;
}

const auto& filterIdGreaterOrEqualThanNumberCompoundExpression() {
    static auto signature = detail::makeSignature("filter-id->=", [](const EvaluationContext& params, double lhs) -> Result<bool> {
        auto rhs = featureIdAsDouble(params);
        return rhs ? rhs >= lhs : false;
    });
    return signature;
}

const auto& filterIdGreaterOrEqualThanStringCompoundExpression() {
    static auto signature = detail::makeSignature(
        "filter-id->=", [](const EvaluationContext& params, const std::string& lhs) -> Result<bool> {
            auto rhs = featureIdAsString(params);
            return rhs ? rhs >= lhs : false;
        });
    return signature;
}

const auto& filterHasCompoundExpression() {
    static auto signature = detail::makeSignature("filter-has", [](const EvaluationContext& params, const std::string& key) -> Result<bool> {
        assert(params.feature);
        return bool(params.feature->getValue(key));
    });
    return signature;
}

const auto& filterHasIdCompoundExpression() {
    static auto signature = detail::makeSignature("filter-has-id", [](const EvaluationContext& params) -> Result<bool> {
        assert(params.feature);
        return !params.feature->getID().is<NullValue>();
    });
    return signature;
}

const auto& filterTypeInCompoundExpression() {
    static auto signature = detail::makeSignature("filter-type-in", [](const EvaluationContext& params, const Varargs<std::string>& types) -> Result<bool> {
        assert(params.feature);
        optional<std::string> type = featureTypeAsString(params.feature->getType());
        return std::find(types.begin(), types.end(), type) != types.end();
    });
    return signature;
}

const auto& filterIdInCompoundExpression() {
    static auto signature = detail::makeSignature("filter-id-in", [](const EvaluationContext& params, const Varargs<Value>& ids) -> Result<bool> {
        auto id = featureIdAsExpressionValue(params);
        return std::find(ids.begin(), ids.end(), id) != ids.end();
    });
    return signature;
}

const auto& filterInCompoundExpression() {
    static auto signature = detail::makeSignature("filter-in", [](const EvaluationContext& params, const Varargs<Value>& varargs) -> Result<bool> {
        if (varargs.size() < 2) return false;
        assert(varargs[0].is<std::string>());
        auto value = featurePropertyAsExpressionValue(params, varargs[0].get<std::string>());
        return value ? std::find(varargs.begin() + 1, varargs.end(), *value) != varargs.end() : false;
    });
    return signature;
}

using ParseCompoundFunction = const std::unique_ptr<detail::SignatureBase>& (*)();
MAPBOX_ETERNAL_CONSTEXPR const auto compoundExpressionRegistry =
    mapbox::eternal::hash_map<mapbox::eternal::string, ParseCompoundFunction>({
        {"e", eCompoundExpression},
        {"pi", piCompoundExpression},
        {"ln2", ln2CompoundExpression},
        {"typeof", typeofCompoundExpression},
        {"to-rgba", toRgbaCompoundExpression},
        {"rgba", rgbaCompoundExpression},
        {"rgb", rgbCompoundExpression},
        {"zoom", zoomCompoundExpression},
        {"heatmap-density", heatmapDensityCompoundExpression},
        {"line-progress", lineProgressCompoundExpression},
        {"accumulated", accumulatedCompoundExpression},
        {"has", hasContextCompoundExpression},
        {"has", hasObjectCompoundExpression},
        {"get", getContextCompoundExpression},
        {"get", getObjectCompoundExpression},
        {"properties", propertiesCompoundExpression},
        {"geometry-type", geometryTypeCompoundExpression},
        {"id", idCompoundExpression},
        {"+", plusCompoundExpression},
        {"-", minusCompoundExpression},
        {"-", negateCompoundExpression},
        {"*", multiplyCompoundExpression},
        {"/", divideCompoundExpression},
        {"%", modCompoundExpression},
        {"^", powCompoundExpression},
        {"sqrt", sqrtCompoundExpression},
        {"log10", log10CompoundExpression},
        {"ln", lnCompoundExpression},
        {"log2", log2CompoundExpression},
        {"sin", sinCompoundExpression},
        {"cos", cosCompoundExpression},
        {"tan", tanCompoundExpression},
        {"asin", asinCompoundExpression},
        {"acos", acosCompoundExpression},
        {"atan", atanCompoundExpression},
        {"min", minCompoundExpression},
        {"max", maxCompoundExpression},
        {"round", roundCompoundExpression},
        {"floor", floorCompoundExpression},
        {"ceil", ceilCompoundExpression},
        {"abs", absCompoundExpression},
        {"!", notCompoundExpression},
        {"is-supported-script", isSupportedScriptCompoundExpression},
        {"upcase", upcaseCompoundExpression},
        {"downcase", downcaseCompoundExpression},
        {"concat", concatCompoundExpression},
        {"resolved-locale", resolvedLocaleCompoundExpression},
        {"error", errorCompoundExpression},
        {"feature-state", featureStateCompoundExpression},
        // Legacy Filters
        {"filter-==", filterEqualsCompoundExpression},
        {"filter-id-==", filterIdEqualsCompoundExpression},
        {"filter-type-==", filterTypeEqualsCompoundExpression},
        {"filter-<", filterLessThanNumberCompoundExpression},
        {"filter-<", filterLessThanStringCompoundExpression},
        {"filter-id-<", filterIdLessThanNumberCompoundExpression},
        {"filter-id-<", filterIdLessThanStringCompoundExpression},
        {"filter->", filterMoreThanNumberCompoundExpression},
        {"filter->", filterMoreThanStringCompoundExpression},
        {"filter-id->", filterIdMoreThanNumberCompoundExpression},
        {"filter-id->", filterIdMoreThanStringCompoundExpression},
        {"filter-<=", filterLessOrEqualThanNumberCompoundExpression},
        {"filter-<=", filterLessOrEqualThanStringCompoundExpression},
        {"filter-id-<=", filterIdLessOrEqualThanNumberCompoundExpression},
        {"filter-id-<=", filterIdLessOrEqualThanStringCompoundExpression},
        {"filter->=", filterGreaterOrEqualThanNumberCompoundExpression},
        {"filter->=", filterGreaterOrEqualThanStringCompoundExpression},
        {"filter-id->=", filterIdGreaterOrEqualThanNumberCompoundExpression},
        {"filter-id->=", filterIdGreaterOrEqualThanStringCompoundExpression},
        {"filter-has", filterHasCompoundExpression},
        {"filter-has-id", filterHasIdCompoundExpression},
        {"filter-type-in", filterTypeInCompoundExpression},
        {"filter-id-in", filterIdInCompoundExpression},
        {"filter-in", filterInCompoundExpression},
    });

using namespace mbgl::style::conversion;

using DefinitionIterator = decltype(compoundExpressionRegistry)::const_iterator;
using Definitions = std::pair<DefinitionIterator, DefinitionIterator>;

std::string expectedTypesError(const Definitions& definitions,
                               const std::vector<std::unique_ptr<Expression>>& args) {
    std::vector<std::string> availableOverloads; // Only used if there are no overloads with matching number of args
    std::vector<std::string> overloads;
    for (auto it = definitions.first; it != definitions.second; ++it) {
        const auto& signature = it->second();
        signature->params.match(
        [&](const VarargsType& varargs) {
            std::string overload = "(" + toString(varargs.type) + ")";
            overloads.push_back(overload);
        },
        [&](const std::vector<type::Type>& params) {
            std::string overload = "(";
            bool first = true;
            for (const type::Type& param : params) {
                if (!first) overload += ", ";
                overload += toString(param);
                first = false;
            }
            overload += ")";
            if (params.size() == args.size()) {
                overloads.push_back(overload);
            } else {
                availableOverloads.push_back(overload);
            }
        }
        );
    }
    std::string signatures = overloads.empty() ?
        boost::algorithm::join(availableOverloads, " | ") :
        boost::algorithm::join(overloads, " | ");

    std::string actualTypes;
    for (const auto& arg : args) {
        if (!actualTypes.empty()) {
            actualTypes += ", ";
        }
        actualTypes += toString(arg->getType());
    }

    return "Expected arguments of type " + signatures + ", but found (" + actualTypes + ") instead.";
}

static ParseResult createCompoundExpression(const Definitions& definitions,
                                            std::vector<std::unique_ptr<Expression>> args,
                                            ParsingContext& ctx)
{
    ParsingContext signatureContext(ctx.getKey());

    for (auto it = definitions.first; it != definitions.second; ++it) {
        const auto& signature = it->second();
        signatureContext.clearErrors();

        if (signature->params.is<std::vector<type::Type>>()) {
            const std::vector<type::Type>& params = signature->params.get<std::vector<type::Type>>();
            if (params.size() != args.size()) {
                signatureContext.error(
                    "Expected " + util::toString(params.size()) +
                    " arguments, but found " + util::toString(args.size()) + " instead."
                );
                continue;
            }

            for (std::size_t i = 0; i < args.size(); i++) {
                const std::unique_ptr<Expression>& arg = args[i];
                optional<std::string> err = type::checkSubtype(params.at(i), arg->getType());
                if (err) {
                    signatureContext.error(*err, i + 1);
                }
            }
        } else if (signature->params.is<VarargsType>()) {
            const type::Type& paramType = signature->params.get<VarargsType>().type;
            for (std::size_t i = 0; i < args.size(); i++) {
                const std::unique_ptr<Expression>& arg = args[i];
                optional<std::string> err = type::checkSubtype(paramType, arg->getType());
                if (err) {
                    signatureContext.error(*err, i + 1);
                }
            }
        }

        if (signatureContext.getErrors().empty()) {
            return ParseResult(std::make_unique<CompoundExpression>(*signature, std::move(args)));
        }
    }

    if (definitions.second - definitions.first == 1) {
        ctx.appendErrors(std::move(signatureContext));
    } else {
        ctx.error(expectedTypesError(definitions, args));
    }

    return ParseResult();
}

ParseResult parseCompoundExpression(const std::string& name, const Convertible& value, ParsingContext& ctx) {
    assert(isArray(value) && arrayLength(value) > 0);

    const auto definitions = compoundExpressionRegistry.equal_range(name.c_str());
    if (definitions.first == definitions.second) {
        ctx.error(
             R"(Unknown expression ")" + name + R"(". If you wanted a literal array, use ["literal", [...]].)",
            0
        );
        return ParseResult();
    }

    auto length = arrayLength(value);

    for (auto it = definitions.first; it != definitions.second; ++it) {
        const auto& signature = it->second();

        if (
            signature->params.is<VarargsType>() ||
            signature->params.get<std::vector<type::Type>>().size() == length - 1
            ) {
            // First parse all the args, potentially coercing to the
            // types expected by this overload.
            ctx.clearErrors();
            bool argParseFailed = false;
            std::vector<std::unique_ptr<Expression>> args;
            args.reserve(length - 1);

            for (std::size_t i = 1; i < length; i++) {
                optional<type::Type> expected = signature->params.match(
                    [](const VarargsType& varargs) { return varargs.type; },
                    [&](const std::vector<type::Type>& params_) { return params_[i - 1]; }
                );

                auto parsed = ctx.parse(arrayMember(value, i), i, expected);
                if (!parsed) {
                    argParseFailed = true;
                    break;
                }
                args.push_back(std::move(*parsed));
            }
            if (argParseFailed) {
                // Couldn't coerce args of this overload to expected type, move
                // on to next one.
                continue;
            } else {
                ParseResult parseWithArgs = createCompoundExpression(definitions, std::move(args), ctx);
                if (parseWithArgs) {
                    return parseWithArgs;
                }
            }
        }
    }
    // The args couldn't be coerced to any of the expected types.
    // Parse the arguments again without expected types just for the error message
    ctx.clearErrors();
    std::vector<std::unique_ptr<Expression>> args;
    args.reserve(length - 1);

    for (std::size_t i = 1; i < length; i++) {
        auto parsed = ctx.parse(arrayMember(value, i), i);
        if (!parsed) {
            return ParseResult();
        }
        args.push_back(std::move(*parsed));
    }

    ctx.error(expectedTypesError(definitions, args));

    return ParseResult();
}

ParseResult createCompoundExpression(const std::string& name,
                                     std::vector<std::unique_ptr<Expression>> args,
                                     ParsingContext& ctx)
{
    return createCompoundExpression(compoundExpressionRegistry.equal_range(name.c_str()), std::move(args), ctx);
}

CompoundExpression::CompoundExpression(const detail::SignatureBase& signature_, std::vector<std::unique_ptr<Expression>> args_) :
    Expression(Kind::CompoundExpression, signature_.result),
    signature(signature_),
    args(std::move(args_))
{}

std::string CompoundExpression::getOperator() const {
    return signature.name;
}

EvaluationResult CompoundExpression::evaluate(const EvaluationContext& evaluationParams) const {
    return signature.apply(evaluationParams, args);
}

optional<std::size_t> CompoundExpression::getParameterCount() const {
    return signature.params.match([&](const VarargsType&) -> optional<std::size_t> { return {}; },
                                  [&](const std::vector<type::Type>& p) -> optional<std::size_t> { return p.size(); });
}

std::vector<optional<Value>> CompoundExpression::possibleOutputs() const {
    return { nullopt };
}

void CompoundExpression::eachChild(const std::function<void(const Expression&)>& visit) const {
    for (const std::unique_ptr<Expression>& e : args) {
        visit(*e);
    }
}

bool CompoundExpression::operator==(const Expression& e) const {
    if (e.getKind() == Kind::CompoundExpression) {
        auto rhs = static_cast<const CompoundExpression*>(&e);
        return signature.name == rhs->signature.name &&
            signature.result == rhs->signature.result &&
            signature.params == rhs->signature.params &&
            Expression::childrenEqual(args, rhs->args);
    }
    return false;
}

bool CompoundExpression::exists(const std::string& name) {
    return compoundExpressionRegistry.contains(name.c_str());
}

} // namespace expression
} // namespace style
} // namespace mbgl


// [===========================================================================]
// [                                M o n e t a                                ]
// [---------------------------------------------------------------------------]
// [                                                                           ]
// [                          Copyright (C) 2005-2015                          ]
// [                      Rodrigo Madera <madera@acm.org>                      ]
// [                                                                           ]
// [---------------------------------------------------------------------------]
// [         Distributed under the Boost Software License, Version 1.0         ]
// [ Read accompanying LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt ]
// [===========================================================================]

#pragma once
#include <boost/mpl/bool.hpp>

// TODO: Clean this up, MPLize and PPize it and create fork on GitHub to give back to the community.
// From: https://github.com/jaredhoberock/is_call_possible
// With inspiration from: Roman Perepelitsa

// inspired by Roman Perepelitsa's presentation from comp.lang.c++.moderated
// based on the implementation here: http://www.rsdn.ru/forum/cpp/2759773.1.aspx

namespace is_call_possible_detail {
	template<typename T> struct add_reference     { typedef T& type; };
	template<typename T> struct add_reference<T&> { typedef T& type; };
} // end is_call_possible_detail

#define DEFINE_HAS_MEMBER_FUNCTION(trait_name, member_function_name)                                         \
                                                                                \
template<typename T, typename Signature>                                                                     \
class trait_name;                                                                                \
                                                                                \
template<typename T, typename Result>                                                                        \
class trait_name<T, Result(void)>                                                                            \
{                                                                                \
   class yes { char m; };                                                                                \
   class no { yes m[2]; };                                                                                \
   struct base_mixin                                                                                \
   {                                                                                \
     Result member_function_name();                                                                          \
   };                                                                                \
   struct base : public T, public base_mixin {};                                                             \
   template <typename U, U t>  class helper{};                                                               \
   template <typename U>                                                                                \
   static no deduce(U*, helper<Result (base_mixin::*)(), &U::member_function_name>* = 0);                    \
   static yes deduce(...);                                                                                \
public:                                                                                \
   static const bool value = sizeof(yes) == sizeof(deduce(static_cast<base*>(0)));                           \
   static const boost::mpl::bool_<value> type;                                                               \
};                                                                                \
                                                                                \
template<typename T, typename Result, typename Arg>                                                          \
class trait_name<T, Result(Arg)>                                                                             \
{                                                                                \
   class yes { char m; };                                                                                \
   class no { yes m[2]; };                                                                                \
   struct base_mixin                                                                                \
   {                                                                                \
     Result member_function_name(Arg);                                                                       \
   };                                                                                \
   struct base : public T, public base_mixin {};                                                             \
   template <typename U, U t>  class helper{};                                                               \
   template <typename U>                                                                                \
   static no deduce(U*, helper<Result (base_mixin::*)(Arg), &U::member_function_name>* = 0);                 \
   static yes deduce(...);                                                                                \
public:                                                                                \
   static const bool value = sizeof(yes) == sizeof(deduce(static_cast<base*>(0)));                           \
   static const boost::mpl::bool_<value> type;                                                               \
};                                                                                \
                                                                                \
template<typename T, typename Result, typename Arg1, typename Arg2>                                          \
class trait_name<T, Result(Arg1,Arg2)>                                                                       \
{                                                                                \
   class yes { char m; };                                                                                \
   class no { yes m[2]; };                                                                                \
   struct base_mixin                                                                                \
   {                                                                                \
     Result member_function_name(Arg1,Arg2);                                                                 \
   };                                                                                \
   struct base : public T, public base_mixin {};                                                             \
   template <typename U, U t>  class helper{};                                                               \
   template <typename U>                                                                                \
   static no deduce(U*, helper<Result (base_mixin::*)(Arg1,Arg2), &U::member_function_name>* = 0);           \
   static yes deduce(...);                                                                                \
public:                                                                                \
   static const bool value = sizeof(yes) == sizeof(deduce(static_cast<base*>(0)));                           \
   static const boost::mpl::bool_<value> type;                                                               \
};                                                                                \
                                                                                \
template<typename T, typename Result, typename Arg1, typename Arg2, typename Arg3>                           \
class trait_name<T, Result(Arg1,Arg2,Arg3)>                                                                  \
{                                                                                \
   class yes { char m; };                                                                                \
   class no { yes m[2]; };                                                                                \
   struct base_mixin                                                                                \
   {                                                                                \
     Result member_function_name(Arg1,Arg2,Arg3);                                                            \
   };                                                                                \
   struct base : public T, public base_mixin {};                                                             \
   template <typename U, U t>  class helper{};                                                               \
   template <typename U>                                                                                \
   static no deduce(U*, helper<Result (base_mixin::*)(Arg1,Arg2,Arg3), &U::member_function_name>* = 0);      \
   static yes deduce(...);                                                                                \
public:                                                                                \
   static const bool value = sizeof(yes) == sizeof(deduce(static_cast<base*>(0)));                           \
   static const boost::mpl::bool_<value> type;                                                               \
};                                                                                \
                                                                                \
template<typename T, typename Result, typename Arg1, typename Arg2, typename Arg3, typename Arg4>            \
class trait_name<T, Result(Arg1,Arg2,Arg3,Arg4)>                                                             \
{                                                                                \
   class yes { char m; };                                                                                \
   class no { yes m[2]; };                                                                                \
   struct base_mixin                                                                                \
   {                                                                                \
     Result member_function_name(Arg1,Arg2,Arg3,Arg4);                                                       \
   };                                                                                \
   struct base : public T, public base_mixin {};                                                             \
   template <typename U, U t>  class helper{};                                                               \
   template <typename U>                                                                                \
   static no deduce(U*, helper<Result (base_mixin::*)(Arg1,Arg2,Arg3,Arg4), &U::member_function_name>* = 0); \
   static yes deduce(...);                                                                                \
public:                                                                                \
   static const bool value = sizeof(yes) == sizeof(deduce(static_cast<base*>(0)));                           \
   static const boost::mpl::bool_<value> type;                                                               \
};                                                                                \
                                                                                \
template<typename T, typename Result, typename Arg1, typename Arg2, typename Arg3, typename Arg4, typename Arg5> \
class trait_name<T, Result(Arg1,Arg2,Arg3,Arg4,Arg5)>                                                        \
{                                                                                \
   class yes { char m; };                                                                                \
   class no { yes m[2]; };                                                                                \
   struct base_mixin                                                                                \
   {                                                                                \
     Result member_function_name(Arg1,Arg2,Arg3,Arg4,Arg5);                                                  \
   };                                                                                \
   struct base : public T, public base_mixin {};                                                             \
   template <typename U, U t>  class helper{};                                                               \
   template <typename U>                                                                                \
   static no deduce(U*, helper<Result (base_mixin::*)(Arg1,Arg2,Arg3,Arg4,Arg5), &U::member_function_name>* = 0); \
   static yes deduce(...);                                                                                \
public:                                                                                \
   static const bool value = sizeof(yes) == sizeof(deduce(static_cast<base*>(0)));                           \
   static const boost::mpl::bool_<value> type;                                                               \
};                                                                                \
                                                                                \
template<typename T, typename Result, class T0, class T1, class T2, class T3, class T4, class T5>            \
class trait_name<T, Result(T0,T1,T2,T3,T4,T5)>                                                               \
{                                                                                \
   class yes { char m; };                                                                                \
   class no { yes m[2]; };                                                                                \
   struct base_mixin                                                                                \
   {                                                                                \
     Result member_function_name(T0,T1,T2,T3,T4,T5);                                                         \
   };                                                                                \
   struct base : public T, public base_mixin {};                                                             \
   template <typename U, U t>  class helper{};                                                               \
   template <typename U>                                                                                \
   static no deduce(U*, helper<Result (base_mixin::*)(T0,T1,T2,T3,T4,T5), &U::member_function_name>* = 0);   \
   static yes deduce(...);                                                                                \
public:                                                                                \
   static const bool value = sizeof(yes) == sizeof(deduce(static_cast<base*>(0)));                           \
   static const boost::mpl::bool_<value> type;                                                               \
};                                                                                \
                                                                                \
template<typename T, typename Result, class T0, class T1, class T2, class T3, class T4, class T5, class T6>  \
class trait_name<T, Result(T0,T1,T2,T3,T4,T5,T6)>                                                            \
{                                                                                \
   class yes { char m; };                                                                                \
   class no { yes m[2]; };                                                                                \
   struct base_mixin                                                                                \
   {                                                                                \
     Result member_function_name(T0,T1,T2,T3,T4,T5,T6);                                                      \
   };                                                                                \
   struct base : public T, public base_mixin {};                                                             \
   template <typename U, U t>  class helper{};                                                               \
   template <typename U>                                                                                \
   static no deduce(U*, helper<Result (base_mixin::*)(T0,T1,T2,T3,T4,T5,T6), &U::member_function_name>* = 0);\
   static yes deduce(...);                                                                                \
public:                                                                                \
   static const bool value = sizeof(yes) == sizeof(deduce(static_cast<base*>(0)));                           \
   static const boost::mpl::bool_<value> type;                                                               \
};

namespace is_call_possible_detail {
   template <typename T>
   class void_exp_result {};

   template <typename T, typename U>
   U const& operator,(U const&, void_exp_result<T>);

   template <typename T, typename U>
   U& operator,(U&, void_exp_result<T>);

   template <typename src_type, typename dest_type>
   struct clone_constness { typedef dest_type type; };

   template <typename src_type, typename dest_type>
   struct clone_constness<const src_type, dest_type> {
     typedef const dest_type type;
   };
}

#define DEFINE_IS_CALL_POSSIBLE(trait_name, member_function_name)                                                       \
namespace moneta { namespace traits { namespace detail {                                                                \
namespace trait_name##_detail                                                                                \
{                                                                                \
DEFINE_HAS_MEMBER_FUNCTION(has_member, member_function_name)                                                            \
}                                                                                \
                                                                                \
template <typename T, typename Signature>                                                                               \
struct trait_name                                                                                \
{                                                                                \
  private:                                                                                \
   class yes {};                                                                                \
   class no { yes m[2]; };                                                                                \
   struct derived : public T                                                                                \
   {                                                                                \
     using T::member_function_name;                                                                                \
     no member_function_name(...) const;                                                                                \
   };                                                                                \
                                                                                \
   typedef typename is_call_possible_detail::clone_constness<T, derived>::type derived_type;                            \
                                                                                \
   template <typename U, typename Result>                                                                               \
   struct return_value_check                                                                                \
   {                                                                                \
     static yes deduce(Result);                                                                                \
     static no deduce(...);                                                                                \
     static no deduce(no);                                                                                \
     static no deduce(is_call_possible_detail::void_exp_result<T>);                                                     \
   };                                                                                \
                                                                                \
   template <typename U>                                                                                \
   struct return_value_check<U, void>                                                                                \
   {                                                                                \
     static yes deduce(...);                                                                                \
     static no deduce(no);                                                                                \
   };                                                                                \
                                                                                \
   template <bool has_the_member_of_interest, typename F>                                                               \
   struct impl                                                                                \
   {                                                                                \
     static const bool value = false;                                                                                \
     typedef boost::mpl::bool_<value> type;                                                                             \
   };                                                                                \
                                                                                \
   template <typename Result, typename Arg>                                                                             \
   struct impl<true, Result(Arg)>                                                                                \
   {                                                                                \
     static typename is_call_possible_detail::add_reference<derived_type>::type test_me;                                \
     static typename is_call_possible_detail::add_reference<Arg>::type          arg;                                    \
                                                                                \
     static const bool value =                                                                                \
       sizeof(                                                                                \
            return_value_check<T, Result>::deduce(                                                                      \
             (test_me.member_function_name(arg), is_call_possible_detail::void_exp_result<T>())                         \
                         )                                                                                \
            ) == sizeof(yes);                                                                                \
     typedef boost::mpl::bool_<value> type;                                                                        \
   };                                                                                \
                                                                                \
   template <typename Result, typename Arg1, typename Arg2>                                                             \
   struct impl<true, Result(Arg1,Arg2)>                                                                                \
   {                                                                                \
     static typename is_call_possible_detail::add_reference<derived_type>::type test_me;                                \
     static typename is_call_possible_detail::add_reference<Arg1>::type         arg1;                                   \
     static typename is_call_possible_detail::add_reference<Arg2>::type         arg2;                                   \
                                                                                \
     static const bool value =                                                                                \
       sizeof(                                                                                \
            return_value_check<T, Result>::deduce(                                                                      \
             (test_me.member_function_name(arg1,arg2), is_call_possible_detail::void_exp_result<T>())                   \
                         )                                                                                \
            ) == sizeof(yes);                                                                                \
     typedef boost::mpl::bool_<value> type;                                                                             \
   };                                                                                \
                                                                                \
   template <typename Result, typename Arg1, typename Arg2, typename Arg3>                                              \
   struct impl<true, Result(Arg1,Arg2,Arg3)>                                                                            \
   {                                                                                \
     static typename is_call_possible_detail::add_reference<derived_type>::type test_me;                                \
     static typename is_call_possible_detail::add_reference<Arg1>::type         arg1;                                   \
     static typename is_call_possible_detail::add_reference<Arg2>::type         arg2;                                   \
     static typename is_call_possible_detail::add_reference<Arg3>::type         arg3;                                   \
                                                                                \
     static const bool value =                                                                                \
       sizeof(                                                                                \
            return_value_check<T, Result>::deduce(                                                                      \
             (test_me.member_function_name(arg1,arg2,arg3), is_call_possible_detail::void_exp_result<T>())              \
                         )                                                                                \
            ) == sizeof(yes);                                                                                \
     typedef boost::mpl::bool_<value> type;                                                                             \
   };                                                                                \
                                                                                \
   template <typename Result, typename Arg1, typename Arg2, typename Arg3, typename Arg4>                               \
   struct impl<true, Result(Arg1,Arg2,Arg3,Arg4)>                                                                       \
   {                                                                                \
     static typename is_call_possible_detail::add_reference<derived_type>::type test_me;                                \
     static typename is_call_possible_detail::add_reference<Arg1>::type         arg1;                                   \
     static typename is_call_possible_detail::add_reference<Arg2>::type         arg2;                                   \
     static typename is_call_possible_detail::add_reference<Arg3>::type         arg3;                                   \
     static typename is_call_possible_detail::add_reference<Arg4>::type         arg4;                                   \
                                                                                \
     static const bool value =                                                                                \
       sizeof(                                                                                \
            return_value_check<T, Result>::deduce(                                                                      \
             (test_me.member_function_name(arg1,arg2,arg3,arg4), is_call_possible_detail::void_exp_result<T>())         \
                         )                                                                                \
            ) == sizeof(yes);                                                                                \
     typedef boost::mpl::bool_<value> type;                                                                             \
   };                                                                                \
                                                                                \
   template <typename Result, typename Arg1, typename Arg2, typename Arg3, typename Arg4, typename Arg5>                \
   struct impl<true, Result(Arg1,Arg2,Arg3,Arg4,Arg5)>                                                                  \
   {                                                                                \
     static typename is_call_possible_detail::add_reference<derived_type>::type test_me;                                \
     static typename is_call_possible_detail::add_reference<Arg1>::type         arg1;                                   \
     static typename is_call_possible_detail::add_reference<Arg2>::type         arg2;                                   \
     static typename is_call_possible_detail::add_reference<Arg3>::type         arg3;                                   \
     static typename is_call_possible_detail::add_reference<Arg4>::type         arg4;                                   \
     static typename is_call_possible_detail::add_reference<Arg5>::type         arg5;                                   \
                                                                                \
     static const bool value =                                                                                \
       sizeof(                                                                                \
            return_value_check<T, Result>::deduce(                                                                      \
             (test_me.member_function_name(arg1,arg2,arg3,arg4,arg5), is_call_possible_detail::void_exp_result<T>())    \
                         )                                                                                \
            ) == sizeof(yes);                                                                                \
     typedef boost::mpl::bool_<value> type;                                                                             \
   };                                                                                \
                                                                                \
   template <typename Result, class T0, class T1, class T2, class T3, class T4, class T5>                               \
   struct impl<true, Result(T0,T1,T2,T3,T4,T5)>                                                                         \
   {                                                                                \
     static typename is_call_possible_detail::add_reference<derived_type>::type test_me;                                \
     static typename is_call_possible_detail::add_reference<T0>::type         arg1;                                     \
     static typename is_call_possible_detail::add_reference<T1>::type         arg2;                                     \
     static typename is_call_possible_detail::add_reference<T2>::type         arg3;                                     \
     static typename is_call_possible_detail::add_reference<T3>::type         arg4;                                     \
     static typename is_call_possible_detail::add_reference<T4>::type         arg5;                                     \
     static typename is_call_possible_detail::add_reference<T5>::type         arg6;                                     \
                                                                                \
     static const bool value =                                                                                \
       sizeof(                                                                                \
            return_value_check<T, Result>::deduce(                                                                      \
             (test_me.member_function_name(arg1,arg2,arg3,arg4,arg5,arg6), is_call_possible_detail::void_exp_result<T>()) \
                         )                                                                                \
            ) == sizeof(yes);                                                                                \
     typedef boost::mpl::bool_<value> type;                                                                             \
   };                                                                                \
                                                                                \
   template <typename Result, class T0, class T1, class T2, class T3, class T4, class T5, class T6>                     \
   struct impl<true, Result(T0,T1,T2,T3,T4,T5,T6)>                                                                      \
   {                                                                                \
     static typename is_call_possible_detail::add_reference<derived_type>::type test_me;                                \
     static typename is_call_possible_detail::add_reference<T0>::type         arg1;                                     \
     static typename is_call_possible_detail::add_reference<T1>::type         arg2;                                     \
     static typename is_call_possible_detail::add_reference<T2>::type         arg3;                                     \
     static typename is_call_possible_detail::add_reference<T3>::type         arg4;                                     \
     static typename is_call_possible_detail::add_reference<T4>::type         arg5;                                     \
     static typename is_call_possible_detail::add_reference<T5>::type         arg6;                                     \
     static typename is_call_possible_detail::add_reference<T5>::type         arg7;                                     \
                                                                                \
     static const bool value =                                                                                \
       sizeof(                                                                                \
            return_value_check<T, Result>::deduce(                                                                      \
             (test_me.member_function_name(arg1,arg2,arg3,arg4,arg5,arg6,arg7), is_call_possible_detail::void_exp_result<T>()) \
                         )                                                                                \
            ) == sizeof(yes);                                                                                \
     typedef boost::mpl::bool_<value> type;                                                                             \
   };                                                                                \
                                                                                \
  public:                                                                                \
    static const bool value = impl<trait_name##_detail::has_member<T,Signature>::value, Signature>::value;              \
    typedef boost::mpl::bool_<value> type;                                                                              \
};                                                                                \
}}}