#ifndef JSON_LOGIC_H
#define JSON_LOGIC_H

#include <functional>
#include <string>
#include <unordered_map>

#include <ArduinoJson.h>

#include "jsonlogic/exception.h"

namespace json_logic
{
    using Json = ArduinoJson::JsonDocument;
    using JsonView = ArduinoJson::JsonVariantConst;
    using JsonArrayView = ArduinoJson::JsonArrayConst;
    using Operation = std::function<Json(JsonArrayView, JsonView)>;

    class IJsonLogic
    {
    public:
        virtual ~IJsonLogic() = default;
        virtual Json Apply(JsonView logic) const = 0;
        virtual Json Apply(JsonView logic, JsonView data) const = 0;
    };

    class JsonLogic : public IJsonLogic
    {
    public:
        JsonLogic(const JsonLogic& other) = delete;
        void operator=(const JsonLogic& other) = delete;

        static JsonLogic* GetInstance();

        Json Apply(JsonView logic) const override;
        Json Apply(JsonView logic, JsonView data) const override;

        void AddOperation(const std::string& name, const Operation& operation);
        void RmOperation(const std::string& name);

        bool HasError() const;
        const std::string& GetLastError() const;
        void ClearError() const;

    protected:
        JsonLogic();

    private:
        std::unordered_map<std::string, Operation> operations_;
        mutable std::string last_error_;

        Json ApplyInternal(JsonView logic, JsonView data) const;
        Json Fail(const char* operation_name, const std::string& message) const;

        static Json Copy(JsonView value);
        static Json MakeArray();
        static Json MakeBoolean(bool value);
        static Json MakeNumber(double value);
        static Json MakeInteger(ArduinoJson::JsonInteger value);
        static Json MakeString(const std::string& value);

        static bool IsLogic(JsonView logic);
        static std::string GetOperator(JsonView logic);
        static JsonView GetValues(JsonView logic);
        static bool IsNumber(JsonView value);
        static bool IsInteger(JsonView value);
        static double ToDouble(JsonView value);
        static bool JsonEqual(JsonView lhs, JsonView rhs);
        static bool StrictEqual(JsonView lhs, JsonView rhs);
        static bool AbstractEqual(JsonView lhs, JsonView rhs);
        static bool Truthy(JsonView value);
        static bool TryResolveVariable(JsonView key, JsonView data, Json& result);

        Json CastToNumber(JsonView value) const;

        Json operation_data_var(JsonArrayView values, JsonView data) const;
        Json operation_data_missing(JsonArrayView values, JsonView data) const;
        Json operation_data_missing_some(JsonArrayView values, JsonView data) const;

        Json operation_logic_if(JsonArrayView values, JsonView data) const;
        Json operation_logic_abstract_equal(JsonArrayView values, JsonView data) const;
        Json operation_logic_strict_equal(JsonArrayView values, JsonView data) const;
        Json operation_logic_abstract_different(JsonArrayView values, JsonView data) const;
        Json operation_logic_strict_different(JsonArrayView values, JsonView data) const;
        Json operation_logic_not(JsonArrayView values, JsonView data) const;
        Json operation_logic_truthy(JsonArrayView values, JsonView data) const;
        Json operation_logic_or(JsonArrayView values, JsonView data) const;
        Json operation_logic_and(JsonArrayView values, JsonView data) const;

        Json operation_numeric_gt(JsonArrayView values, JsonView data) const;
        Json operation_numeric_gte(JsonArrayView values, JsonView data) const;
        Json operation_numeric_lt(JsonArrayView values, JsonView data) const;
        Json operation_numeric_lte(JsonArrayView values, JsonView data) const;
        Json operation_numeric_max(JsonArrayView values, JsonView data) const;
        Json operation_numeric_min(JsonArrayView values, JsonView data) const;
        Json operation_numeric_addition(JsonArrayView values, JsonView data) const;
        Json operation_numeric_subtraction(JsonArrayView values, JsonView data) const;
        Json operation_numeric_multiplication(JsonArrayView values, JsonView data) const;
        Json operation_numeric_division(JsonArrayView values, JsonView data) const;
        Json operation_numeric_modulo(JsonArrayView values, JsonView data) const;

        Json operation_array_map(JsonArrayView values, JsonView data) const;
        Json operation_array_reduce(JsonArrayView values, JsonView data) const;
        Json operation_array_filter(JsonArrayView values, JsonView data) const;
        Json operation_array_all(JsonArrayView values, JsonView data) const;
        Json operation_array_none(JsonArrayView values, JsonView data) const;
        Json operation_array_some(JsonArrayView values, JsonView data) const;
        Json operation_array_merge(JsonArrayView values, JsonView data) const;
        Json operation_array_string_in(JsonArrayView values, JsonView data) const;

        Json operation_string_cat(JsonArrayView values, JsonView data) const;
        Json operation_string_substr(JsonArrayView values, JsonView data) const;

        Json operation_misc_log(JsonArrayView values, JsonView data) const;
    };
}

#endif
