#ifndef PORYGONLANG_USERDATATEMPLATES_HPP
#define PORYGONLANG_USERDATATEMPLATES_HPP

/*!
\brief Begins creating an invokable function. Make sure to call PORYGON_USERDATA_END after this.
\returns The start of an invokable function with the name __createUserData. This can be called to return a userdata object.
*/
#define PORYGON_USERDATA_START(type) \
    using T_USERDATA = type; \
    static Porygon::UserData::UserData* __createUserData(){ \
        return new Porygon::UserData::UserData({ \

/*!
\brief Ends creation of invokable function to create userdata.
*/
#define PORYGON_USERDATA_END() });};

/*!
\brief Creates an invokable static function with the name __createUserData. This can be called to generate a userdata object.
\param fields The fields of the object.
\returns an invokable static function with the name __createUserData. This can be called to generate a userdata object.
*/
#define PORYGON_USERDATA(type, fields) \
    PORYGON_USERDATA_START(type) \
    fields \
    PORYGON_USERDATA_END()

#define PORYGON_INTEGER_TYPE ((Porygon::ScriptType*)new Porygon::NumericScriptType(true, false))
#define PORYGON_FLOAT_TYPE ((Porygon::ScriptType*)new Porygon::NumericScriptType(true, true))
#define PORYGON_STRING_TYPE ((Porygon::ScriptType*)new Porygon::StringScriptType(false, 0))
#define PORYGON_INDEXABLE_TYPE(keyType, valueType) \
    ((Porygon::ScriptType*)Porygon::UserData::UserDataCollectionType::CreateIndexable(keyType, valueType))

#define PORYGON_FIELD(fieldName, fieldType, getterHelper, setterHelper) \
    { \
        Porygon::Utilities::HashedString::ConstHash(#fieldName), \
            new Porygon::UserData::UserDataField(fieldType, \
                [](void* obj) -> const Porygon::Evaluation::EvalValue* { return new getterHelper;}, \
                [](void* obj, const Porygon::Evaluation::EvalValue* val) { ((T_USERDATA*)obj)->fieldName =  setterHelper;} \
        ) \
    }, \

#define PORYGON_READONLY_FIELD(fieldName, fieldType, getterHelper) \
    { \
        Porygon::Utilities::HashedString::ConstHash(#fieldName), \
            new Porygon::UserData::UserDataField(fieldType, \
                [](void* obj) -> const Porygon::Evaluation::EvalValue* { return new getterHelper;}, \
                nullptr \
        ) \
    }, \

#define PORYGON_INTEGER_FIELD(fieldName) \
    PORYGON_FIELD(fieldName, PORYGON_INTEGER_TYPE, \
        const Porygon::Evaluation::IntegerEvalValue(((T_USERDATA*)obj)->fieldName), val->EvaluateInteger())

#define PORYGON_READONLY_INTEGER_FIELD(fieldName) \
    PORYGON_READONLY_FIELD(fieldName, PORYGON_INTEGER_TYPE, \
        Porygon::Evaluation::IntegerEvalValue(((T_USERDATA*)obj)->fieldName))

#define PORYGON_FLOAT_FIELD(fieldName) \
    PORYGON_FIELD(fieldName, PORYGON_FLOAT_TYPE, \
        Porygon::EvaluationFloatEvalValue(((T_USERDATA*)obj)->fieldName), val->EvaluateFloat())

#define PORYGON_READONLY_FLOAT_FIELD(fieldName) \
    PORYGON_READONLY_FIELD(fieldName, PORYGON_FLOAT_TYPE, \
        Porygon::EvaluationFloatEvalValue(((T_USERDATA*)obj)->fieldName))

/*
#define PORYGON_INDEXABLE_FIELD(fieldName, keyType, valueType) \
    PORYGON_FIELD(fieldName, PORYGON_INDEXABLE_TYPE(keyType, valueType), \
        const Porygon::Evaluation::IntegerEvalValue(((T_USERDATA*)obj)->fieldName), val->EvaluateInteger())
*/

#define PORYGON_READONLY_VECTOR_FIELD(fieldName, valueType) \
    PORYGON_READONLY_FIELD(fieldName, PORYGON_INDEXABLE_TYPE(PORYGON_INTEGER_TYPE, valueType), \
        Porygon::UserData::UserDataCollectionValue( \
            PORYGON_INDEXABLE_TYPE(PORYGON_INTEGER_TYPE, valueType), \
            new UserDataCollectionHelper( \
                obj, \
                [](void* obj, const EvalValue* v) -> const EvalValue*{ \
                    auto index = v->EvaluateInteger() - 1; \
                    auto val = ((T_USERDATA*)obj)->fieldName;\
                    return EvalValueHelper::Create(val[index]); \
                } \
                , [](void* obj, const EvalValue* key, const EvalValue* value){ \
                    auto index = key->EvaluateInteger() - 1;\
                    ((T_USERDATA*)obj)->fieldName[index] = value->EvaluateInteger(); \
                } \
                , [](void* obj) -> Porygon::Evaluation::Iterator* {\
                    auto val = ((T_USERDATA*)obj)->fieldName; \
                    auto size = val.size(); \
                    return new Porygon::UserData::UserDataCollectionRangeIterator(0, size); \
                } \
            ) \
        ) \
    )


#define PORYGON_FUNCTION(fieldName, returnType, ...) \
    { \
        Porygon::Utilities::HashedString::ConstHash(#fieldName), \
        new Porygon::UserData::UserDataField( \
                new Porygon::GenericFunctionScriptType( \
                Porygon::UserData::UserDataFunctionOption::FromRawPointers(returnType,  {__VA_ARGS__}  )), \
        \
        \
        [](void* obj) -> const Porygon::Evaluation::EvalValue* { \
                auto t = new Porygon::Evaluation::GenericFunctionEvalValue(make_shared<GenericFunctionScriptType>(), \
                        Porygon::Utilities::Random::Get()); \
                t->RegisterOption(new Porygon::UserData::UserDataFunction( \
                [](void* obj, const ScriptOptions* opts, const Porygon::Evaluation::EvalValue* par[], int parameterCount) \
                    -> const Porygon::Evaluation::EvalValue*{return ((const T_USERDATA*)obj)->invoke__##fieldName(obj, opts, par, parameterCount);},  \
                obj)); \
                return t;}, \
        nullptr) \
    },


#define PORYGON_INTEGER_FUNCTION(fieldName, ...) \
    PORYGON_FUNCTION(fieldName, new Porygon::NumericScriptType(true, false), __VA_ARGS__ )


/*!
\brief Creates an invokable function that resolves EvalValues into actual parameters and vice versa for its return type.
\param userDataTypeName The type name of the UserData its created for.
\param fieldName The field name of the original function.
\param returnType The type for which the constructor gets called.
\param ... How to resolve the calling parameters into actual values.
\returns An invokable function.
*/
#define PORYGON_PREPARE_FUNCTION(userDataTypeName, fieldName, returnType, ...) \
    static const Porygon::Evaluation::EvalValue* invoke__##fieldName(void* obj, const ScriptOptions* opts, \
            const Porygon::Evaluation::EvalValue* par[], int parameterCount){ \
        return Porygon::Evaluation::EvalValueHelper::Create(((userDataTypeName*)obj)->fieldName( \
                __VA_ARGS__ \
            ));}





#endif //PORYGONLANG_USERDATATEMPLATES_HPP
