#include "WhittedRendererWorker.h"
#include "WhittedRenderer.h"
#include "VRUT_NsightCustomEvents.h"

using namespace VRUT;

WhittedRendererWorker::WhittedRendererWorker(WhittedRenderer * renderer, int myTID) : renderer(renderer), myTID(myTID), exit(false), mode(NORMAL)
{
	inputRayStream = (RayStream*) VrutAlignedMalloc(sizeof(RayStream), 32);
	outputRayStream = (RayStream*) VrutAlignedMalloc(sizeof(RayStream), 32);
	shadowRayStream = (RayStream*) VrutAlignedMalloc(sizeof(RayStream), 32);
	new (inputRayStream) RayStream();
	new (outputRayStream) RayStream();
	new (shadowRayStream) RayStream();
}

WhittedRendererWorker::~WhittedRendererWorker()
{
	// the thread is being destroyed; make sure not to leave dangling pointers around
	renderer->workers[myTID] = nullptr;
	VrutAlignedFree(inputRayStream);
	VrutAlignedFree(outputRayStream);
	VrutAlignedFree(shadowRayStream);
}

wxThread::ExitCode WhittedRendererWorker::Entry()
{
#if wxCHECK_VERSION(3, 1, 0)
#ifdef __WXMSW__
	//we set affinity mask for worker threads, so we will minimize threads migration
	DWORD_PTR err = SetThreadAffinityMask(this->MSWGetHandle(), ((DWORD_PTR)1) << myTID);
	if (err == 0)
	{
		DWORD err2 = GetLastError();
		LOG(wxString::Format(wxT("WhittedRendererWorker: SetThreadAffinityMask error %i"), err2));
	}
#endif
#endif
	while (true)
	{
		PUSH_RANGE("WRW semaphore.Wait()", 4);
		semaphore.Wait();
		POP_RANGE;
		if (exit)
		{
			return 0;
		}

		PUSH_RANGE("WRW BuildGeoAcc", 5);
		// build geometric accelerator
		while (!renderer->rerender && renderer->BuildGeoAcc())
			;
		POP_RANGE;

		const int sceneID = renderer->scene->GetID();

		// while render image
		if (mode == NORMAL || mode == MASTER)
		{
			// max number of remote tiles per rendering client thread
			const int maxRemoteTiles = renderer->remoteTiles;

			// this is the simple normal code loop
			while (!renderer->rerender)
			{
				unsigned int tileIndex = -1;
				PUSH_RANGE("WRW GetTile", 2);
				if (!renderer->GetTile(tileIndex))
				{
					POP_RANGE;
					break; // a new tile could not be allocated
				}
				POP_RANGE;
				if (tileIndex != -1)
				{
					PUSH_RANGE("WRW RenderTile", 6);
					if ((renderer->remoteTilesCount < std::min(maxRemoteTiles, int(renderer->tileCount))))
					{
						Tile * tile = &renderer->tiles[tileIndex];

						RenderTileData data(renderer->frameId, tileIndex, tile->LOD, tile->skip, tile->cameraIndex, tile->antialiasing, tile->min, tile->max, renderer->texWidth, renderer->texHeight, myTID);
#if LUKAS_RUZICKA_DEBUG
						auto now = VRUTGetTime();
						data.time = now;
						data.totalTime = now;
						LOG(wxString::Format(wxT("LUKAS_RUZICKA_DEBUG 1.1, %i, %i, %i, -1, -1"), data.frameId, data.tileIndex, data.LOD));
#endif
						renderer->rayTracer->PostToKernel(GET_EVT_RENDER_TILE(sceneID, renderer->rayTracer->GetCameraID(), data));
						++renderer->remoteTilesCount;
					}
					else
					{
						renderer->RenderTile(tileIndex, myTID); // this tile will be rendered locally
					}
					POP_RANGE;
				}
				// scan the received tiles and fetch the ones for this worker

				PUSH_RANGE("WRW remoteTiles", 6);

				while (!renderer->rerender || renderer->lod0ProcessedTiles<renderer->tileCount)
				{
					auto remoteTileData = renderer->DequeueReceivedTile();

					if (remoteTileData)
					{
#if LUKAS_RUZICKA_DEBUG
						auto now = VRUTGetTime();
						auto diff = now - remoteTileData->time;
						auto diffTotal = now - remoteTileData->totalTime;
						LOG(wxString::Format(wxT("LUKAS_RUZICKA_DEBUG 10.1, %i, %i, %i, %llu, %llu"), remoteTileData->frameId, remoteTileData->tileIndex, remoteTileData->LOD, diff.GetValue(), diffTotal.GetValue()));
#endif
						if (remoteTileData->frameId != renderer->frameId)
						{
							//LOG(wxString::Format("Invalid frame data, currentFrame %d tile frame %d tile %d LOD %d", renderer->frameId, remoteTileData->frameId, remoteTileData->tileIndex, remoteTileData->LOD));
							//delete remoteTileData;
							remoteTileData.reset();
							remoteTileData = nullptr;
							--renderer->remoteTilesCount;
							continue;
						}
#if LUKAS_RUZICKA_DEBUG
						now = VRUTGetTime();
						diff = now - remoteTileData->time;
						diffTotal = now - remoteTileData->totalTime;
						LOG(wxString::Format(wxT("LUKAS_RUZICKA_DEBUG 10.2, %i, %i, %i, %llu, %llu"), remoteTileData->frameId, remoteTileData->tileIndex, remoteTileData->LOD, diff.GetValue(), diffTotal.GetValue()));
#endif//3. zmena
					}

					if (!remoteTileData)
						break;
					--renderer->remoteTilesCount;

					// process the received tiles
					int j = 0;
					std::map<uint64_t, unsigned int> pixelMap;
					Tile t;
					t.antialiasing = remoteTileData->antialiasing;
					t.min = remoteTileData->minVal;
					t.max = remoteTileData->maxVal;
					t.LOD = remoteTileData->LOD;

					const bool isMapUsed = renderer->GetSampler(t)->GenPixMapIsNecessary(pixelMap, &t, remoteTileData->numPixels);
					
					if (isMapUsed)
						{
							// copy tile pixels
							if (remoteTileData->antialiasing)
							{
								for (auto &pixelM : pixelMap)
								{
									Vector2i coords = AbstractSampler::U64ToVec2i(pixelM.first);

#ifndef USE_TEMPORAL
									renderer->WriteColorToTexDataAdd(remoteTileData->cameraIndex, coords.x, coords.y, remoteTileData->pixels.get()[pixelM.second]);
#else
									renderer->WriteColorToTexDataAdd(remoteTileData->cameraIndex, coords.x, coords.y, remoteTileData->pixels.get()[pixelM.second],0);
#endif
								}
							}
							else
							{
								for (auto &pixelM : pixelMap)
								{
									Vector2i coords = AbstractSampler::U64ToVec2i(pixelM.first);

#ifndef USE_TEMPORAL
									renderer->WriteColorToTexDataAdd(remoteTileData->cameraIndex, coords.x, coords.y, remoteTileData->pixels.get()[pixelM.second]);
#else
									renderer->WriteColorToTexDataAdd(remoteTileData->cameraIndex, coords.x, coords.y, remoteTileData->pixels.get()[pixelM.second], 0);
#endif
								}
							}
						}
					else
						{
							// copy tile pixels
							if (remoteTileData->antialiasing)
							{
								for (int y = remoteTileData->minVal.y; y < remoteTileData->maxVal.y; ++y) {
									for (int x = remoteTileData->minVal.x; x < remoteTileData->maxVal.x; ++x, ++j)
									{
#ifndef USE_TEMPORAL
										renderer->WriteColorToTexDataAdd(remoteTileData->cameraIndex, x, y, remoteTileData->pixels.get()[j]);
#else
										renderer->WriteColorToTexDataAdd(remoteTileData->cameraIndex, x, y, remoteTileData->pixels.get()[j],0);
#endif
#if COMPUTE_LUMINANCE
										// TODO: Same as in WhittedRenderer::RenderTile.
#endif
									}
								}
							}
							else
							{
								for (int y = remoteTileData->minVal.y; y < remoteTileData->maxVal.y; ++y)
								{
									for (int x = remoteTileData->minVal.x; x < remoteTileData->maxVal.x; ++x, ++j)
									{
#ifndef USE_TEMPORAL
										renderer->WriteColorToTexData(remoteTileData->cameraIndex, x, y, remoteTileData->pixels.get()[j]);
#else
										renderer->WriteColorToTexData(remoteTileData->cameraIndex, x, y, remoteTileData->pixels.get()[j],0);
#endif
#if COMPUTE_LUMINANCE
										// TODO: Same as in WhittedRenderer::RenderTile.
#endif
									}
								}
							}
						}

					Tile * tile = &renderer->tiles[remoteTileData->tileIndex];
					if (tile->LOD == 0)
					{
						unsigned int tmpTilesProcessed = ++renderer->lod0ProcessedTiles;
						if (tmpTilesProcessed == renderer->tileCount)
						{
							renderer->firstLODSemaphore.Post();
						}
					}
					// set the computed LOD
					tile->LOD = remoteTileData->LOD;
					// add the number of computed samples
					tile->samplesOverall += remoteTileData->samples;
					tile->antialiasing = remoteTileData->antialiasing;
					// add the samples to the samples counter
					renderer->samplesOverall += remoteTileData->samples;
					// reduce the skip counter
					int skip = tile->skip -= remoteTileData->skip; // MD Store value of the ATOMIC operation for the test below, because value of the member skip can change.
					if (skip > 0) 
					{
						// generate a new request to fill in the missing LODs
						RenderTileData data(renderer->frameId, remoteTileData->tileIndex, tile->LOD, tile->skip, tile->cameraIndex, tile->antialiasing, tile->min, tile->max, renderer->texWidth, renderer->texHeight, myTID);
#if LUKAS_RUZICKA_DEBUG
						auto now = VRUTGetTime();
						data.time = now;
						data.totalTime = now;
						LOG(wxString::Format(wxT("LUKAS_RUZICKA_DEBUG 1.2, %i, %i, %i, -1, -1"), data.frameId, data.tileIndex, data.LOD));
#endif
						renderer->rayTracer->PostToKernel(GET_EVT_RENDER_TILE(sceneID, renderer->rayTracer->GetCameraID(), data));
						++renderer->remoteTilesCount;
					}
					//delete remoteTileData; // the tile is processed so free the memory
					remoteTileData.reset();
				}
				POP_RANGE;

				// loop until the frame is being rendered
			}
		}
		else if (mode == SLAVE)
		{
			while (!renderer->rerender && !TestDestroy()) 
			{
				RenderTileData * tile= nullptr;
				{
					wxCriticalSectionLocker lock(renderer->tileRenderQueueLock);
					if (!renderer->tileRenderQueue.empty()) 
					{
						// get the tile from the render queue
						tile = renderer->tileRenderQueue.front();
						renderer->tileRenderQueue.pop();
					}
				}
				if (tile) 
				{
					if (renderer->cameras)
					{
						// render the tile to the memory
						renderer->RenderRemoteTile(*tile, myTID);
						// send the tile back to the rendering client
						//LOG(wxString::Format(wxT("WhittedRendererWorker 3: frameId=%i, tileIndex=%i, LOD=%i"), tile->frameId, tile->tileIndex, tile->LOD));
						//renderer->rayTracer->PostToKernel(GET_EVT_RENDER_TILE_DONE(sceneID, renderer->rayTracer->GetCameraID(), *tile));
#if LUKAS_RUZICKA_DEBUG
						auto now = VRUTGetTime();
						auto diff = now - tile->time;
						tile->time = now;
						LOG(wxString::Format(wxT("LUKAS_RUZICKA_DEBUG 5, %i, %i, %i, %llu, -1"), tile->frameId, tile->tileIndex, tile->LOD, diff.GetValue()));
#endif
						KERNEL->GetMessageSink()->DistributeToListeners(GET_EVT_RENDER_TILE_DONE(sceneID, renderer->rayTracer->GetCameraID(), *tile));
					}
					// delete the memory allocated for this tile
					delete tile;
					tile = nullptr;
				}
				else
				{
					// sleep for some time (only if no more tiles were available in the buffer!)
					wxThread::Sleep(1);
				}
			}
		}
		renderer->WorkerFinished(myTID);
	}
	return 0;
}
