#include "MLRHeaders.hpp"

//#############################################################################
//#########################    MLRLookUpLight    ################################
//#############################################################################

MLRLookUpLight::ClassData*
	MLRLookUpLight::DefaultData = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::InitializeClass()
{
	Verify(!DefaultData);
	Verify(gos_GetCurrentHeap() == StaticHeap);
	DefaultData =
		new ClassData(
			MLRLookUpLightClassID,
			"MidLevelRenderer::MLRLookUpLight",
			MLRInfiniteLight::DefaultData
		);
	Check_Object(DefaultData);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::TerminateClass()
{
	Check_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MLRLookUpLight::MLRLookUpLight() :
	MLRInfiniteLight(DefaultData)
{
	Verify(gos_GetCurrentHeap() == LightsHeap);

	mapOrigin.x = 0.0f;
	mapOrigin.y = 0.0f;
	mapOrigin.z = 0.0f;

	mapZoneCountX = 1, mapZoneCountZ = 1;

	zoneSizeX = 1260.0f, zoneSizeZ = 1260.0f;
	one_Over_zoneSizeX = 1.0f/zoneSizeX;
	one_Over_zoneSizeZ = 1.0f/zoneSizeZ;

	shadowIntensity = 0.5f;

	maps = NULL;
	mapName = *MString::s_Empty;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MLRLookUpLight::MLRLookUpLight(
	Stuff::MemoryStream *stream,
	int version
) :
	MLRInfiniteLight(DefaultData, stream, version)
{
	Check_Object(stream);
	Verify(gos_GetCurrentHeap() == LightsHeap);

	*stream >> mapOrigin;
	*stream >> mapZoneCountX >> mapZoneCountZ;
	*stream >> zoneSizeX >> zoneSizeZ;
	*stream >> mapName;

	one_Over_zoneSizeX = 1.0f/zoneSizeX;
	one_Over_zoneSizeZ = 1.0f/zoneSizeZ;

	if(version>16)
	{
		*stream >> shadowIntensity;
	}
	else
	{
		shadowIntensity = 0.5f;
	}

	maps = new BYTE * [mapZoneCountX * mapZoneCountZ];
	Check_Pointer(maps);

	for(int i=0;i<mapZoneCountX*mapZoneCountZ;i++)
	{
		maps[i] = static_cast<BYTE*>(stream->GetPointer());
		stream->AdvancePointer(256*32);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MLRLookUpLight::MLRLookUpLight(Stuff::Page *page):
	MLRInfiniteLight(DefaultData, page)
{
	Check_Object(page);
	Verify(gos_GetCurrentHeap() == LightsHeap);

	maps = NULL;

	const char *data;

	mapOrigin.x = 0.0f;
	mapOrigin.y = 0.0f;
	mapOrigin.z = 0.0f;
	if (page->GetEntry("MapOrigin", &data))
	{
		sscanf(data, "%f %f %f", &mapOrigin.x, &mapOrigin.y, &mapOrigin.z);
	}

	mapZoneCountX = 1, mapZoneCountZ = 1;
	if(page->GetEntry("MapSize", &data))
	{
		sscanf(data, "%d %d", &mapZoneCountX, &mapZoneCountZ);
	}

	zoneSizeX = 1280.0f, zoneSizeZ = 1280.0f;
	if(page->GetEntry("ZoneSize", &data))
	{
		sscanf(data, "%f %f", &zoneSizeX, &zoneSizeX);
	}
	one_Over_zoneSizeX = 1.0f/zoneSizeX;
	one_Over_zoneSizeZ = 1.0f/zoneSizeZ;

	shadowIntensity = 0.5f;
	page->GetEntry("ShadowIntensity", &shadowIntensity);

	mapName = *MString::s_Empty;
	if(page->GetEntry("MapName", &data))
	{
		mapName = data;
	}

	LoadMap();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
MLRLookUpLight::~MLRLookUpLight()
{
	if(maps!=NULL)
	{
		for(int i=0;i<mapZoneCountX*mapZoneCountZ;i++)
		{
			Check_Pointer(maps[i]);
			maps[i] = NULL;
		}
		Check_Pointer(maps);
		delete [] maps;
		maps = NULL;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);

	MLRInfiniteLight::Save(stream);

	*stream << mapOrigin;
	*stream << mapZoneCountX << mapZoneCountZ;
	*stream << zoneSizeX << zoneSizeZ;
	*stream << mapName;
	*stream << shadowIntensity;

	for(int i=0;i<mapZoneCountX*mapZoneCountZ;i++)
	{
		stream->WriteBytes(maps[i], 256*32);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::Write(Stuff::Page *page)
{
	Check_Object(this);
	Check_Object(page);

	MLRInfiniteLight::Write(page);

	char data[256];

	sprintf(data, "%f %f %f", mapOrigin.x, mapOrigin.y, mapOrigin.z);
	Verify(strlen(data) < sizeof(data));
	page->SetEntry("MapOrigin", data);

	sprintf(data, "%d %d", mapZoneCountX, mapZoneCountZ);
	Verify(strlen(data) < sizeof(data));
	page->SetEntry("MapSize", data);

	sprintf(data, "%f %f", zoneSizeX, zoneSizeZ);
	Verify(strlen(data) < sizeof(data));
	page->SetEntry("ZoneSize", data);

	page->SetEntry("ShadowIntensity", shadowIntensity);

	page->SetEntry("MapName", mapName);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::SetMapSizeAndName(int x, int z, const char *name)
{
	Check_Object(this);

	if(maps!=NULL)
	{
		for(int i=0;i<mapZoneCountX*mapZoneCountZ;i++)
		{
			Check_Pointer(maps[i]);
			delete [] maps[i];
			maps[i] = NULL;
		}
		Check_Pointer(maps);
		delete [] maps;
		maps = NULL;
	}

	mapZoneCountX = x;
	mapZoneCountZ = z;
	mapName = name;
	
	LoadMap();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
	MLRLookUpLight::LoadMap()
{
	Check_Object(this);

	Stuff::FileStream element_stream(mapName);

	BYTE *map = new BYTE [mapZoneCountX*mapZoneCountZ*256*256];
	Check_Pointer(map);
	element_stream.ReadBytes(map, mapZoneCountX*mapZoneCountZ*256*256);

	Verify(maps==NULL);
	maps = new BYTE * [mapZoneCountX * mapZoneCountZ];
	Check_Pointer(maps);

	int i, j;
	for(j=0;j<mapZoneCountZ;j++)
	{
		for(i=0;i<mapZoneCountX;i++)
		{
			maps[j*mapZoneCountX+i] = new BYTE [256*32];
			Check_Pointer(maps[j*mapZoneCountX+i]);
		}
	}

	BYTE *uptr = map;
	int k, l, m, mask;
	for(j=0;j<mapZoneCountZ;j++)
	{
		for(k=0;k<256;k++)
		{
			for(i=0;i<mapZoneCountX;i++)
			{
				for(l=0;l<32;l++)
				{
					maps[j*mapZoneCountX+i][k*32+l] = 0;

					mask = 0x80;
					for(m=0;m<8;m++,mask>>=1)
					{
						if(*uptr++>128)
						{
							maps[j*mapZoneCountX+i][k*32+l] |= mask;
						}
					}
//				Mem_Copy(&maps[j*mapZoneCountX+i][k*256], uptr, 256, (256-k)*256);
//				uptr += 256;


//					&map[(256*j+k)*(mapZoneCountX*256) + i*256]
				}
			}
		}
	}

	Check_Pointer(map);
	delete map;

	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::SetLightToShapeMatrix(const LinearMatrix4D& worldToShape)
{
	Check_Object(this);

	lightToShape.Multiply(lightToWorld, worldToShape);

	shapeToWorld.Invert(worldToShape);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::LightVertex(const MLRVertexData& vertexData)
{
	UnitVector3D light_z;

	GetInShapeDirection(light_z);

//	const Scalar One_Over_255 = 1.f/255.0f;

	Point3D worldPoint;

	worldPoint.Multiply(*(vertexData.point), shapeToWorld);

	Scalar prep_x = mapZoneCountX*zoneSizeX - worldPoint.x - mapOrigin.x;
	Scalar prep_z = mapZoneCountZ*zoneSizeZ - worldPoint.z - mapOrigin.z;

	if(prep_x < 0)
	{
		prep_x = 0;
	}
	int map_x = Truncate_Float_To_Word(prep_x*one_Over_zoneSizeX);
	if(prep_z < 0)
	{
		prep_z = 0;
	}
	int map_z = Truncate_Float_To_Word(prep_z*one_Over_zoneSizeZ);

	if(map_x>=mapZoneCountX)
	{
		map_x = mapZoneCountX-1;
	}
	if(map_z>=mapZoneCountZ)
	{
		map_z = mapZoneCountZ-1;
	}

//	Verify(map_x>=0 && map_x<mapZoneCountX);
//	Verify(map_z>=0 && map_z<mapZoneCountZ);

	int off_x = Truncate_Float_To_Word((prep_x - map_x*zoneSizeX)*256.0f*one_Over_zoneSizeX);
	int off_z = Truncate_Float_To_Word((prep_z - map_z*zoneSizeZ)*256.0f*one_Over_zoneSizeZ);

	if(off_x < 0)
	{
		off_x = 0;
	}
	if(off_x>255)
	{
		off_x = 255;
	}
	if(off_z < 0)
	{
		off_z = 0;
	}
	if(off_z>255)
	{
		off_z = 255;
	}

//	Verify(off_x>=0 && off_x < 256);
//	Verify(off_z>=0 && off_z < 256);

	BYTE b = maps[map_z*mapZoneCountX+map_x][(off_z<<5) + (off_x>>3)];
	Scalar mapIntensity = b & (0x80>>(off_x&7)) ? intensity : shadowIntensity;

	//
	//-------------------------------------------------------------------
	// Now we reduce the light level falling on the vertex based upon the
	// cosine of the angle between light and normal
	//-------------------------------------------------------------------
	//
	Scalar cosine = mapIntensity;

	if(vertexData.normal!=NULL)
	{
		cosine = -(light_z * (*vertexData.normal))*mapIntensity;
	}

	RGBColor light_color(color);

	if (cosine > SMALL)
	{
		light_color.red *= cosine;
		light_color.green *= cosine;
		light_color.blue *= cosine;

		Stuff::RGBAColor *t = const_cast<Stuff::RGBAColor*>(vertexData.color);
		Check_Pointer(t);
		t->red += light_color.red;
		t->green += light_color.green;
		t->blue += light_color.blue;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
	MLRLookUpLight::LightCenter(RGBAColor& rcol)
{
	UnitVector3D light_z;

	GetInShapeDirection(light_z);

//	const Scalar One_Over_255 = 1.f/255.0f;

	Point3D worldPoint;

	worldPoint = shapeToWorld;

	Scalar prep_x = mapZoneCountX*zoneSizeX - worldPoint.x - mapOrigin.x;
	Scalar prep_z = mapZoneCountZ*zoneSizeZ - worldPoint.z - mapOrigin.z;

	if(prep_x < 0.0f)
	{
		prep_x = 0.0f;
	}
	int map_x = Truncate_Float_To_Word(prep_x*one_Over_zoneSizeX);

	if(prep_z < 0.0f)
	{
		prep_z = 0.0f;
	}
	int map_z = Truncate_Float_To_Word(prep_z*one_Over_zoneSizeZ);

	if(map_x>=mapZoneCountX)
	{
		map_x = mapZoneCountX-1;
	}
	if(map_z>=mapZoneCountZ)
	{
		map_z = mapZoneCountZ-1;
	}

//	Verify(map_x>=0 && map_x<mapZoneCountX);
//	Verify(map_z>=0 && map_z<mapZoneCountZ);

	int off_x = Truncate_Float_To_Word((prep_x - map_x*zoneSizeX)*256.0f*one_Over_zoneSizeX);
	int off_z = Truncate_Float_To_Word((prep_z - map_z*zoneSizeZ)*256.0f*one_Over_zoneSizeZ);

	if(off_x < 0)
	{
		off_x = 0;
	}
	if(off_x>255)
	{
		off_x = 255;
	}
	if(off_z < 0)
	{
		off_z = 0;
	}
	if(off_z>255)
	{
		off_z = 255;
	}

//	Verify(off_x>=0 && off_x < 256);
//	Verify(off_z>=0 && off_z < 256);

//	Scalar mapIntensity = maps[map_z*mapZoneCountX+map_x][(off_z<<8)+off_x]*One_Over_255;
	BYTE b = maps[map_z*mapZoneCountX+map_x][(off_z<<5)+(off_x>>3)];
	Scalar mapIntensity = b & (0x80>>(off_x&3)) ? intensity : shadowIntensity;

	//
	//-------------------------------------------------------------------
	// Now we reduce the light level falling on the vertex based upon the
	// cosine of the angle between light and normal
	//-------------------------------------------------------------------
	//
	Scalar cosine = mapIntensity;

	RGBColor light_color(color);

	if (cosine > SMALL)
	{
		light_color.red *= cosine;
		light_color.green *= cosine;
		light_color.blue *= cosine;

		rcol.red += light_color.red;
		rcol.green += light_color.green;
		rcol.blue += light_color.blue;
	}
}