/*     Copyright 2019 Diligent Graphics LLC
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PROPRIETARY RIGHTS.
 *
 *  In no event and under no legal theory, whether in tort (including negligence), 
 *  contract, or otherwise, unless required by applicable law (such as deliberate 
 *  and grossly negligent acts) or agreed to in writing, shall any Contributor be
 *  liable for any damages, including any direct, indirect, special, incidental, 
 *  or consequential damages of any character arising as a result of this License or 
 *  out of the use or inability to use the software (including but not limited to damages 
 *  for loss of goodwill, work stoppage, computer failure or malfunction, or any and 
 *  all other commercial damages or losses), even if such Contributor has been advised 
 *  of the possibility of such damages.
 */

#pragma once

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include "EnumMappings.h"

namespace Diligent
{
    template< typename ValueType>
    ValueType ReadValueFromLua( lua_State *L, int Index )
    {
        UNSUPPORTED( "Type is not supported" );
        return static_cast<ValueType>(0);
    }
    template<>int ReadValueFromLua<int>( lua_State *L, int Index );
    template<>double ReadValueFromLua<double>( lua_State *L, int Index );
    template<>float ReadValueFromLua<float>( lua_State *L, int Index );
    template<>String ReadValueFromLua<String>( lua_State *L, int Index );
    template<>const Char* ReadValueFromLua<const Char*>( lua_State *L, int Index );
    template<>Bool ReadValueFromLua<Bool>( lua_State *L, int Index );
    template<>Uint32 ReadValueFromLua<Uint32>( lua_State *L, int Index );
    template<>Uint8 ReadValueFromLua<Uint8>( lua_State *L, int Index );


    inline void CheckType( lua_State *L, int Index, int ExpectedType )
    {
        auto Type = lua_type( L, Index );
        if( Type != ExpectedType )
        {
            auto TypeName = lua_typename( L, Type );
            auto ExpectedTypeName = lua_typename( L, ExpectedType );
            auto Param = lua_tostring( L, Index );
            SCRIPT_PARSING_ERROR( L, "Incorrect argument: \"", Param ? Param : "<Unknown>", "\". \"", ExpectedTypeName, "\" is expected, while \"", TypeName, "\" is provided." );
        }
    }

    // Special version of luaL_testudata() which takes an array of allowed metatables
    void *luaL_testudata( lua_State *L, int ud, const std::vector<String> &MetatableNames );

    template<typename DataType, typename MetatableNameType>
    DataType GetUserData( lua_State *L, int Index, MetatableNameType MetatableName )
    {
        CheckType( L, Index, LUA_TUSERDATA );
        auto pUserData = luaL_testudata( L, Index, MetatableName );
        if( pUserData == nullptr )
        {
            auto Type = lua_type( L, Index );
            auto TypeName = lua_typename( L, Type );
            SCRIPT_PARSING_ERROR( L, "Bad argument #", Index, ". User data with metatable \"", MetatableName, "\" is expected. \"", TypeName, "\" is provided." );
        }
        return reinterpret_cast<DataType>(pUserData);
    }

    template<typename DataType>
    DataType GetGlobalObject( lua_State *L, const Char* ObjectName, const Char* MetatableName )
    {
        INIT_LUA_STACK_TRACKING( L );
        // Pushes onto the stack the value of the given global name
        lua_getglobal( L, ObjectName );                             // -0 | +1 -> +1
        auto pData = GetUserData<DataType>( L, -1, MetatableName ); // -0 | +0 -> 0
        lua_pop( L, 1 );                                            // -1 | +0 -> -1
        CHECK_LUA_STACK_HEIGHT();
        return pData;
    }



    template<typename Type>
    void PushValue( lua_State *L, Type )
    {
        UNSUPPORTED( "Type is not supported" );
    }
    // Forward declarations of template specializations
    template<> void PushValue<double>( lua_State *L, double Val );
    template<> void PushValue<const float&>( lua_State *L, const float& Val );
    template<> void PushValue<const Int32&>( lua_State *L, const Int32& Val );
    template<> void PushValue<const Uint32&>( lua_State *L, const Uint32& Val );
    template<> void PushValue<const Uint8&>( lua_State *L, const Uint8& Val );
    template<> void PushValue<const Char*>( lua_State *L, const Char* Val );
    template<> void PushValue<const Char* const&>( lua_State *L, const Char* const& Val );
    template<> void PushValue<const String&>( lua_State *L, const String& Val );
    template<> void PushValue<bool>( lua_State *L, bool Val );
    template<> void PushValue<const bool &>( lua_State *L, const bool &Val );


    class MemberBinderBase
    {
    public:
        MemberBinderBase( size_t MemberOffset ) :
            m_MemberOffset( MemberOffset )
        {}
        virtual ~MemberBinderBase(){}
        virtual void GetValue( lua_State *L, const void* pBasePointer ) = 0;
        virtual void SetValue( lua_State *L, int Index, void* pBasePointer ) = 0;

    protected:
        const size_t m_MemberOffset;
    };
    typedef std::unordered_map<HashMapStringKey, std::unique_ptr<MemberBinderBase>, HashMapStringKey::Hasher> BindingsMapType;

#define DEFINE_BINDER_EX(BindingsMap, Struct, Member, type, ValidationFunc) \
    do{\
        auto *pNewBinder = new MemberBinder<type>( offsetof( Struct, Member ), ValidationFunc ); \
        /* No need to make a copy of #Member since it is constant string.  */                     \
        /* HashMapStringKey will simply keep pointer to it                 */                     \
        BindingsMap.insert( std::make_pair( #Member, std::unique_ptr<MemberBinderBase>(pNewBinder) ) ); \
    }while(false)

#define DEFINE_BINDER(BindingsMap, Struct, Member) DEFINE_BINDER_EX(BindingsMap, Struct, Member, decltype(Struct::Member), Validator<decltype(Struct::Member)>() )

    template<typename ValueType>
    void SkipValidationFunc( const ValueType & )
    {
        // Do nothing
    }

    template<typename ValueType>
    class Validator
    {
    public:
        typedef void( *ValidationFuncType )(const ValueType &);
        Validator( const char *pParameterName, ValueType MinValue, ValueType MaxValue ) :
            m_pParameterName( pParameterName ),
            m_MinValue( MinValue ),
            m_MaxValue( MaxValue ),
            m_ValidationFunc( nullptr )
        {}

        Validator( ValidationFuncType ValidationFunc = SkipValidationFunc<ValueType> ) :
            m_pParameterName( nullptr ),
            m_MinValue( ValueType() ),
            m_MaxValue( ValueType() ),
            m_ValidationFunc( ValidationFunc )
        {}

        void SetParameterName( const char *NewName ){ m_pParameterName = NewName; }

        void operator()( lua_State *L, const ValueType &Value )const
        {
            if( m_ValidationFunc )
            {
                m_ValidationFunc( Value );
            }
            else
            {
                if( Value < m_MinValue || Value > m_MaxValue )
                {
                    SCRIPT_PARSING_ERROR( L, "Parameter '", m_pParameterName, "' (", Value, ") is out of range [", m_MinValue, ",", m_MaxValue, "]\n" );
                }
            }
        }

    private:
        const char *m_pParameterName;
        ValueType m_MinValue, m_MaxValue;
        ValidationFuncType m_ValidationFunc;
    };

    template<>
    class Validator < Bool >
    {
    public:
        void operator()( lua_State *L, const Bool & )const
        {
            // Do nothing
        }
    };
    
    template<typename MemberType>
    MemberType& GetMemberByOffest( void* pBasePointer, size_t Offset )
    {
        return *(reinterpret_cast<MemberType*>(reinterpret_cast<char*>(pBasePointer)+Offset));
    }

    template<typename MemberType>
    const MemberType& GetMemberByOffest( const void* pBasePointer, size_t Offset )
    {
        return *(reinterpret_cast<const MemberType*>(reinterpret_cast<const char*>(pBasePointer)+Offset));
    }

    template<typename MemberType>
    class MemberBinder : public MemberBinderBase
    {
    public:
        MemberBinder( size_t MemberOffset, Validator<MemberType> Validator ) :
            MemberBinderBase( MemberOffset ),
            m_Validator( Validator )
        {}

        virtual void GetValue( lua_State *L, const void* pBasePointer )
        {
            const auto &Value = GetMemberByOffest<MemberType>(pBasePointer, m_MemberOffset);
            PushValue<const MemberType &>( L, Value );
        }

        virtual void SetValue( lua_State *L, int Index, void* pBasePointer )
        {
            auto Value = ReadValueFromLua<MemberType>( L, Index );
            m_Validator( L, Value );
            GetMemberByOffest<MemberType>( pBasePointer, m_MemberOffset ) = Value;
        }
    protected:
        Validator<MemberType> m_Validator;
    };


    template<typename TableElementParser>
    inline void ParseLuaTable( lua_State *L, int Index, void* pBasePointer, TableElementParser ElemParser )
    {
        CheckType( L, Index, LUA_TTABLE );

        lua_pushnil( L );  // first key which will be popped out in the first call to lua_next()
        // lua_next() pops a key from the stack, and pushes a key–value pair from the table at the given index 
        // (the "next" pair after the given key). If there are no more elements in the table, then lua_next returns 
        // 0 (and pushes nothing).
        if( Index < 0 )
            --Index;
        while( lua_next( L, Index ) != 0 )
        {
            //  Key is now at index -2 and Value is at index -1
            auto IsString = lua_isstring( L, -2 );
            if( !IsString )
            {
                SCRIPT_PARSING_ERROR( L, "Table key value must be string")
            }
            // NOTE: the lua_tostring function returns a pointer to an internal copy of the string. 
            // The string is always zero-terminated and Lua ensures that this pointer is valid as long 
            // as the corresponding value is in the stack. 
            auto Key = lua_tostring( L, -2 );
            ElemParser(-1, pBasePointer, Key);

            // Pop value from the stack, but KEEP Key for the next iteration
            lua_pop( L, 1 );
        }
    }

    inline void ParseLuaTable( lua_State *L, int Index, void* pBasePointer, BindingsMapType &Bindings )
    {
        ParseLuaTable( L, Index, pBasePointer, 
                       [&](int Index, void* pBasePointer, const char *Key)
                        {
                            auto Binding = Bindings.find( Key );
                            if( Binding != Bindings.end() )
                            {
                                Binding->second->SetValue( L, Index, pBasePointer );
                            }
                            else
                            {
                                SCRIPT_PARSING_ERROR( L, "Unknown Member \"", Key, '\"' );
                            }
                        }
                     );
    }

    template<class ArrayElemParser>
    inline void ParseLuaArray( lua_State *L, int Index, void* pBasePointer, ArrayElemParser ElemParser )
    {
        CheckType( L, Index, LUA_TTABLE );

        lua_pushnil( L );  // first key which will be popped out in the first call to lua_next()
        // lua_next() pops a key from the stack, and pushes a key–value pair from the table at the given index 
        // (the "next" pair after the given key). If there are no more elements in the table, then lua_next returns 
        // 0 (and pushes nothing).
        if( Index < 0 )
            --Index;
        while( lua_next( L, Index ) != 0 )
        {
            //  Key is now at index -2 and Value is at index -1
            CheckType( L, -2, LUA_TNUMBER );

            auto NewArrayIndex = static_cast<int>( lua_tointeger( L, -2 ) );
            ElemParser( pBasePointer, -1, NewArrayIndex );

            // Pop value from the stack, but KEEP Key for the next iteration
            lua_pop( L, 1 );
        }
    }

    inline void PushLuaTable( lua_State *L, const void* pBasePointer, BindingsMapType &Bindings )
    {
        lua_newtable( L );
        for( auto it = Bindings.begin(); it != Bindings.end(); ++it )
        {
            const auto &Field = it->first.GetStr();
            lua_pushstring( L, Field );
            it->second->GetValue( L, pBasePointer );
            lua_settable( L, -3 ); // Stack: 0
        }
    }

    template<typename ItType, class ElemPushAlg>
    void PushLuaArray( lua_State *L, ItType Begin, ItType End, ElemPushAlg PushAlg )
    {
        lua_newtable( L );
        int ArrayInd = 1; // Lua arrays are 1-based
        for( auto it = Begin; it != End; ++it, ++ArrayInd )
        {
            lua_pushnumber( L, ArrayInd );  // -0 | +1 -> +1
            PushAlg( *it );                 // -0 | +1 -> +1
            lua_settable( L, -3 );          // -2 | +0 -> -2
        }
    }
    
    template<typename ArrayType, class ElemPushAlg>
    void PushLuaArray( lua_State *L, const ArrayType& Arr, ElemPushAlg PushAlg )
    {
        PushLuaArray( L, Arr.begin(), Arr.end(), PushAlg );
    }

    inline void PushField( lua_State *L, const void* pBasePointer, const Char *Field, BindingsMapType &Bindings )
    {
        auto It = Bindings.find( Field );
        if( It != Bindings.end() )
        {
            It->second->GetValue( L, pBasePointer );
        }
        else
        {
            SCRIPT_PARSING_ERROR( L, "Unknown Member \"", Field, '\"' );
        }
    }

    inline void UpdateField( lua_State *L, int Index, void* pBasePointer, const Char *Field, BindingsMapType &Bindings )
    {
        auto It = Bindings.find( Field );
        if( It != Bindings.end() )
        {
            It->second->SetValue( L, Index, pBasePointer );
        }
        else
        {
            SCRIPT_PARSING_ERROR( L, "Unknown Member \"", Field, '\"' );
        }
    }

    class RGBALoader; // Used only as a template switch
    template<>
    class MemberBinder<RGBALoader> : public MemberBinderBase
    {
    public:
        MemberBinder( size_t MemberOffset, size_t Dummy ) :
            MemberBinderBase( MemberOffset )
        {
            const char* Members[] = { "r", "g", "b", "a" };
            for( int c = 0; c < 4; ++c )
            {
                auto *pNewBinder = new MemberBinder<Float32>( MemberOffset + sizeof( Float32 )*c, Validator<float>() );
                // No need to make a copy of Members[c] since it is constant string.
                // HashMapStringKey will simply keep pointer to it
                Bindings.insert( std::make_pair( Members[c], std::unique_ptr<MemberBinderBase>( pNewBinder ) ) );
            }
        }
        virtual void GetValue( lua_State *L, const void* pBasePointer )
        {
            PushLuaTable( L, pBasePointer, Bindings );
        }

        virtual void SetValue( lua_State *L, int Index, void* pBasePointer )
        {
            ParseLuaTable( L, Index, pBasePointer, Bindings );
        }
    private:
        BindingsMapType Bindings;
    };


    template< typename EnumType >
    String GetEnumMappingsString( const EnumMapping<EnumType> &EnumMapping )
    {
        String Values;
        bool bFirst = true;
        for( auto it = EnumMapping.m_Str2ValMap.begin(); it != EnumMapping.m_Str2ValMap.end(); ++it )
        {
            if( !bFirst )
                Values += ", ";
            Values += '\"';
            Values.append( it->first.GetStr() );
            Values += '\"';
            bFirst = false;
        }
        return Values;
    }

    template<typename EnumType>
    class EnumMemberBinder : public MemberBinderBase
    {
    public:
        EnumMemberBinder( size_t MemberOffset, const Char* MemberName, const EnumMapping<EnumType> &EnumMapping ) :
            MemberBinderBase( MemberOffset ),
            m_MemberName( MemberName ),
            m_EnumMapping( EnumMapping )
        {
        }

        virtual void GetValue( lua_State *L, const void* pBasePointer )
        {
            const auto &Val = GetMemberByOffest<EnumType>( pBasePointer, m_MemberOffset );
            auto It = m_EnumMapping.m_Val2StrMap.find( Val );
            if( It != m_EnumMapping.m_Val2StrMap.end() )
            {
                const String& StrVal = It->second;
                PushValue<const String&>( L, StrVal );
            }
            else
            {
                UNEXPECTED( "Enum value (", static_cast<Int32>(Val), ") not found in the map" );
                SCRIPT_PARSING_ERROR( L, "Enum value (", static_cast<Int32>(Val), ") not found in the map" );
            }
        }

        virtual void SetValue( lua_State *L, int Index, void* pBasePointer )
        {
            auto Str = ReadValueFromLua<String>( L, Index );
            auto It = m_EnumMapping.m_Str2ValMap.find( Str.c_str() );
            if( It != m_EnumMapping.m_Str2ValMap.end() )
            {
                auto Val = It->second;
                GetMemberByOffest<EnumType>(pBasePointer, m_MemberOffset) = Val;
            }
            else
            {
                String AllowableValues = GetEnumMappingsString<EnumType>( m_EnumMapping );
                SCRIPT_PARSING_ERROR( L, "Unknown value (\"", Str, "\") provided for parameter ", m_MemberName, ". Only the following values are allowed:\n", AllowableValues );
            }
        }

    private:
        const Char *m_MemberName;
        const EnumMapping<EnumType> &m_EnumMapping;
    };

#define DEFINE_ENUM_BINDER(BindingsMap, Struct, Member, EnumMapping ) \
    do{\
        auto *pNewBinder = new EnumMemberBinder<decltype(Struct::Member)>( offsetof( Struct, Member ), #Member, EnumMapping ); \
        /* No need to make a copy of #Member since it is constant string.  */                               \
        /* HashMapStringKey will simply keep pointer to it                 */                               \
        BindingsMap.insert( std::make_pair( #Member, std::unique_ptr<MemberBinderBase>(pNewBinder) ) ); \
    }while(false)

    template< typename EnumType, typename FlagsType = typename std::underlying_type<EnumType>::type >
    class FlagsLoader : public MemberBinderBase
    {
    public:
        FlagsLoader( size_t MemberOffset, const Char* MemberName, const EnumMapping<EnumType> &EnumMapping ) :
            MemberBinderBase( MemberOffset ),
            m_MemberName( MemberName ),
            m_EnumMapping( EnumMapping )
        {
        }
        virtual void GetValue( lua_State *L, const void* pBasePointer )
        {
            auto Flags = GetMemberByOffest<FlagsType>( pBasePointer, m_MemberOffset );
            lua_newtable( L );
            int ArrayInd = 1;
            for( auto it = m_EnumMapping.m_Val2StrMap.begin(); it != m_EnumMapping.m_Val2StrMap.end(); ++it )
            {
                if( (it->first != 0 && static_cast<EnumType>(Flags & it->first) == it->first) ||
                    (Flags == 0 && it->first == 0) )
                {
                    lua_pushnumber( L, ArrayInd );              // -0 | +1 -> +1
                    PushValue<const String&>( L, it->second );  // -0 | +1 -> +1
                    lua_settable( L, -3 );                      // -2 | +0 -> -2
                    ++ArrayInd;
                }
            }
        }

        virtual void SetValue( lua_State *L, int Index, void* pBasePointer )
        {
            FlagsType Flags = static_cast<FlagsType>(0);
            if( lua_isnumber( L, Index ) )
            {
                Flags = static_cast<FlagsType>( ReadValueFromLua<Uint32>( L, Index ) );
            }
            else if( lua_isstring( L, Index ) )
            {
                Flags = ReadFlag( L, Index );
            }
            else if( lua_istable( L, Index ) )
            {
                ParseLuaArray( L, Index, pBasePointer, [ &]( void* _pBasePointer, int StackIndex, int NewArrayIndex )
                {
                    auto CurrFlag = ReadFlag( L, StackIndex );
                    Flags |= CurrFlag;
                }
                );
            }
            else
            {
                SCRIPT_PARSING_ERROR( L, m_MemberName, "must be specified as a single string or an array of strings." );
            }
            GetMemberByOffest<FlagsType>( pBasePointer, m_MemberOffset ) = Flags;
        }

    private:
        FlagsType ReadFlag( lua_State *L, int StackIndex )
        {
            auto CurrFlagName = ReadValueFromLua<const Char *>( L, StackIndex );
            auto It = m_EnumMapping.m_Str2ValMap.find( CurrFlagName );
            if( It != m_EnumMapping.m_Str2ValMap.end() )
            {
                return It->second;
            }
            else
            {
                String AllowableValues = GetEnumMappingsString<EnumType>( m_EnumMapping );
                SCRIPT_PARSING_ERROR( L, "Unknown flag (\"", CurrFlagName, "\") provided for parameter ", m_MemberName, ". Only the following flags are allowed:\n", AllowableValues );
                return static_cast<FlagsType>(0);
            }
        }

        const Char *m_MemberName;
        const EnumMapping<EnumType> &m_EnumMapping;
    };
#define DEFINE_FLAGS_BINDER(BindingsMap, Struct, Member, type, EnumMapping ) \
    do{\
        auto *pNewBinder = new FlagsLoader<type, decltype(Struct::Member)>( offsetof( Struct, Member ), #Member, EnumMapping ); \
        /* No need to make a copy of #Member since it is constant string.  */                          \
        /* HashMapStringKey will simply keep pointer to it                 */                          \
        BindingsMap.insert( std::make_pair( #Member, std::unique_ptr<MemberBinderBase>(pNewBinder) ) ); \
    }while(false)


    template<typename FieldType>
    void SetTableField( lua_State *L, const char *FieldName, int TableStackIndex, FieldType Value )
    {
        INIT_LUA_STACK_TRACKING( L );

        lua_pushstring( L, FieldName );   // -0 | +1 -> +1
        PushValue<FieldType>( L, Value ); // -0 | +1 -> +1

        // lua_settable() does the equivalent to t[k] = v, where t is the value at the given index, 
        // v is the value at the top of the stack (-1), and k is the value just below the top (-2)
        // The function pops both the key and the value from the stack. As in Lua, this function may 
        // trigger a metamethod for the "newindex" event
        lua_settable( L, TableStackIndex - 2 ); // -2 | +0 -> -2

        CHECK_LUA_STACK_HEIGHT();
    }

    class NumericArrayLoader
    {
        public:
            NumericArrayLoader();
            void LoadArray( lua_State *L, int StackIndex, std::vector< Uint8 >& RawData );
        private:
            EnumMapping<VALUE_TYPE> m_ValueTypeEnumMapping;
            EnumMemberBinder<VALUE_TYPE> m_ValueTypeBinder;
            typedef void (*ParseNumericArrayFuncType)(lua_State *L, int StackIndex, std::vector< Uint8 >& RawData);
            std::unordered_map< VALUE_TYPE, ParseNumericArrayFuncType, std::hash<std::underlying_type<VALUE_TYPE>::type> > m_ParseFuncJumpTbl;
    };


    template<typename EngineObjectType>
    class EngineObjectPtrLoader
    {
    public:
        typedef EngineObjectType ObjectType;
    };

    template<typename EngineObjectType>
    class MemberBinder< EngineObjectPtrLoader<EngineObjectType> > : public MemberBinderBase
    {
    public:
        typedef typename EngineObjectPtrLoader<EngineObjectType>::ObjectType ObjectType;

        MemberBinder( size_t MemberOffset, const std::vector<String> &_Metatables ) :
            MemberBinderBase( MemberOffset ),
            Metatables( _Metatables )
        {
        }
        
        virtual void GetValue( lua_State *L, const void* pBasePointer )
        {
            VERIFY( Metatables.size() == 1, "Ambiguous metatable" );
            const auto& MetatableName = *Metatables.begin();
            auto pObject = GetMemberByOffest<ObjectType*>( pBasePointer, m_MemberOffset );
            if( pObject )
            {
                auto ppNewObject = reinterpret_cast<ObjectType**>(lua_newuserdata( L, sizeof( ObjectType* ) ));
                *ppNewObject = pObject;
                pObject->AddRef();
                // Push onto the stack the metatable associated with name given in the registry
                luaL_getmetatable( L, MetatableName.c_str() );                  // -0 | +1 -> +1
                // Pop a table from the top of the stack and set it as the new metatable 
                // for the value at the given index (which is where the new user datum is)
                lua_setmetatable( L, -2 );                                      // -1 | +0 -> -1
            }
            else
            {
                lua_pushnil(L);
            }
        }

        virtual void SetValue( lua_State *L, int Index, void* pBasePointer )
        {
            ObjectType *pObject = nullptr;
            pObject = *GetUserData<ObjectType**>( L, -1, Metatables );
            GetMemberByOffest<ObjectType*>( pBasePointer, m_MemberOffset ) = pObject;
        }
    private:
        BindingsMapType Bindings;
        std::vector<String> Metatables;
    };

    // None of the graphics engine structures (such as BufferDesc or TextureDesc)
    // provide storage for strings, but only contain const Char* pointers.
    // The script wraps every such structure to provide the storage
    // This binder handles such strings
    class BufferedStringBinder : public MemberBinderBase
    {
    public:
        BufferedStringBinder( 
                size_t StringPtrOffset, // This is the offset of the const Char* pointer
                size_t StringBuffOffset // This is the offset of the buffer that contains string data
            ) :
            MemberBinderBase( StringBuffOffset ),
            m_StringPtrOffset( StringPtrOffset ) 
        {
        }

        virtual void GetValue( lua_State *L, const void* pBasePointer )
        {
            // Always use const Char* pointer to push value to Lua as there 
            // might be no valid buffer
            auto *StringPtr = GetMemberByOffest<const Char*>( pBasePointer, m_StringPtrOffset );
            PushValue<const Char*>( L, StringPtr );
        }

        virtual void SetValue( lua_State *L, int Index, void* pBasePointer )
        {
            // When reading the string from Lua, we need to make
            // sure that the string pointer contains the right data
            auto SrcString = ReadValueFromLua<String>( L, Index );
            // Set the const Char* pointer
            auto &DstStrBuff = GetMemberByOffest<String>( pBasePointer, m_MemberOffset );
            auto &DstStringPtr = GetMemberByOffest<const Char*>( pBasePointer, m_StringPtrOffset );
            DstStrBuff = SrcString;
            DstStringPtr = DstStrBuff.data();
        }
    protected:
        size_t m_StringPtrOffset;
    };

#define DEFINE_BUFFERED_STRING_BINDER(BindingsMap, Struct, StringPtr, StringBuffer) \
    {\
        auto *pNewBinder = new BufferedStringBinder( offsetof( Struct, StringPtr ), offsetof( Struct, StringBuffer ) ); \
        /* No need to make a copy of #Member since it is constant string.  */                               \
        /* HashMapStringKey will simply keep pointer to it                 */                               \
        BindingsMap.insert( std::make_pair( #StringPtr, std::unique_ptr<MemberBinderBase>(pNewBinder) ) ); \
    }

    // Object description wrapper that provides storage for
    // the Name field
    template<typename ObjectDescType>
    struct ObjectDescWrapper : ObjectDescType
    {
        String NameBuffer;
    };
}
