#include "common.h"

#include "main.h"
#include "General.h"
#include "Renderer.h"
#include "Camera.h"
#include "Renderer.h"
#include "ModelInfo.h"
#include "AnimManager.h"
#include "custompipes.h"
#include "Streaming.h"
#include "smallHeap.h"
#include "Leeds.h"

TempIdeData m_sTempIdeData[800];

base::cRelocatableChunkClassInfo CSimpleModelInfo::msClassInfo("CSimpleModelInfo", VTABLE_ADDR(&msClassInstance), sizeof(msClassInstance));
CSimpleModelInfo CSimpleModelInfo::msClassInstance;

void
CSimpleModelInfo::DeleteRwObject(void)
{
	int i;
	RwFrame *f;
	if(m_atomics == nil)
		return;
	if(!gUseChunkFiles){
		for(i = 0; i < m_numAtomics; i++)
			if(m_atomics[i]){
				f = RpAtomicGetFrame(m_atomics[i]);
				RpAtomicDestroy(m_atomics[i]);
				RwFrameDestroy(f);
				m_atomics[i] = nil;
				RemoveTexDictionaryRef();
				if(GetAnimFileIndex() != -1)
					CAnimManager::RemoveAnimBlockRef(GetAnimFileIndex());
			}
	}else if(m_chunk){
		CStreaming::UnregisterPointer(&m_atomics, 2);
		for(i = 0; i < m_numAtomics; i++)
			CStreaming::UnregisterAtomic(m_atomics[i], nil);
		DeleteChunk();
		RemoveTexDictionaryRef();
		if(GetAnimFileIndex() != -1)
			CAnimManager::RemoveAnimBlockRef(GetAnimFileIndex());
	}
	m_atomics = nil;
}

RwObject*
CSimpleModelInfo::CreateInstance(void)
{
	RpAtomic *atomic;
	if(m_atomics == nil || m_atomics[0] == nil)
		return nil;
	atomic = RpAtomicClone(m_atomics[0]);
	RpAtomicSetFrame(atomic, RwFrameCreate());
	CStreaming::RegisterInstance(atomic, nil);
	return (RwObject*)atomic;
}

RwObject*
CSimpleModelInfo::CreateInstance(RwMatrix *matrix)
{
	RpAtomic *atomic;
	RwFrame *frame;

	if(m_atomics == nil || m_atomics[0] == nil)
		return nil;
	atomic = RpAtomicClone(m_atomics[0]);
	frame = RwFrameCreate();
	*RwFrameGetMatrix(frame) = *matrix;
	RpAtomicSetFrame(atomic, frame);
	CStreaming::RegisterInstance(atomic, nil);
	return (RwObject*)atomic;
}

void
CSimpleModelInfo::Init(void)
{
	m_atomics = new RpAtomic*[3];
	m_numAtomics = 0;
	m_firstDamaged  = 0;
	m_wetRoadReflection    = 0;
	m_isDamaged     = 0;
	m_isBigBuilding = 0;
	m_noFade        = 0;
	m_drawLast      = 0;
	m_additive      = 0;
	m_isSubway      = 0;
	m_ignoreLight   = 0;
	m_noZwrite      = 0;
	m_noShadows     = 0;
	m_ignoreDrawDist  = 0;
	m_isCodeGlass     = 0;
	m_isArtistGlass   = 0;
	m_relatedModel = nil;
}

void
CSimpleModelInfo::SetAtomic(int n, RpAtomic *atomic)
{
	if(m_atomics == nil){
		m_atomics = new RpAtomic*[3];
		m_atomics[0] = nil;
		m_atomics[1] = nil;
		m_atomics[2] = nil;
	}
	m_atomics[n] = atomic;
	AddTexDictionaryRef();
	if(GetAnimFileIndex() != -1)
		CAnimManager::AddAnimBlockRef(GetAnimFileIndex());
	RpGeometry *geo = RpAtomicGetGeometry(atomic);
	if(m_ignoreLight)
		RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) & ~rpGEOMETRYLIGHT);
/*
	if(RpGeometryGetFlags(geo) & rpGEOMETRYNORMALS &&
	   RpGeometryGetNumTriangles(geo) > 200)
		debug("%s has %d polys\n", m_name, RpGeometryGetNumTriangles(geo));
*/

#ifdef EXTENDED_PIPELINES
	if(m_wetRoadReflection)
		CustomPipes::AttachGlossPipe(atomic);
	else
		CustomPipes::AttachWorldPipe(atomic);
#endif
}

void
CSimpleModelInfo::SetLodDistances(float *dist)
{
	m_lodDistances[0] = dist[0];
	m_lodDistances[1] = dist[1];
	m_lodDistances[2] = dist[2];
}

void
CSimpleModelInfo::IncreaseAlpha(void)
{
	if(m_alpha >= 0xEF)
		m_alpha = 0xFF;
	else
		m_alpha += 0x10;
}

float
CSimpleModelInfo::GetLodDistance(int i)
{
	return m_lodDistances[i] * TheCamera.LODDistMultiplier;
}

float
CSimpleModelInfo::GetNearDistance(void)
{
	return m_lodDistances[2] * TheCamera.LODDistMultiplier;
}

float
CSimpleModelInfo::GetLargestLodDistance(void)
{
	float d;
	if(m_firstDamaged == 0 || m_isDamaged)
		d = m_lodDistances[m_numAtomics-1];
	else
		d = m_lodDistances[m_firstDamaged-1];
	return d * TheCamera.LODDistMultiplier;
}

RpAtomic*
CSimpleModelInfo::GetLodAtomic(int n)
{
	if(m_atomics == nil || n >= m_numAtomics)
		return nil;
	return m_atomics[n];
}

RpAtomic*
CSimpleModelInfo::GetLastAtomic(void)
{
	if(m_atomics == nil)
		return nil;
	if(m_firstDamaged == 0 || m_isDamaged)
		return m_atomics[m_numAtomics-1];
	else
		return m_atomics[m_firstDamaged-1];
}

RpAtomic*
CSimpleModelInfo::GetLastAtomic(float dist)
{
	int n;
	if(m_atomics == nil)
		return nil;
	if(m_firstDamaged == 0 || m_isDamaged)
		n = m_numAtomics-1;
	else
		n = m_firstDamaged-1;
	if(dist < m_lodDistances[n] * TheCamera.LODDistMultiplier)
		return m_atomics[n];
	return nil;
}

RpAtomic*
CSimpleModelInfo::GetAtomicFromDistance(float dist)
{
	int i;
	i = 0;
// HACk until we figure out what's going on
if(m_atomics == nil) return nil;
	if(m_isDamaged)
		i = m_firstDamaged;
	for(; i < m_numAtomics; i++)
		if(dist < m_lodDistances[i] * TheCamera.LODDistMultiplier)
			return m_atomics[i];
	return nil;
}

RpAtomic*
CSimpleModelInfo::GetFirstAtomicFromDistance(float dist)
{
// HACk until we figure out what's going on
if(m_atomics == nil) return nil;
	if(dist < m_lodDistances[0] * TheCamera.LODDistMultiplier)
		return m_atomics[0];
	return nil;
}

void
CSimpleModelInfo::FindRelatedModel(void)
{
	CBaseModelInfo *mi;
	int thisIndex, otherIndex;

	// find our own index in temp data
	for(thisIndex = 0; thisIndex < ARRAY_SIZE(m_sTempIdeData); thisIndex++){
		if(m_sTempIdeData[thisIndex].id == -1)
			break;
		if(this == CModelInfo::GetModelInfo(m_sTempIdeData[thisIndex].id))
			goto found;
	}
	thisIndex = -1;
found:
#ifdef FIX_BUGS
	if(thisIndex == -1)
		return;
#endif

	for(otherIndex = 0; otherIndex < ARRAY_SIZE(m_sTempIdeData); otherIndex++){
		if(m_sTempIdeData[otherIndex].id == -1)
			break;

		mi = CModelInfo::GetModelInfo(m_sTempIdeData[otherIndex].id);
		if(mi && mi != this &&
		   !CGeneral::faststrcmp(m_sTempIdeData[thisIndex].name+3, m_sTempIdeData[otherIndex].name+3)){
			assert(mi->IsSimple());
			this->SetRelatedModel((CSimpleModelInfo*)mi);
			return;
		}
	}
}

#define NEAR_DRAW_DIST 100.0f	// 0.0f in vice city

void
CSimpleModelInfo::SetupBigBuilding(void)
{
	CSimpleModelInfo *related;
	if(m_lodDistances[0] < 0.0f)
		m_lodDistances[0] = -m_lodDistances[0];	// what?
	else if(m_lodDistances[0] > LOD_DISTANCE && GetRelatedModel() == nil){
		m_isBigBuilding = 1;
		FindRelatedModel();
		related = GetRelatedModel();
		if(related){
			m_lodDistances[2] = related->GetLargestLodDistance()/TheCamera.LODDistMultiplier;
			if(m_drawLast){
				m_drawLast = false;
				debug("%s was draw last\n", GetModelName());
			}
		}else
			m_lodDistances[2] = NEAR_DRAW_DIST;
	}
}


void
CSimpleModelInfo::LoadModel(void *atomics, const void *chunk)
{
	int i;
	m_chunk = (void*)chunk;
	m_atomics = (RpAtomic**)atomics;
	CStreaming::RegisterPointer(m_chunk, 2, true);
	CStreaming::RegisterPointer(m_atomics, 2, true);
	for(i = 0; i < m_numAtomics; i++){
		LoadResource(m_atomics[i]);
		CStreaming::RegisterAtomic(m_atomics[i], nil);
	}
}

void
CSimpleModelInfo::Write(base::cRelocatableChunkWriter &writer)
{
	CBaseModelInfo::Write(writer);
	if(WriteModel(writer))
		writer.AddPatch(&m_atomics);
	else
		m_atomics = nil;
	if(m_isBigBuilding)
		writer.AddPatch(&m_relatedModel);
}

void*
CSimpleModelInfo::WriteModel(base::cRelocatableChunkWriter &writer)
{
	int i;
	if(m_atomics == nil || m_atomics[0] == nil)
		return nil;

	// remove empty atomics
	int numAtomics = 0;
	for(i = 0; i < m_numAtomics; i++)
		if(m_atomics[i]){
			m_atomics[numAtomics] = m_atomics[i];
#ifdef FIX_BUGS
			m_lodDistances[numAtomics] = m_lodDistances[i];
#endif
			numAtomics++;
		}
	if(m_firstDamaged){
		int firstDam = m_firstDamaged - m_numAtomics + numAtomics;
		if(firstDam < numAtomics)
			m_firstDamaged = firstDam;
		else
			m_firstDamaged = 0;
	}
	m_numAtomics = numAtomics;

	// write the actual models
	writer.AllocateRaw(m_atomics, m_numAtomics*sizeof(void*), sizeof(void*), false, true);
	for(i = 0; m_numAtomics; i++){
		writer.AddPatch(&m_atomics[i]);
		SaveResource(m_atomics[i], writer);
	}
	return m_atomics;
}

void
CSimpleModelInfo::RcWriteThis(base::cRelocatableChunkWriter &writer)
{
	writer.AllocateRaw(this, sizeof(*this), 0x10, false, true);
	writer.Class(VTABLE_ADDR(this), msClassInfo);
}

void
CSimpleModelInfo::RcWriteEmpty(base::cRelocatableChunkWriter &writer)
{
	writer.AllocateRaw(this, sizeof(*this), 0x10, false, true);
	writer.Class(VTABLE_ADDR(this), msClassInfo);
}
