// TestAString.cpp
//------------------------------------------------------------------------------

// Includes
//------------------------------------------------------------------------------
#include <TestFramework/TestGroup.h>

#include "Core/FileIO/ConstMemoryStream.h"
#include "Core/FileIO/MemoryStream.h"
#include "Core/Reflection/MetaData/Meta_File.h"
#include "Core/Reflection/MetaData/Meta_Optional.h"
#include "Core/Reflection/MetaData/Meta_Path.h"
#include "Core/Reflection/Object.h"
#include "Core/Reflection/ReflectedProperty.h"
#include "Core/Strings/AStackString.h"
#include "Core/Tracing/Tracing.h"

// system
#include <memory.h>

// TestReflection
//------------------------------------------------------------------------------
class TestReflection : public TestGroup
{
private:
    DECLARE_TESTS

    void TestGetSet() const;
    void TestInheritance() const;
    void MetaData() const;
    void ArraySize() const;
};

// Register Tests
//------------------------------------------------------------------------------
REGISTER_TESTS_BEGIN( TestReflection )
    REGISTER_TEST( TestGetSet )
    REGISTER_TEST( TestInheritance )
    REGISTER_TEST( MetaData )
    REGISTER_TEST( ArraySize )
REGISTER_TESTS_END

// TestStruct
//------------------------------------------------------------------------------
struct TestStruct
{
    REFLECT_STRUCT_DECLARE( TestStruct )
public:
    TestStruct()
        : m_MyInt( 851 )
    {
    }

    uint32_t m_MyInt;
};

REFLECT_STRUCT_BEGIN_BASE( TestStruct )
    REFLECT( m_MyInt,   "MyInt",    MetaNone() )
REFLECT_END( TestStruct )

// TestObject
//------------------------------------------------------------------------------
class TestObject : public Object
{
    REFLECT_DECLARE( TestObject )
public:
    TestObject()
        : m_Float( 0.0f )
        , m_UInt8( 0 )
        , m_UInt16( 0 )
        , m_UInt32( 0 )
        , m_UInt64( 0 )
        , m_Int8( 0 )
        , m_Int16( 0 )
        , m_Int32( 0 )
        , m_Int64( 0 )
        , m_Bool( false )
        , m_AString( "" )
    {
    }

    void PopulateWithTestData()
    {
        m_Float = 99.0f;
        m_UInt8 = 42;
        m_UInt16 = 9901;
        m_UInt32 = 1000000;
        m_UInt64 = 100000000000000;
        m_Int8 = 43;
        m_Int16 = 9202;
        m_Int32 = 1000001;
        m_Int64 = 100000000000001;
        m_Bool = true;
        m_AString = "Test string.";

        m_FloatArray.Append( 111.0f );
        m_FloatArray.Append( 222.0f );
        m_FloatArray.Append( 333.0f );

        m_StructArray.SetSize( 3 );
    }

private: // ensure reflection can set private members
    friend class TestReflection;

    float m_Float;
    uint8_t m_UInt8;
    uint16_t m_UInt16;
    uint32_t m_UInt32;
    uint64_t m_UInt64;
    int8_t m_Int8;
    int16_t m_Int16;
    int32_t m_Int32;
    int64_t m_Int64;
    bool m_Bool;
    AString m_AString;

    TestStruct m_TestStruct;

    Array<float> m_FloatArray;

    Array<TestStruct> m_StructArray;
};

REFLECT_BEGIN( TestObject, Object, MetaNone() )
    REFLECT( m_Float,   "Float",    MetaNone() )
    REFLECT( m_UInt8,   "UInt8",    MetaNone() )
    REFLECT( m_UInt16,  "UInt16",   MetaNone() )
    REFLECT( m_UInt32,  "UInt32",   MetaNone() )
    REFLECT( m_UInt64,  "UInt64",   MetaNone() )
    REFLECT( m_Int8,    "Int8",     MetaNone() )
    REFLECT( m_Int16,   "Int16",    MetaNone() )
    REFLECT( m_Int32,   "Int32",    MetaNone() )
    REFLECT( m_Int64,   "Int64",    MetaNone() )
    REFLECT( m_Bool,    "Bool",     MetaNone() )
    REFLECT( m_AString, "AString",  MetaNone() )
    REFLECT_STRUCT( m_TestStruct,   "TestStruct",   TestStruct, MetaNone() )
    REFLECT_ARRAY( m_FloatArray, "FloatArray", MetaNone() )
    REFLECT_ARRAY_OF_STRUCT( m_StructArray, "StructArray", TestStruct, MetaNone() )
REFLECT_END( TestObject )

// TestGetSet
//------------------------------------------------------------------------------
void TestReflection::TestGetSet() const
{
    TestObject o;
    const ReflectionInfo * info = o.GetReflectionInfoV();

#define CHECK( name, member, type, value ) \
    { \
        TEST_ASSERT( info->SetProperty( &o, name, (type)value ) ); \
        TEST_ASSERT( o.member == value ); \
        type v; \
        TEST_ASSERT( info->GetProperty( &o, name, &v ) ); \
        TEST_ASSERT( v == value ); \
    }

    CHECK( "Float", m_Float, float, 3.0f )
    CHECK( "UInt8", m_UInt8, uint8_t, 7 )
    CHECK( "UInt16", m_UInt16, uint16_t, 10101 )
    CHECK( "UInt32", m_UInt32, uint32_t, 0x12345678 )
    CHECK( "UInt64", m_UInt64, uint64_t, 0x8765432112345678 )
    CHECK( "Int8", m_Int8, int8_t, 7 )
    CHECK( "Int16", m_Int16, int16_t, 10101 )
    CHECK( "Int32", m_Int32, int32_t, 0x12345678 )
    CHECK( "Int64", m_Int64, int64_t, 0x7765432112345678 )
    CHECK( "Bool", m_Bool, bool, true )
    CHECK( "AString", m_AString, AString, AString( "hello" ) )

#undef CHECK
}

// TestInheritance
//------------------------------------------------------------------------------
class BaseClass : public Object
{
    REFLECT_DECLARE( BaseClass )
public:
    BaseClass()
        : m_A( -1 )
    {
    }
    int m_A;
};

class DerivedClass : public BaseClass
{
    REFLECT_DECLARE( DerivedClass )
public:
    DerivedClass()
        : m_B( -1 )
    {
    }
    int m_B;
};

REFLECT_BEGIN( BaseClass, Object, MetaNone() )
    REFLECT( m_A, "a", MetaNone() )
REFLECT_END( BaseClass )

REFLECT_BEGIN( DerivedClass, BaseClass, MetaNone() )
    REFLECT( m_B, "b", MetaNone() )
REFLECT_END( DerivedClass )

void TestReflection::TestInheritance() const
{
    // Create an object with inheritance
    DerivedClass obj;

    // Set property on class
    const ReflectionInfo * ri = obj.GetReflectionInfoV();
    const int bValue = 200;
    TEST_ASSERT( ri->SetProperty( &obj, "b", bValue ) );
    int bValueGet = 0;
    TEST_ASSERT( ri->GetProperty( &obj, "b", &bValueGet ) );
    TEST_ASSERT( bValue == bValueGet );

    // Set property on base class
    const int aValue = 100;
    TEST_ASSERT( ri->SetProperty( &obj, "a", aValue ) );
    int aValueGet = 0;
    TEST_ASSERT( ri->GetProperty( &obj, "a", &aValueGet ) );
    TEST_ASSERT( aValue == aValueGet );
}

// MetaData
//------------------------------------------------------------------------------
class ObjectWithMetaData : public Object
{
    REFLECT_DECLARE( ObjectWithMetaData )
public:
    uint32_t m_Property = 0;
};

REFLECT_BEGIN( ObjectWithMetaData, Object, MetaFile() + MetaOptional() + MetaPath() )
    REFLECT( m_Property, "Property", MetaFile() + MetaOptional() + MetaPath() )
REFLECT_END( ObjectWithMetaData )

void TestReflection::MetaData() const
{
    ObjectWithMetaData obj;
    const ReflectionInfo * ri = obj.GetReflectionInfoV();

    // Check all MetaData is present on object
    TEST_ASSERT( ri->HasMetaData<Meta_File>() );
    TEST_ASSERT( ri->HasMetaData<Meta_Optional>() );
    TEST_ASSERT( ri->HasMetaData<Meta_Path>() );

    // Check all MetaData is present on property
    const ReflectedProperty * rp = ri->GetReflectedProperty( AStackString( "Property" ) );
    TEST_ASSERT( rp->HasMetaData<Meta_File>() );
    TEST_ASSERT( rp->HasMetaData<Meta_Optional>() );
    TEST_ASSERT( rp->HasMetaData<Meta_Path>() );
}

// ArraySize
//------------------------------------------------------------------------------
void TestReflection::ArraySize() const
{
    // The reflection system makes assumptions about Array's implementation
    // (how it records the array size) which need to be updated if changed.
    // This test should fail if our assumptions are invalidated
    TestObject o;
    const ReflectionInfo * ri = TestObject::GetReflectionInfoS();
    const ReflectedProperty * rp = ri->GetReflectedProperty( AStackString( "StructArray" ) );
    const ReflectedPropertyStruct * rps = (const ReflectedPropertyStruct *)rp;
    TEST_ASSERT( rps->GetArraySize( &o ) == 0 );
    o.m_StructArray.SetSize( 4 );
    TEST_ASSERT( rps->GetArraySize( &o ) == 4 );
}

//------------------------------------------------------------------------------
