/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/


#include "hsTypes.h"

#include "plAccessGeometry.h"

#include "pnSceneObject/plDrawInterface.h"

#include "plDrawableSpans.h"
#include "plGeometrySpan.h"

#include "plAccessSpan.h"
#include "plAccessPartySpan.h"
#include "plAccessTriSpan.h"
#include "plAccessVtxSpan.h"
#include "plAccessSnapShot.h"

// For dipping directly into device buffers.
#include "plPipeline/plGBufferGroup.h"
#include "plPipeline/hsGDeviceRef.h"
#include "plPipeline.h"

#include "plTweak.h"

//////////////////////////////////////////////////////////////////////////////////
// Dropping these here, because they have no place else to go except a header.
void plAccessSpan::SetSource(plSpan* s)
{ 
	fLocalToWorld = &s->fLocalToWorld; 
	fWorldToLocal = &s->fWorldToLocal;
	fLocalBounds = &s->fLocalBounds;
	fWorldBounds = &s->fWorldBounds;

	fWaterHeight = s->fProps & plSpan::kWaterHeight ? &s->fWaterHeight : nil;
}
void plAccessSpan::SetSource(plGeometrySpan* s)
{ 
	fLocalToWorld = &s->fLocalToWorld; 
	fWorldToLocal = &s->fWorldToLocal;
	fLocalBounds = &s->fLocalBounds;
	fWorldBounds = &s->fWorldBounds;

	fWaterHeight = s->fProps & plGeometrySpan::kWaterHeight ? &s->fWaterHeight : nil;
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// Simple constructor

plAccessGeometry::plAccessGeometry(plPipeline* pipe)
:	fPipe(pipe)
{
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// Global access stuff.
plAccessGeometry*	plAccessGeometry::fInstance = nil;

void plAccessGeometry::Init(plPipeline* pipe)
{
	plAccessGeometry* oldAcc = fInstance;

	fInstance = NEW(plAccessGeometry)(pipe);

	hsRefCnt_SafeUnRef(oldAcc);
}

void plAccessGeometry::DeInit()
{
	if( fInstance )
		fInstance->Nilify();
	hsRefCnt_SafeUnRef(fInstance);
}

void plAccessGeometry::SetTheIntance(plAccessGeometry* i)
{
	hsRefCnt_SafeAssign(fInstance, i);
}




//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

// The first couple of these just interpret between the SceneObjects we like to
// think about and the clumps of geometry that comprise each one.

void plAccessGeometry::OpenRO(const plDrawInterface* di, hsTArray<plAccessSpan>& accs, hsBool useSnap) const
{
	int numGot = 0;
	accs.SetCount(di->GetNumDrawables());
	accs.SetCount(0);
	int j;
	for( j = 0; j < di->GetNumDrawables(); j++ )
	{
		plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
		// Nil dr - it hasn't loaded yet or something.
		if( dr )
		{
			plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(j));
			if( !diIndex.IsMatrixOnly() )
			{
				int k;
				for( k = 0; k < diIndex.GetCount(); k++ )
				{
					accs.Expand(numGot+1);
					accs.SetCount(numGot+1);
					OpenRO(dr, diIndex[k], accs[numGot++]);
				}
			}
		}
	}

}

void plAccessGeometry::OpenRW(const plDrawInterface* di, hsTArray<plAccessSpan>& accs, hsBool idxToo) const
{
	int numGot = 0;
	accs.Expand(di->GetNumDrawables());
	accs.SetCount(0);
	int j;
	for( j = 0; j < di->GetNumDrawables(); j++ )
	{
		plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
		// Nil dr - it hasn't loaded yet or something.
		if( dr )
		{
			plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(j));
			if( !diIndex.IsMatrixOnly() )
			{
				int k;
				for( k = 0; k < diIndex.GetCount(); k++ )
				{
					accs.Expand(numGot+1);
					accs.SetCount(numGot+1);
					OpenRW(dr, diIndex[k], accs[numGot++], idxToo);
				}
			}
		}
	}
}

void plAccessGeometry::Close(hsTArray<plAccessSpan>& accs) const
{
	int i;
	for( i = 0; i < accs.GetCount(); i++ )
		Close(accs[i]);
}

void plAccessGeometry::TakeSnapShot(const plDrawInterface* di, UInt32 channels) const
{
	int j;
	for( j = 0; j < di->GetNumDrawables(); j++ )
	{
		plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
		// Nil dr - it hasn't loaded yet or something.
		if( dr )
		{
			plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(j));
			if( !diIndex.IsMatrixOnly() )
			{
				int k;
				for( k = 0; k < diIndex.GetCount(); k++ )
				{
					TakeSnapShot(dr, diIndex[k], channels);
				}
			}
		}
	}
}

void plAccessGeometry::RestoreSnapShot(const plDrawInterface* di, UInt32 channels) const
{
	int j;
	for( j = 0; j < di->GetNumDrawables(); j++ )
	{
		plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
		// Nil dr - it hasn't loaded yet or something.
		if( dr )
		{
			plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(j));
			if( !diIndex.IsMatrixOnly() )
			{
				int k;
				for( k = 0; k < diIndex.GetCount(); k++ )
				{
					RestoreSnapShot(dr, diIndex[k], channels);
				}
			}
		}
	}
}

void plAccessGeometry::ReleaseSnapShot(const plDrawInterface* di) const
{
	int j;
	for( j = 0; j < di->GetNumDrawables(); j++ )
	{
		plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
		// Nil dr - it hasn't loaded yet or something.
		if( dr )
		{
			plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(j));
			if( !diIndex.IsMatrixOnly() )
			{
				int k;
				for( k = 0; k < diIndex.GetCount(); k++ )
				{
					ReleaseSnapShot(dr, diIndex[k]);
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

void plAccessGeometry::Close(plAccessSpan& acc) const
{
	if( !fPipe )
		return;

	fPipe->CloseAccess(acc);
}

void plAccessGeometry::IOpen(plDrawable* d, UInt32 spanIdx, plAccessSpan& acc, hsBool useSnap, hsBool readOnly, hsBool idxToo) const
{
	acc.SetType(plAccessSpan::kUndefined);

	plDrawableSpans* drawable = plDrawableSpans::ConvertNoRef(d);
	if( !drawable )
		return;

	if( drawable->GetSourceSpans().GetCount() && !drawable->GetNumSpans() )
		IAccessSpanFromSourceSpan(acc, drawable->GetSourceSpans()[spanIdx]);
	else
		IAccessSpanFromSpan(acc, drawable, drawable->GetSpan(spanIdx), useSnap, readOnly);

	if( !readOnly )
	{
		// Need to mark the drawable's data as dirty.
		plDrawableSpans* ds = plDrawableSpans::ConvertNoRef(drawable);
		if( !ds )
			return;
		if( acc.HasAccessVtx() )
		{
			plVertexSpan* vtx = (plVertexSpan*)ds->GetSpan(spanIdx);
			ds->DirtyVertexBuffer(vtx->fGroupIdx, vtx->fVBufferIdx);
		}

		if( idxToo && acc.HasAccessTri() )
		{
			plIcicle* ice = (plIcicle*)ds->GetSpan(spanIdx);
			ds->DirtyIndexBuffer(ice->fGroupIdx, ice->fIBufferIdx);
		}
	}
}

void plAccessGeometry::OpenRO(plDrawable* d, UInt32 spanIdx, plAccessSpan& acc, hsBool useSnap) const
{
	IOpen(d, spanIdx, acc, useSnap, true);
}

void plAccessGeometry::OpenRW(plDrawable* drawable, UInt32 spanIdx, plAccessSpan& acc, hsBool idxToo) const
{
	IOpen(drawable, spanIdx, acc, false, false, idxToo);

}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

void plAccessGeometry::TakeSnapShot(plDrawable* drawable, UInt32 spanIdx, UInt32 channels) const
{
	plDrawableSpans* ds = plDrawableSpans::ConvertNoRef(drawable);
	if( !ds )
		return;
	const plSpan* span = ds->GetSpan(spanIdx);

	if( !span->fSnapShot )
		span->fSnapShot = NEW(plAccessSnapShot);

	plAccessSpan tmp;
	OpenRO(drawable, spanIdx, tmp, false);

	if( tmp.HasAccessVtx() )
	{
		span->fSnapShot->IncRef();
		span->fSnapShot->CopyFrom(tmp.AccessVtx(), channels);
	}
}

void plAccessGeometry::RestoreSnapShot(plDrawable* drawable, UInt32 spanIdx, UInt32 channels) const
{
	plDrawableSpans* ds = plDrawableSpans::ConvertNoRef(drawable);
	if( !ds )
		return;
	const plSpan* span = ds->GetSpan(spanIdx);

	if( !span->fSnapShot )
		return;

	plAccessSpan tmp;
	OpenRW(drawable, spanIdx, tmp);

	if( tmp.HasAccessVtx() )
		span->fSnapShot->CopyTo(tmp.AccessVtx(), channels);
}

void plAccessGeometry::ReleaseSnapShot(plDrawable* drawable, UInt32 spanIdx) const
{
	plDrawableSpans* ds = plDrawableSpans::ConvertNoRef(drawable);
	if( !ds )
		return;
	const plSpan* span = ds->GetSpan(spanIdx);

	if( !span->fSnapShot )
		return;

	span->fSnapShot->Release();
}

//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////

void plAccessGeometry::IAccessSpanFromSourceSpan(plAccessSpan& dst, const plGeometrySpan* src) const
{
	// Get the connectivity info
	dst.SetType(plAccessSpan::kTri);
	plAccessTriSpan& tri = dst.AccessTri();
	tri.fNumTris = src->fNumIndices / 3;
	tri.fTris = src->fIndexData;
	tri.fIdxDeviceRef = nil;

	dst.SetSource(const_cast<plGeometrySpan*>(src));
	dst.SetMaterial(src->fMaterial);

	plAccessVtxSpan& acc = dst.AccessVtx();
	acc.fNumVerts = (UInt16)(src->fNumVerts);
	UInt32 sz = src->GetVertexSize(src->fFormat);
	UInt8* ptr = src->fVertexData;
	acc.PositionStream(ptr, (UInt16)sz, 0);
	ptr += sizeof(hsPoint3);
	acc.NormalStream(ptr, (UInt16)sz, 0);
	ptr += sizeof(hsVector3);
	acc.DiffuseStream(src->fDiffuseRGBA, sizeof(UInt32), 0);
	acc.SpecularStream(src->fSpecularRGBA, sizeof(UInt32), 0);
	acc.UVWStream(ptr, (UInt16)sz, 0);
	acc.SetNumUVWs(src->CalcNumUVs(src->fFormat));

	acc.fVtxDeviceRef = nil;
}

void plAccessGeometry::IAccessSpanFromSpan(plAccessSpan& dst, plDrawableSpans* drawable, const plSpan* span, hsBool useSnap, hsBool readOnly) const
{
	dst.SetType(plAccessSpan::kUndefined);
	dst.SetSource(const_cast<plSpan*> (span));
	if( span->fTypeMask & plSpan::kIcicleSpan )
	{
		IAccessSpanFromIcicle(dst, drawable, (const plIcicle*)span, readOnly);
	}
	else if( span->fTypeMask & plSpan::kParticleSpan )
	{
		IAccessSpanFromParticle(dst, drawable, (const plParticleSpan*)span, readOnly);
	}
	if( useSnap )
	{
		IAccessSpanFromSnap(dst, drawable, span);
	}
}

void plAccessGeometry::IAccessSpanFromSnap(plAccessSpan& dst, plDrawableSpans* drawable, const plSpan* span) const
{
	plAccessVtxSpan& acc = dst.AccessVtx();
	if( span->fSnapShot )
	{
		span->fSnapShot->SetupChannels(acc);
	}
}

void plAccessGeometry::IAccessSpanFromVertexSpan(plAccessSpan& dst, plDrawableSpans* drawable, const plVertexSpan* span, hsBool readOnly) const
{
	dst.SetMaterial(drawable->GetMaterial(span->fMaterialIdx));

	plAccessVtxSpan& acc = dst.AccessVtx();
	
	plGBufferGroup* grp = drawable->GetBufferGroup(span->fGroupIdx);

//#define MF_TOSSER
#ifndef MF_TOSSER
	plConst(hsBool) useDev(false);
#else // MF_TOSSER
	plConst(hsBool) useDev(true);
#endif // MF_TOSSER
	if( useDev && !drawable->GetNativeProperty(plDrawable::kPropVolatile) && grp->GetVertexBufferRef(span->fVBufferIdx) )
	{
		fPipe->OpenAccess(dst, drawable, span, readOnly);
		return;
	}

	acc.fNumVerts = (UInt16)(span->fVLength);

	plGBufferCell* cell = grp->GetCell(span->fVBufferIdx, span->fCellIdx);

	UInt8* ptr = grp->GetVertBufferData(span->fVBufferIdx);

	// Interleaved
	if( cell->fColorStart == UInt32(-1) )
	{
		UInt32 stride = grp->GetVertexSize();

		ptr += cell->fVtxStart + span->fCellOffset * stride;
		Int32 offset = (-(Int32)(span->fVStartIdx)) * (Int32)stride;

		acc.PositionStream(ptr, (UInt16)stride, offset);
		ptr += sizeof(hsPoint3);

		int numWgts = grp->GetNumWeights();
		if( numWgts )
		{
			acc.SetNumWeights(numWgts);
			acc.WeightStream(ptr, (UInt16)stride, offset);
			ptr += numWgts * sizeof(hsScalar);
			if( grp->GetVertexFormat() & plGBufferGroup::kSkinIndices )
			{
				acc.WgtIndexStream(ptr, (UInt16)stride, offset);
				ptr += sizeof(UInt32);
			}
			else
			{
				acc.WgtIndexStream(nil, 0, offset);
			}
		}
		else
		{
			acc.SetNumWeights(0);
		}

		acc.NormalStream(ptr, (UInt16)stride, offset);
		ptr += sizeof(hsVector3);

		acc.DiffuseStream(ptr, (UInt16)stride, offset);
		ptr += sizeof(UInt32);

		acc.SpecularStream(ptr, (UInt16)stride, offset);
		ptr += sizeof(UInt32);

		acc.UVWStream(ptr, (UInt16)stride, offset);

		acc.SetNumUVWs(grp->GetNumUVs());

	}
	else
	{
		UInt32 stride = grp->GetVertexLiteStride();

		ptr += cell->fVtxStart + span->fCellOffset * stride;
		Int32 posOffset = (-(Int32)(span->fVStartIdx)) * (Int32)stride;

		acc.PositionStream(ptr, (UInt16)stride, posOffset);
		ptr += sizeof(hsPoint3);

		int numWgts = grp->GetNumWeights();
		if( numWgts )
		{
			acc.SetNumWeights(numWgts);
			acc.WeightStream(ptr, (UInt16)stride, posOffset);
			ptr += numWgts * sizeof(hsScalar);
			if( grp->GetVertexFormat() & plGBufferGroup::kSkinIndices )
			{
				acc.WgtIndexStream(ptr, (UInt16)stride, posOffset);
				ptr += sizeof(UInt32);
			}
			else
			{
				acc.WgtIndexStream(nil, 0, 0);
			}
		}
		else
		{
			acc.SetNumWeights(0);
		}

		acc.NormalStream(ptr, (UInt16)stride, posOffset);
		ptr += sizeof(hsVector3);

		plGBufferColor* col = grp->GetColorBufferData(span->fVBufferIdx) + cell->fColorStart;

		Int16 colOffset = (Int16)((-(Int32)(span->fVStartIdx)) * (Int32)stride);
		colOffset = (Int16)((-(Int32)(span->fVStartIdx)) * sizeof(*col));

		acc.DiffuseStream(&col->fDiffuse, sizeof(*col), colOffset);

		acc.SpecularStream(&col->fSpecular, sizeof(*col), colOffset);

		acc.UVWStream(ptr, (UInt16)stride, posOffset);

		acc.SetNumUVWs(grp->GetNumUVs());
	}

	acc.fVtxDeviceRef = nil;
}

void plAccessGeometry::IAccessConnectivity(plAccessSpan& dst, plDrawableSpans* drawable, const plSpan* src) const
{
	if( src->fTypeMask & plSpan::kIcicleSpan )
	{
		const plIcicle* span = (const plIcicle*)src;

		dst.SetType(plAccessSpan::kTri);

		plAccessTriSpan& acc = dst.AccessTri();
		
		acc.fNumTris = span->fILength / 3;
		plGBufferGroup* grp = drawable->GetBufferGroup(span->fGroupIdx);
		acc.fTris = grp->GetIndexBufferData(span->fIBufferIdx) + span->fIStartIdx;

		acc.fIdxDeviceRef = nil;
	}
	// Hmm, particle should probably go here...
	else 
	{
		dst.SetType(plAccessSpan::kVtx);
	}

}

void plAccessGeometry::IAccessSpanFromIcicle(plAccessSpan& dst, plDrawableSpans* drawable, const plIcicle* span, hsBool readOnly) const
{
	dst.SetType(plAccessSpan::kTri);

	plAccessTriSpan& acc = dst.AccessTri();
	
	IAccessSpanFromVertexSpan(dst, drawable, span, readOnly);
	
	acc.fNumTris = span->fILength / 3;
	plGBufferGroup* grp = drawable->GetBufferGroup(span->fGroupIdx);
	acc.fTris = grp->GetIndexBufferData(span->fIBufferIdx) + span->fIStartIdx;

	acc.fIdxDeviceRef = nil;
}

void plAccessGeometry::IAccessSpanFromParticle(plAccessSpan& dst, plDrawableSpans* drawable, const plParticleSpan* span, hsBool readOnly) const
{
	hsAssert(false, "Aint got to it yet");
	// dst.SetType(plAccessSpan::kParty);
}