// SwiftShader Software Renderer
//
// Copyright(c) 2005-2012 TransGaming Inc.
//
// All rights reserved. No part of this software may be copied, distributed, transmitted,
// transcribed, stored in a retrieval system, translated into any human or computer
// language by any means, or disclosed to third parties without the explicit written
// agreement of TransGaming Inc. Without such an agreement, no rights or licenses, express
// or implied, including but not limited to any patent rights, are granted to you.
//

#ifndef sw_RoutineCache_hpp
#define sw_RoutineCache_hpp

#include "LRUCache.hpp"

#include "Reactor/Reactor.hpp"

namespace sw
{
	template<class State>
	class RoutineCache : public LRUCache<State, Routine>
	{
	public:
		RoutineCache(int n, const char *precache = 0);
		~RoutineCache();

	private:
		const char *precache;
		#if defined(_WIN32)
		HMODULE precacheDLL;
		#endif
	};
}

#if defined(_WIN32)
	#include "Shader/Constants.hpp"
	#include "Reactor/DLL.hpp"
#endif

namespace sw
{
	template<class State>
	RoutineCache<State>::RoutineCache(int n, const char *precache) : LRUCache<State, Routine>(n), precache(precache)
	{
		#if defined(_WIN32)
			precacheDLL = 0;

			if(precache)
			{
				char dllName[1024]; sprintf(dllName, "%s.dll", precache);
				char dirName[1024]; sprintf(dirName, "%s.dir", precache);

				precacheDLL = LoadLibrary(dllName);
				FILE *dir = fopen(dirName, "rb");
				int ordinal = 1;

				while(precacheDLL && dir)
				{
					State state;
					int offset;
					int size;

					size_t bytes = fread(&state, 1, sizeof(State), dir);
					bytes += fread(&offset, 1, sizeof(offset), dir);
					bytes += fread(&size, 1, sizeof(size), dir);

					if(bytes != sizeof(State) + sizeof(offset) + sizeof(size))
					{
						break;
					}

					void (*routine)(void) = (void(*)(void))GetProcAddress(precacheDLL, (char*)ordinal);
					ordinal++;

					if(routine)
					{
						add(state, new Routine(routine, size, offset));
					}
				}

				if(dir)
				{
					fclose(dir);
				}
			}
		#endif
	}

	template<class State>
	RoutineCache<State>::~RoutineCache()
	{
		#if defined(_WIN32)
			char dllName[1024]; sprintf(dllName, "%s.dll", precache);
			char dirName[1024]; sprintf(dirName, "%s.dir", precache);

			if(precache)
			{
				DLL dll(dllName, &constants, sizeof(Constants));
				FILE *dir = fopen(dirName, "wb");

				for(int i = 0; i < getSize(); i++)
				{
					State &state = getKey(i);
					Routine *routine = query(state);

					if(routine)
					{
						unsigned char *buffer = (unsigned char*)routine->getBuffer();
						unsigned char *entry = (unsigned char*)routine->getEntry();
						int size = routine->getBufferSize();
						int codeSize = routine->getCodeSize();

						#ifndef _M_AMD64
							for(int j = 1; j < codeSize - 4; j++)
							{
								unsigned char modRM_SIB = entry[j - 1];
								unsigned int address = *(unsigned int*)&entry[j];

								if((modRM_SIB & 0x05) == 0x05 && (address % 4) == 0)
								{
									if(address >= (unsigned int)buffer && address < (unsigned int)entry)   // Constant stored above the function entry
									{
										dll.addRelocation(buffer, &entry[j], true);

										j += 4;
									}
								}
							}
						#else
							for(int j = 1; j < codeSize - 4; j++)
							{
								unsigned char modRM_SIB = entry[j - 1];
								uint64_t address = *(uint64_t*)&entry[j];

							//	if((modRM_SIB & 0x05) == 0x05 && (address % 4) == 0)
								{
									if(address >= (uint64_t)buffer && address < (uint64_t)entry)   // Constant stored above the function entry
									{
										dll.addRelocation(buffer, &entry[j], true);

										j += 4;
									}
								}
							}
						#endif

						dll.addFunction(buffer, entry, size);
						fwrite(&state, 1, sizeof(State), dir);
						int offset = (int)(entry - buffer);
						fwrite(&offset, 1, sizeof(offset), dir);
						fwrite(&size, 1, sizeof(size), dir);
					}
				}

				FreeLibrary(precacheDLL);

				dll.emit();
				fclose(dir);
			}
			else
			{
				FreeLibrary(precacheDLL);

				remove(dllName);
				remove(dirName);
			}
		#endif
	}
}

#endif   // sw_RoutineCache_hpp
