/*H************************************************************************************************/
/*!
  \File future_unittest.cpp

  \Description

  \Notes
  None.

  \Copyright
  (c) 2013   Electronic Arts Inc.

  \Version    1.0     04/02/2013 (CB) First version
  */
/************************************************************************************************H*/

#if EA_RUNTIME_TESTING

#include "EABaseTypes/factory.h"
#include "EABaseTypes/stringtable.h"
#include "EABaseTypes/callermap.h"
#include "EABaseTypes/metatypes.h"
#include "EABaseTypes/functor.h"
#include "EABaseTypes/functioncallwrappers.h"
#include "EABaseTypesAssetStream/assetstreamfilehandler.h"
#include "EABaseTypesJson/jsonloader.h"
#include "EABaseTypesFuture/future.h"

#include "unit/unit.h"
#include "coreunit/interface.h"
#include "coreallocator/icoreallocatormacros.h"

#include "coreunit/interface.h"

class FutureUnitTests : public EATest::AsyncTestSuite
{
public:

    FutureUnitTests()
    {
    }

    virtual void Initialize()
    {
        SuiteName("FutureUnitTests");
        EATEST_REGISTER_ASYNC("TestSimpleFuture", "Testing Simple Future", FutureUnitTests, TestSimpleFuture);
        EATEST_REGISTER_ASYNC("TestIndexFuture", "Testing Index Future", FutureUnitTests, TestIndexFuture);
        EATEST_REGISTER_ASYNC("TestDoubleFuture", "Testing Double Future", FutureUnitTests, TestDoubleFuture);
        EATEST_REGISTER_ASYNC("TestComposeFuture", "Testing Compose Future", FutureUnitTests, TestComposeFuture);
    }

    void Setup(void* data)
    {
        CoreUnit::UnitTestInfo* info = static_cast<CoreUnit::UnitTestInfo*>(data);
        EA::Allocator::ICoreAllocator* allocator = info->mAllocator;

        mAllocator = allocator;

        mStringTable = CORE_NEW(allocator, "EA::StringTable", EA::Allocator::MEM_PERM) EA::StringTable(allocator);
        mCallerMap = CORE_NEW(allocator, "EA::Types::CallerMap", EA::Allocator::MEM_PERM) EA::Types::CallerMap(allocator);
        mMetaTypes = CORE_NEW(allocator, "EA::Types::MetaTypes", EA::Allocator::MEM_PERM) EA::Types::MetaTypes();
        mFactory = CORE_NEW(allocator, "EA::Types::Factory", EA::Allocator::MEM_PERM) EA::Types::Factory(allocator, mStringTable, mCallerMap, mMetaTypes);
        
        mFileHandler = CORE_NEW(mAllocator, "AssetStreamFileHandler", EA::Allocator::MEM_PERM) EA::Types::AssetStreamFileHandler(mAllocator, ".");
        
        mJsonLoader = CORE_NEW(mAllocator, "JsonLoader", EA::Allocator::MEM_PERM) EA::Types::JsonLoader(mFactory, mFileHandler);
        
        mFinishTest = false;
    }

    virtual void Idle();
    
    void Teardown(void* data)
    {
        
        CORE_DELETE(mAllocator, mJsonLoader);
        mJsonLoader = NULL;

        mFileHandler = NULL;
        
        mMetaTypes->Unload();

        CORE_DELETE(mAllocator, mFactory);
        CORE_DELETE(mAllocator, mMetaTypes);
        CORE_DELETE(mAllocator, mCallerMap);
        CORE_DELETE(mAllocator, mStringTable);
    }

    void TestSimpleFuture(void* data);
    
    void TestIndexFuture(void* data);
    
    void TestDoubleFuture(void* data);
    
    void TestComposeFuture(void* data);

private:

    EA::Types::BaseTypeReturn ProcessData1(EA::Types::BaseTypeIn baseType);
    void ProcessData2(EA::Types::BaseTypeIn baseType);
    
    EA::Types::BaseTypeReturn ProcessData3(EA::Types::BaseTypeIn baseType1, EA::Types::BaseTypeIn baseType2);
    
    EA::Types::BaseTypeReturn ProcessData4(EA::Types::ArrayIn value);
    
	void DoneTest(EA::Types::BaseTypeIn value, const EA::String& error);

    void FinishTest();
    
    EA::Types::IFileHandlerRef      mFileHandler;
    EA::Types::JsonLoader*          mJsonLoader;
    EA::Allocator::ICoreAllocator*  mAllocator;
    EA::StringTable*                mStringTable;
    EA::Types::CallerMap*           mCallerMap;
    EA::Types::MetaTypes*           mMetaTypes;
    EA::Types::Factory*             mFactory;
    bool                            mFinishTest;
};

// make the unit test suite instance
FutureUnitTests FutureUnitTestsInstance;

// this declaration is so that main.cpp in the game can pretend to use it hence keeping
// this entire translation unit from being excluded from the build
EATest::TestSuite* gFutureUnitTests = &FutureUnitTestsInstance;

// *********************************************************************************************
// The test function
// *********************************************************************************************

void FutureUnitTests::TestSimpleFuture(void* data)
{
    printf("\n\n");
    
    // create a function for the future operator
    EA::Types::FunctionRef futureOperator = EA::Types::FutureAsFunction(mFactory);
   
	// create an unbound future
    EA::Types::FutureFunctionRef f1 = EA::Types::Future(mFactory);

	// future-call the load function with the unbound future as the callback and "data/test.json"
	// f1 will be resolved when the load function finishes (asynchronously)
    
    mJsonLoader->Load(f1->GetResolver(), "data/test.json");

	// make a function for the ProcessData1 function
    EA::Types::FunctionRef processData1 = mFactory->Function(this, &FutureUnitTests::ProcessData1);
    
	// future-call the ProcessData1 function with the f1 future as the argument
	// ProcessData1 will be called with the eventual value of f1
	// f2 contains a future representing the results of the call (which hasn't happened yet, probably)
    EA::Types::FutureFunctionRef f2 = futureOperator->Call<EA::Types::FutureFunctionReturn>(processData1, f1);
    
	// make a function for the ProcessData2 function
    EA::Types::FunctionRef processData2 = mFactory->Function(this, &FutureUnitTests::ProcessData2);
    
	// future-call the ProcessData2 function with the f2 future as the argument
	// ProcessData2 will be called with the eventual value of f2
	// f3 contains a future representing the results of the call (which hasn't happened yet, probably)
    EA::Types::FutureFunctionRef f3 = futureOperator->Call<EA::Types::FutureFunctionReturn>(processData2, f2);
    
	// make a function for the DoneTest function
	EA::Types::FunctionRef doneTest = mFactory->Function(this, &FutureUnitTests::DoneTest);
	
	// call the DoneTest function once f3 has been resolved - it will be passed the final value and any errors
	f3->AddCallback(doneTest);

    printf("\n\n");
}

void FutureUnitTests::TestIndexFuture(void* data)
{
    printf("\n\n");
    
    // create a function for the future operator
    EA::Types::FunctionRef futureOperator = EA::Types::FutureAsFunction(mFactory);
    
	// create an unbound future
    EA::Types::FutureFunctionRef f1 = EA::Types::Future(mFactory);

	// future-call the load function with the unbound future as the callback and "data/test.json"
	// f1 will be resolved when the load function finishes (asynchronously)    
    mJsonLoader->Load(f1->GetResolver(), "data/test.json");

	// make a function for the ProcessData1 function
    EA::Types::FunctionRef processData1 = mFactory->Function(this, &FutureUnitTests::ProcessData1);
    
	// future-call the ProcessData1 function with the f1.foobar future as the argument
	// ProcessData1 will be calle with the eventual value of f1.foobar
	// f2 contains a future representing the results of the call (which hasn't happened yet, probably)
    EA::Types::FutureFunctionRef f2 = futureOperator->Call<EA::Types::FutureFunctionReturn>(processData1, (*f1)["foobar"]);
    
	// make a function for the ProcessData2 function
    EA::Types::FunctionRef processData2 = mFactory->Function(this, &FutureUnitTests::ProcessData2);
    
	// future-call the ProcessData2 function with the f2[3] future as the argument
	// ProcessData1 will be called with the eventual value of f2[3]
	// f3 contains a future representing the results of the call (which hasn't happened yet, probably)
    EA::Types::FutureFunctionRef f3 = futureOperator->Call<EA::Types::FutureFunctionReturn>(processData2, (*f2)[3]);
    
	// make a function for the DoneTest function
	EA::Types::FunctionRef doneTest = mFactory->Function(this, &FutureUnitTests::DoneTest);
	
	// call the DoneTest function once f3 has been resolved - it will be passed the final value and any errors
	f3->AddCallback(doneTest);

    printf("\n\n");
}

void FutureUnitTests::TestDoubleFuture(void* data)
{
    printf("\n\n");
    
    // create a function for the future operator
    EA::Types::FunctionRef futureOperator = EA::Types::FutureAsFunction(mFactory);
    
	// create an unbound future
    EA::Types::FutureFunctionRef f1 = EA::Types::Future(mFactory);

	// future-call the load function with the unbound future as the callback and "data/test.json"
	// f1 will be resolved when the load function finishes (asynchronously)
	mJsonLoader->Load(f1->GetResolver(), "data/test.json");
    
    // create another unbound future
    EA::Types::FutureFunctionRef f2 = futureOperator->Call<EA::Types::FutureFunctionReturn>();

	// future-call the load function with the unbound future as the callback and "data/test1.json"
	// f2 will be resolved when the load function finishes (asynchronously)
    mJsonLoader->Load(f2->GetResolver(), "data/test1.json");

	// make a function for the ProcessData3 function
    EA::Types::FunctionRef processData3 = mFactory->Function(this, &FutureUnitTests::ProcessData3);
    
	// future-call the ProcessData3 function with the f1 and f2 futures as the arguments
	// ProcessData3 will be called with the eventual values of f1 and f2
	// f3 contains a future representing the results of the call (which hasn't happened yet, probably)
    EA::Types::FutureFunctionRef f3 = futureOperator->Call<EA::Types::FutureFunctionReturn>(processData3, f1, f2);
    
	// make a function for the DoneTest function
	EA::Types::FunctionRef doneTest = mFactory->Function(this, &FutureUnitTests::DoneTest);
	
	// call the DoneTest function once f3 has been resolved - it will be passed the final value and any errors
	f3->AddCallback(doneTest);

    printf("\n\n");
}

void FutureUnitTests::TestComposeFuture(void* data)
{
	// create a function for the future operator
	EA::Types::FunctionRef futureOperator = EA::Types::FutureAsFunction(mFactory);
    
    // create an array for the filenames
    EA::Types::ArrayRef fileNames = mFactory->Array();
    fileNames->push_back("data/test.json");
    fileNames->push_back("data/test1.json");
    fileNames->push_back("data/test2.json");
    fileNames->push_back("data/test3.json");
    fileNames->push_back("data/test4.json");
    
    // iterate over the filenames
    EA::Types::Array::iterator it = fileNames->begin();
    EA::Types::Array::iterator end = fileNames->end();
    
    EA::Types::ArrayRef files = mFactory->Array();
    
    for(; it != end; ++it)
    {
        // create an unbound future
        EA::Types::FutureFunctionRef f = EA::Types::Future(mFactory);
        
        // load the json, pass the future's resolution function as the callback
        mJsonLoader->Load(f->GetResolver(), (*it)->AsCStr());

        // stick the file name and the file data future into an array
        EA::Types::ObjectRef o = files->push_backObject();
        o->insert("fileName", (*it));
        o->insert("data", f);
    }
    
    // make a function for the ProcessData4 function
    EA::Types::FunctionRef processData4 = mFactory->Function(this, &FutureUnitTests::ProcessData4);
    
    EA::Types::FutureFunctionRef f = futureOperator->Call<EA::Types::FutureFunctionReturn>(processData4, files);
    
    // make a function for the DoneTest function
	EA::Types::FunctionRef doneTest = mFactory->Function(this, &FutureUnitTests::DoneTest);
	
	// call the DoneTest function once f has been resolved - it will be passed the final value and any errors
	f->AddCallback(doneTest);
}

/* private */ EA::Types::BaseTypeReturn FutureUnitTests::ProcessData1(EA::Types::BaseTypeIn value)
{
    EA::RawString msg(mAllocator);
    EA::Types::DumpBaseType(msg, value);
    printf("%s\n", msg.c_str());
    
    EA::Types::ArrayRef a = mFactory->Array();
    a->push_back(true);
    a->push_back("bar");
    a->push_back(4);
    EA::Types::ObjectRef o = a->push_backObject();
    o->insert("foo", false);
    o->insert("bar", 5);
    o->insert("baz", "foobar");
    
    return a;
}

/* private */ void FutureUnitTests::ProcessData2(EA::Types::BaseTypeIn value)
{
    EA::RawString msg(mAllocator);
    EA::Types::DumpBaseType(msg, value);
    printf("%s\n", msg.c_str());
}

/* private */ EA::Types::BaseTypeReturn FutureUnitTests::ProcessData3(EA::Types::BaseTypeIn baseType1, EA::Types::BaseTypeIn baseType2)
{
    EA::Types::ObjectRef o = mFactory->Object();
    o->insert("data1", baseType1);
    o->insert("data2", baseType2);
    
    return o;
}

/* private */ EA::Types::BaseTypeReturn FutureUnitTests::ProcessData4(EA::Types::ArrayIn value)
{
    EA::RawString msg(mAllocator);
    EA::Types::DumpBaseType(msg, value);
    printf("%s\n", msg.c_str());
    
    return mFactory->String("Hello, world!");
}

/* private */ void FutureUnitTests::DoneTest(EA::Types::BaseTypeIn value, const EA::String& error)
{
	EA::RawString msg(mAllocator);
    EA::Types::DumpBaseType(msg, value);
    printf("%s\n", msg.c_str());

	printf("errors: %s\n", error.c_str());
	FinishTest();
}

void FutureUnitTests::Idle()
{
    mFileHandler->Update();
    if(mFinishTest == true)
    {
        mFinishTest = false;
        EATESTContinue();
    }
}

void FutureUnitTests::FinishTest()
{
    mFinishTest = true;
}

#endif
