
#include "coreallocator/icoreallocatormacros.h"
#include "coreallocator/icoreallocator_interface.h"

#include "EAStdC/easprintf.h"
#include "eastl/allocator.h"

#include "PPMalloc/EAGeneralAllocatorDebug.h"
#include "EASmallBlockAllocator.h"

#include "job_manager/job_scheduler.h"

#include "assetstream/assetstream.h"

#include "rw/core/bigfile/handler.h"

#include <EAStdC/EASprintf.h>

#include "EABaseTypes/factory.h"
#include "EABaseTypes/stringtable.h"
#include "EABaseTypes/callermap.h"
#include "EABaseTypes/metatypes.h"

#include "EABaseTypes/functor.h"
#include "EABaseTypesJson/jsonloader.h"

#include "EABaseTypesAssetStream/assetstreamfilehandler.h"

#include "EABaseTypesLua/EABaseTypesLua.h"
#include "EABaseTypesLua/datautillua.h"

//lint -e961
// Violates MISRA Advisory Rule 19.1, only preprocessor statements and comments before '#include'
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}


class GeneralAllocator : public EA::Allocator::ICoreAllocator
{
public:
	GeneralAllocator() 
	{
		mAllocator.Init(NULL, 75000000);
		mAllocator.SetAutoHeapValidation( EA::Allocator::GeneralAllocatorDebug::kHeapValidationLevelFull, 1);
	}

	void *Alloc(size_t size, const char *name, unsigned int flags)
	{
		return mAllocator.MallocDebug(size, static_cast<int>(flags), 0u, name);
	}

	void Free(void *ptr, size_t)
	{
		mAllocator.Free(ptr);
	}

    virtual void *Alloc(size_t size, const char *name, unsigned int flags, unsigned int align, unsigned int alignOffset = 0)
	{
		return mAllocator.MallocAlignedDebug(size, static_cast<size_t>(align), static_cast<size_t>(alignOffset), static_cast<int>(flags), 0u, name);
	}

    EA::Allocator::GeneralAllocatorDebug& GetGeneralAllocator() { return mAllocator; }
	EA::Allocator::GeneralAllocatorDebug mAllocator;
};    

// LUA does a lot of small allocations, in order to keep the unit test running in a reasonable amount of time we use a small SBA
class SBAAllocator : public EA::Allocator::ICoreAllocator
{
public:
	
	SBAAllocator(EA::Allocator::ICoreAllocator* pAllocator)
		: mAllocator(pAllocator)
	{
		EA::Allocator::SmallBlockAllocator::Parameters sbaparams;
		sbaparams.mnMaxAllocSize = 0xFFF0 - static_cast<uint16_t>(EA::Allocator::SmallBlockAllocator::CoreBlockOverhead());
		sbaparams.mnCoreBlockSize = 0xFFF0;
		mSBA.Init(&sbaparams, &SBAAllocator::AllocationFunction, &SBAAllocator::FreeFunction, this);
	}

	virtual ~SBAAllocator() 
	{
		mSBA.Shutdown();
	}

	void *Alloc(size_t size, const char *name, unsigned int flags)
	{
		void* ptr = mSBA.Malloc(size);
		EA_ASSERT(ptr != NULL);
		return ptr;
	}

	void *Alloc(size_t size, const char *name, unsigned int flags, unsigned int align, unsigned int alignOffset = 0)
	{
		void* ptr = NULL;

		if(size <= 8192)
		{
			ptr = mSBA.MallocAligned(size, align);
		}
		
		if(ptr == NULL)
		{
			ptr = mAllocator->Alloc(size, name, flags, align, alignOffset);
		}

		return ptr;
	}

	void Free(void *block, size_t size=0)
	{
		if(!mSBA.FreeSafe(block, size))
		{
			mAllocator->Free(block);
		}
	}

private:

	static void* AllocationFunction(EA::Allocator::SmallBlockAllocator* pAllocator, size_t nSize, size_t nAlignment, size_t nAlignmentOffset, void* pContext)
	{
		SBAAllocator* that = static_cast<SBAAllocator*>(pContext);
		return that->mAllocator->Alloc(nSize, "SBAAllocator", EA::Allocator::MEM_PERM, nAlignment, nAlignmentOffset);
	}

	static void FreeFunction(EA::Allocator::SmallBlockAllocator* pAllocator, void* pCoreBlock, void* pContext)
	{
		SBAAllocator* that = static_cast<SBAAllocator*>(pContext);
		that->mAllocator->Free(pCoreBlock);

	}
	
	EA::Allocator::SmallBlockAllocator mSBA;
	EA::Allocator::ICoreAllocator*	mAllocator;
};

static EA::Allocator::ICoreAllocator* gDefaultAllocator;

EA::Allocator::ICoreAllocator *EA::Allocator::ICoreAllocator::GetDefaultAllocator(void)
{
	return gDefaultAllocator;
}

namespace eastl
{
	allocator* GetDefaultAllocator()
    {
        return NULL;
    }

    allocator::allocator(const char* EASTL_NAME(pName))
    {
        #if EASTL_NAME_ENABLED
            mpName = pName ? pName : EASTL_ALLOCATOR_DEFAULT_NAME;
        #endif
    }

	allocator::allocator(const allocator&, const char* EASTL_NAME(pName))
    {
        #if EASTL_NAME_ENABLED
            mpName = pName ? pName : EASTL_ALLOCATOR_DEFAULT_NAME;
        #endif
    }

	allocator::allocator(const allocator& src)
    {
        #if EASTL_NAME_ENABLED
            mpName = src.mpName ;
        #endif
    }

	allocator& allocator::operator=(const allocator& src)
    {
        // To consider: Should we copy the name as well?
        #if EASTL_NAME_ENABLED
			mpName = src.mpName;
        #endif
        return *this;
    }

    void* allocator::allocate(size_t n, int flags)
    {
		#if EASTL_NAME_ENABLED	
			return gDefaultAllocator->Alloc(n, "Sys::STL", flags);
		#else
			return gDefaultAllocator->Alloc(n, "Sys::STL", flags);
		#endif
    }

	void* allocator::allocate(size_t n, size_t alignment, size_t offset, int flags)
    {
		#if EASTL_NAME_ENABLED
			return gDefaultAllocator->Alloc(n, "Sys::STL", flags, alignment, offset);
		#else
			return gDefaultAllocator->Alloc(n, "Sys::STL", flags, alignment, offset);
		#endif
    }

	void allocator::deallocate(void* p, size_t)
    {
		gDefaultAllocator->Free(p);
    }

	const char* allocator::get_name() const
    {
        #if EASTL_NAME_ENABLED
            return mpName;
        #else
            return EASTL_ALLOCATOR_DEFAULT_NAME;
        #endif
    }

    void allocator::set_name(const char* EASTL_NAME(pName))
    {
        #if EASTL_NAME_ENABLED
            mpName = pName;
        #endif
    }

	bool operator==(const allocator&, const allocator&)
    {
		// All allocators are considered equal, as they merely use default ICoreAllocator
		return true; 
    }

	bool operator!=(const allocator&, const allocator&)
    {
		// All allocators are considered equal, as they merely use default ICoreAllocator
        return false;
    }

} // namespace eastl

int Vsnprintf8(char8_t* pDestination, size_t n, const char8_t* pFormat, va_list arguments)
{
    return EA::StdC::Vsnprintf(pDestination, n, pFormat, arguments);
}

bool gDone = false;

void Done()
{
	gDone = true;
}

void Print(const char* message)
{
	EA::StdC::Fprintf(stdout, "%s", message);
	fflush(stdout);
}

int main(int argc, char** argv)
{
	GeneralAllocator generalAllocator;
	EA::Allocator::ICoreAllocator* allocator = &generalAllocator;
	gDefaultAllocator = allocator;
	
	const int kMaxPPU = 6;

	int ppuThreads;

	int count = EA::Thread::GetProcessorCount();
	EA_ASSERT(count > 1);
	ppuThreads = ((1 << count) - 1) & ~1;

	EA::Jobs::SetAllocator(*allocator);
	EA::Jobs::JobScheduler jobScheduler;
	jobScheduler.Initialize();

	for (int j = 0; j < kMaxPPU; ++j)
	{
		if (ppuThreads & (1 << j))
		{
			EA::Jobs::JobThreadParameters jtp;        
			jtp.environment = EA::Jobs::JOB_ENVIRONMENT_LOCAL;
			jtp.processor = j;
			jobScheduler.AddThread(jtp);
		}
	}
	
	AssetStream::InitializeParameters params;
	params.allocator = allocator;
	params.jobScheduler = &jobScheduler;
	params.jobSchedulerAffinity = EA::Jobs::JOB_AFFINITY_ANY;
	AssetStream::Initialize(params);

	rw::core::filesys::Manager* rwfilesys = rw::core::filesys::Manager::GetInstance();

	rw::core::BigHandler* bigHandler = CORE_NEW(allocator, "System::FileSystem::BigHandler", EA::Allocator::MEM_PERM) rw::core::BigHandler(allocator, "data:");

	rw::core::filesys::Device* bigDevice = rwfilesys->RegisterDevice(bigHandler, rw::core::filesys::Manager::DEVICEFLAG_TYPE_VIRTUAL);
	rwfilesys->AddSearchLocation("data:", rw::core::filesys::Manager::SEARCHPATH_ADD_TO_HEAD);

	void* bigHandle = bigHandler->AddArchiveFromFile("data.big");

	EA::StringTable* stringTable = CORE_NEW(allocator, "EA::StringTable", EA::Allocator::MEM_PERM) EA::StringTable(allocator);
	EA::Types::CallerMap* callerMap = CORE_NEW(allocator, "EA::Types::CallerMap", EA::Allocator::MEM_PERM) EA::Types::CallerMap(allocator);
	EA::Types::MetaTypes* metaTypes = CORE_NEW(allocator, "EA::Types::MetaTypes", EA::Allocator::MEM_PERM) EA::Types::MetaTypes();
	EA::Types::Factory* factory = CORE_NEW(allocator, "EA::Types::Factory", EA::Allocator::MEM_PERM) EA::Types::Factory(allocator, stringTable, callerMap, metaTypes);

	EA::Types::IFileHandlerRef fileHandler = CORE_NEW(allocator, "AssetStreamFileHandler", EA::Allocator::MEM_TEMP) EA::Types::AssetStreamFileHandler(allocator, "");

	EA::Types::JsonLoader* jsonLoader = CORE_NEW(allocator, "JsonLoader", EA::Allocator::MEM_PERM) EA::Types::JsonLoader(factory, fileHandler);
			
	SBAAllocator luaAllocator(allocator);

	EA::Types::FunctionRef print = new (factory) EA::Types::Functor1<void, const char*>(factory, &Print);

	lua_State* L = EA::Lua::NewState(&luaAllocator,
									 allocator,
									 factory,
									 fileHandler,
									 jsonLoader,
									 print,
									 print);
                          
    // add in the IO library so we can read/write files in a ANSI C - like way
    lua_pushcfunction(L, luaopen_io);
	lua_pushstring(L, LUA_IOLIBNAME);
	lua_call(L, 1, 0);

	print = NULL;

	if(0 != luaL_loadstring(L,
		"local arguments = { ... }\n"
		"\n"
		"requireasync(\n"
		"	function(tool, error)\n"
		"		assert(tool ~= nil, error)\n"
		"		tool(unpack(arguments))\n"
		"	end,\n"
		"	\"data:/main.lua\")\n"
		))
	{
		EA_FAIL_MESSAGE(lua_tostring(L, -1));
	}
	
	EA::Types::FunctionRef callback = new (factory) EA::Types::Functor<void>(factory, &Done);

	EA::Lua::PushBaseType(L, callback);
    
    for(int i = 0; i < argc; ++i)
    {
        lua_pushstring(L, argv[i]);
    }

	EA::Lua::Call(L, 1 + argc, 0);
	
	do
	{
		fileHandler->Update();
	}
	while(!gDone);

	callback = NULL;
	
	lua_close(L);
	L = NULL;

	rwfilesys->UnregisterDevice(bigDevice);

	bigHandler->RemoveArchive(bigHandle);

	CORE_DELETE(allocator, bigHandler);

	CORE_DELETE(allocator, jsonLoader);

	fileHandler = NULL;
	
	metaTypes->Unload();

	CORE_DELETE(allocator, factory);
	CORE_DELETE(allocator, metaTypes);
	CORE_DELETE(allocator, callerMap);
	CORE_DELETE(allocator, stringTable);
	
	return 0;
}
