#include "AbstractRenderer.h"
#include "RayCaster.h"
#include "VisualDebugger.h"
#include "BVHBinnedBuilder.h"
#include "BVHSAHBuilder.h"
#include "BVHOptimizer.h"

using namespace VRUT;

bool AbstractRenderer::SamplePixels(Vector2 pixels[], Tile & tile)
{
    AbstractSampler* sampler = GetSampler(tile);
    return (sampler != nullptr ? sampler->SamplePixels(pixels, tile) : false);
}

AbstractSampler* AbstractRenderer::GetSampler(const Tile & tile)
{
	int samplerIndex = 0;
	//if (useTemporalPathTracing)
	//	return haltons[samplerIndex];
	if (samplingStrategy == ADAPTIVE_UNIFORM_STRATEGY)
	{
		RayTracerCamera* camera = cameras[tile.cameraIndex];

        // tex position <0;texWidth> x <0;texHeight> -> screen space <-1;1>^2
        Vector2 tileCenter((tile.min.x + tile.max.x) >> 1, (tile.min.y + tile.max.y) >> 1);
        float px = 2*(+tileCenter.x / texWidth)  - 1.0f;
        float py = 2*(-tileCenter.y / texHeight) + 1.0f;

        // screen space -> unprojected coordinates -> (neutralized projection plane translation+rotation) -> world coordinates
        Vector4 tileCenterPoint(camera->viewProjectionInverse.TransformCoord(Vector4(px, py, 1.0f, 1.0f)));
        Vector3 tileCenterDir(tileCenterPoint.x/tileCenterPoint.w - camera->origin.x,
			tileCenterPoint.y/tileCenterPoint.w - camera->origin.y,
            tileCenterPoint.z/tileCenterPoint.w - camera->origin.z);

		// Find the proper interval within viewConeAngles & select the proper LOD (sampler)
		float cosAngle = tileCenterDir.Normalize().Dot(camera->lookAt);
		for (size_t i = 0; i < viewConeAngles.size() - 1; i++)
			if (cosAngle > viewConeAngles[i] && cosAngle < viewConeAngles[i + 1])
			{
				if (i == 0)
					return nullptr;
				else
					samplerIndex = i - 1;
				break;
			}
	}
	else
	{
		samplerIndex = samplingStrategy;
	}
	return samplers[samplerIndex];
}

unsigned int AbstractRenderer::MaxLODForSamples(int samplesPerPixel)
{
	// NOTE MD: Change this method together with MaxLODForSamples() and make sure corresponding samplers are used
	// If sampling strategy is ADAPTIVE_UNIFORM_STRATEGY, use sampler with the least samples for the first LOD
	int samplerIndex = 0;
	if (samplingStrategy != ADAPTIVE_UNIFORM_STRATEGY)
	{
		samplerIndex = samplingStrategy;
	}
	return samplers[samplerIndex]->LODForSamples(samplesPerPixel);
}

void AbstractRenderer::CacheHeadlightInfo()
{
	// Clear and update headlights
	headlightInfo.clear();
	if (headlight)
	{
		// One headlight for each camera
		for (unsigned int i = 0; i < camerasCount; ++i)
		{
			LightInfoEntry headlightEntry;
			headlightEntry.position = cameras[i]->origin;
			headlightEntry.iesLight = nullptr;
			headlightEntry.light.type = Light::UNKNOWN;
			headlightEntry.light.diffuse = headLightColor;
			headlightEntry.lightID = NODE_ID_NONE;
			headlightInfo.push_back(headlightEntry);
		}
	}
}

void AbstractRenderer::CacheLightInfo()
{
	// clear any previously cached lights
	lightInfo.clear();

	// cache all scene lights
	std::vector<smart_pointer<LightNode> > lights;
	sceneShadow->GetNodesActive(lights, SceneNode::LIGHT);
	for (auto &light : lights)
	{
		if (light && sceneShadow->IsActive(light->GetID()))
		{
			LightInfoEntry li;
			li.lightID=light->GetID();
			li.light = light->GetLight();
			li.light.diffuse._v[0]*=li.light.diffuse.w;
			li.light.diffuse._v[1]*=li.light.diffuse.w;
			li.light.diffuse._v[2]*=li.light.diffuse.w;
			li.inverseWorldMat = *sceneShadow->GetWorldMatrix(light->GetID());

			// Note spot lights uses both the position and direction
			if ((li.light.type == Light::OMNI) || (li.light.type == Light::SPOTLIGHT))
				li.position = li.inverseWorldMat.TransformCoord(Vector3(0,0,0));

			if ((li.light.type == Light::DIRECTIONAL) || (li.light.type == Light::SPOTLIGHT))
				li.direction = li.inverseWorldMat.TransformNormal(Vector3(0,0,-1)).Normalize();

			li.inverseWorldMat.InvertThisPrecise();

			if (!li.light.ies.empty())
			{
				IESLightHashMap::const_iterator it = iesLightMap.find(li.light.ies);
				if (it==iesLightMap.end())
				{
					iesLightMap[li.light.ies]=IESLight(li.light.ies);
					li.iesLight=&iesLightMap[li.light.ies];
				}
				else
					li.iesLight=&iesLightMap[li.light.ies];
			}
			else
				li.iesLight = nullptr;
			lightInfo.push_back(li);
		}
	}
}

bool AbstractRenderer::HasLight(NODE_ID nodeID)
{
	for (auto &lightIn : lightInfo)
		if (lightIn.lightID == nodeID)
			return true;
	return false;
}

AbstractRenderer::AbstractRenderer(RayTracer *_rayTracer) : frameId(0), maxLights(3), maxRecursionDepth(4), trianglesPerLeaf(1), samplingStrategy(0), samplesPerPixel(64),
		headlight(true), fullPolarization(true), castShadowRays(true), sync(false), rayOffset(0.005f), rerender(false), rayTracer(_rayTracer), usePathTracing(false),
		camerasCount(0), cameras(nullptr), sceneShadow(nullptr), texData(nullptr), texWidth(0), texHeight(0), mVisualDebugger(nullptr)
{
	// add and init samplers
	samplers.push_back(new UniformSampler(0));
	samplers.push_back(new UniformSampler(1));
	samplers.push_back(new UniformSampler(2));
	samplers.push_back(new UniformSampler(3));
	samplers.push_back(new UniformSampler(4));
	samplers.push_back(new UniformSampler(5));
	std::vector<unsigned int> sizes, sizes32, sizes1;
	sizes.push_back(64);
	sizes.push_back(128);
	sizes.push_back(256);
	sizes.push_back(576);
	samplers.push_back(new HaltonSampler(sizes, sizes));
	for (int i = 0; i < 32; ++i)
		sizes32.push_back(32);

	for (int i = 0; i < 1024; ++i)
		sizes1.push_back(1);

	samplers.push_back(new FPSSampler(sizes, sizes));
	samplers.push_back(new FPSSampler(sizes32, sizes1));

	std::vector<unsigned int> sizesH64, sizesH32, sizesH16, sizesH8, sizesFull;
	for (int i = 0; i < TILE_SIZE*TILE_SIZE / 16; ++i)
		sizesH32.push_back(16);
	for (int i = 0; i < TILE_SIZE*TILE_SIZE / 16; ++i)
		sizesH16.push_back(16);
	for (int i = 0; i < TILE_SIZE*TILE_SIZE / 8; ++i)
		sizesH8.push_back(8);
	sizesFull.push_back(TILE_SIZE*TILE_SIZE);

#ifdef USE_TEMPORAL
	//haltons.push_back(new InfiniteHaltonSampler(sizesH8));
	//haltons.push_back(new InfiniteHaltonSampler(sizesH16));
	//haltons.push_back(new InfiniteHaltonSampler(sizesH32));
	//samplers.push_back(new InfiniteHaltonSampler(sizesFull));

	samplers.push_back(new InfiniteUniformSampler(TILE_SIZE / 2));
	samplers.push_back(new InfiniteUniformSampler(TILE_SIZE / 4, true));
	samplers.push_back(new InfiniteUniformSampler(TILE_SIZE / 4));
	samplers.push_back(new InfiniteUniformSampler(TILE_SIZE / 8));
	samplers.push_back(new InfiniteUniformSampler(1));
#endif
}

AbstractRenderer::~AbstractRenderer()
{
	for (auto &sampler : samplers)
		SAFE_DELETE(sampler);
	if (mVisualDebugger)
		SAFE_DELETE(mVisualDebugger);
}

bool AbstractRenderer::ShouldRerender()
{
	return rerender;
}

void AbstractRenderer::Rerender()
{
	rerender = true;
}

void AbstractRenderer::DeleteGeometryCache()
{
	bvh.DeleteGeometryCache();
}

void AbstractRenderer::InvalidateGeometryCacheEntry(unsigned int index)
{
	bvh.InvalidateGeometryCacheEntry(index);
}

void AbstractRenderer::InvalidateImageCacheEntry(unsigned int index)
{
	materialCache.InvalidateImageCacheEntry(index);
}

bool AbstractRenderer::BuildGeoAcc()
{
	return bvh.BuildGeomAcc(scene, optRatio, optMaxIterations, optThreshold, optRandomThreshold);
}

int AbstractRenderer::GetMaxLights()
{
	return maxLights;
}

void AbstractRenderer::SetMaxLights(int maxLights)
{
	if (maxLights >= 0)
		this->maxLights = maxLights;
}

int AbstractRenderer::GetMaxRecursionDepth()
{
	return maxRecursionDepth;
}

void AbstractRenderer::SetMaxRecursionDepth(int maxRecursionDepth)
{
	if (maxRecursionDepth >= 0 && maxRecursionDepth <= MAX_RECURSION_DEPTH)
		this->maxRecursionDepth =maxRecursionDepth;
}

int AbstractRenderer::GetTrianglesPerLeaf()
{
	return trianglesPerLeaf;
}

void AbstractRenderer::SetTrianglesPerLeaf(int trianglesPerLeaf)
{
	if (trianglesPerLeaf > 0)
		this->trianglesPerLeaf = trianglesPerLeaf;
}

int AbstractRenderer::GetSamplingStrategy()
{
	return samplingStrategy;
}

void AbstractRenderer::SetSamplingStrategy(int samplingStrategy)
{
	this->samplingStrategy = samplingStrategy;
}
int AbstractRenderer::GetSamplesPerPixel()
{
	return samplesPerPixel;
}

void AbstractRenderer::SetSamplesPerPixel(int samplesPerPixel)
{
	if (samplesPerPixel >= -64 && samplesPerPixel <= TILE_SIZE * TILE_SIZE)
		this->samplesPerPixel = samplesPerPixel;
}

bool AbstractRenderer::GetHeadlight()
{
	return headlight;
}

void AbstractRenderer::SetHeadlight(const Vector4 &color)
{
	this->headlight = (color.w != 0);
	this->headLightColor = color;
}

bool AbstractRenderer::GetFullPolarization()
{
	return fullPolarization;
}

void AbstractRenderer::SetFullPolarization(bool fullPolarization)
{
	this->fullPolarization = fullPolarization;
}

bool AbstractRenderer::GetCastShadowRays()
{
	return castShadowRays;
}

void AbstractRenderer::SetShadowIntensity(float shadowAmount)
{
	this->castShadowRays = shadowAmount != 0;
    shadowIntensity = shadowAmount;
}

bool AbstractRenderer::GetSync()
{
	return sync;
}

void AbstractRenderer::SetSync(bool sync)
{
	this->sync = sync;
}

float AbstractRenderer::GetRayOffset()
{
	return rayOffset;
}

void AbstractRenderer::SetRayOffset(float rayOffset)
{
	this->rayOffset = rayOffset;
}

int AbstractRenderer::GetRRStartDepth()
{
	return rrStartDepth;
}

void AbstractRenderer::SetRRStartDepth(int rrStartDepth)
{
	this->rrStartDepth = rrStartDepth;
}

void AbstractRenderer::SetOptRatio(int optRatio)
{
	this->optRatio = optRatio;
}

void AbstractRenderer::SetOptThreshold(int optThreshold)
{
	this->optThreshold = optThreshold;
}

void AbstractRenderer::SetOptRandomThreshold(int optRandomThreshold)
{
	this->optRandomThreshold = optRandomThreshold;
}

void AbstractRenderer::SetOptMaxIteraions(int optMaxIterations)
{
	this->optMaxIterations = optMaxIterations;
}

void AbstractRenderer::SetViewConeAngles(wxArrayInt& coneArray)
{
	viewConeAngles.resize(coneArray.size());
	for (size_t i = 0; i < coneArray.size(); i++)
		viewConeAngles[i] = cosf(coneArray[i] * M_PI / 180.0f);
}

Vector3 AbstractRenderer::GetNormalMapNormal(const Vector3& normalTBN, const Vector3& normal, const Vector3& tangent, const Vector3& bitangent) const
{
	Matrix tbnToModel;

	tbnToModel._m11 = tangent.x;
	tbnToModel._m12 = bitangent.x;
	tbnToModel._m13 = normal.x;
	tbnToModel._m14 = 0;

	tbnToModel._m21 = tangent.y;
	tbnToModel._m22 = bitangent.y;
	tbnToModel._m23 = normal.y;
	tbnToModel._m24 = 0;

	tbnToModel._m31 = tangent.z;
	tbnToModel._m32 = bitangent.z;
	tbnToModel._m33 = normal.z;
	tbnToModel._m34 = 0;

	tbnToModel._m41 = 0;
	tbnToModel._m42 = 0;
	tbnToModel._m43 = 0;
	tbnToModel._m44 = 1;

	tbnToModel.InvertThisPrecise();
	
	return tbnToModel.TransformNormal(normalTBN).Normalize();
}

