/*==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 "plStaticEnvLayer.h"

#include "iparamb2.h"
#include "iparamm2.h"
#include "stdmat.h"

#include "plBMSampler.h"
#include "../MaxMain/plPlasmaRefMsgs.h"

class plStaticEnvLayerClassDesc : public ClassDesc2
{
public:
	int 			IsPublic()		{ return TRUE; }
	void*			Create(BOOL loading = FALSE) { return TRACKED_NEW plStaticEnvLayer(); }
	const TCHAR*	ClassName()		{ return GetString(IDS_STATIC_ENVMAP_LAYER); }
	SClass_ID		SuperClassID()	{ return TEXMAP_CLASS_ID; }
	Class_ID		ClassID()		{ return STATIC_ENV_LAYER_CLASS_ID; }
	const TCHAR* 	Category()		{ return TEXMAP_CAT_ENV; }
	const TCHAR*	InternalName()	{ return _T("PlasmaStaticEnvMapLayer"); }
	HINSTANCE		HInstance()		{ return hInstance; }
};
static plStaticEnvLayerClassDesc plStaticEnvLayerDesc;
ClassDesc2* GetStaticEnvLayerDesc() { return &plStaticEnvLayerDesc; }

#include "plStaticEnvLayerBitmapPB.cpp"

plStaticEnvLayer::plStaticEnvLayer() :
	fBitmapPB(NULL),
	fUVGen(NULL),
	fTexHandle(NULL),
	fTexTime(0),
	fIValid(NEVER)
{
	int	i;

	for( i = 0; i < 6; i++ )
	{
		fBitmaps[ i ] = NULL;
	}

	plStaticEnvLayerDesc.MakeAutoParamBlocks(this);
	ReplaceReference(kRefUVGen, GetNewDefaultUVGen());	
}

plStaticEnvLayer::~plStaticEnvLayer()
{
	int		i;


	for( i = 0; i < 6; i++ )
	{
		if( fBitmaps[ i ] )
			fBitmaps[ i ]->DeleteThis();
	}

	IDiscardTexHandle();
}

//From MtlBase
void plStaticEnvLayer::Reset() 
{
	GetStaticEnvLayerDesc()->Reset(this, TRUE);	// reset all pb2's
	for( int i = 0; i < 6; i++ )
	{
		SetBitmap( NULL, i );
	}

	fIValid.SetEmpty();
}

void plStaticEnvLayer::Update(TimeValue t, Interval& valid) 
{
	if (!fIValid.InInterval(t))
	{
		fIValid.SetInfinite();

        fUVGen->Update(t,fIValid);
		fBitmapPB->GetValidity(t, fIValid);
	}

	// Gonna need to do this when we support animated bm's
#if 0
	if (fBM)
	{
		if (bi.FirstFrame()!=bi.LastFrame())
			ivalid.SetInstant(t);
	}
#endif

	valid &= fIValid;
}

Interval plStaticEnvLayer::Validity(TimeValue t)
{
	//TODO: Update fIValid here

	// mf horse - Hacking this in just to get animations working.
	// No warranty on this not being stupid.
	Interval v = FOREVER;
	fBitmapPB->GetValidity(t, v);
	v &= fUVGen->Validity(t);
	return v;
}

ParamDlg* plStaticEnvLayer::CreateParamDlg(HWND hwMtlEdit, IMtlParams *imp) 
{
	fIMtlParams = imp;
	IAutoMParamDlg* masterDlg = plStaticEnvLayerDesc.CreateParamDlgs(hwMtlEdit, imp, this);

	SELBitmapDlgProc *paramDlg =  (SELBitmapDlgProc *)gBitmapParamBlk.GetUserDlgProc();
	if( paramDlg )
		paramDlg->fMtlParams = imp;

	return masterDlg;	
}

BOOL plStaticEnvLayer::SetDlgThing(ParamDlg* dlg)
{	
	return FALSE;
}

int plStaticEnvLayer::NumRefs()
{
	return 2;
}

//From ReferenceMaker
RefTargetHandle plStaticEnvLayer::GetReference(int i) 
{
	switch (i)
	{
		case kRefUVGen:		return fUVGen;
		case kRefBitmap:	return fBitmapPB;
		default: return NULL;
	}
}

void plStaticEnvLayer::SetReference(int i, RefTargetHandle rtarg) 
{
	Interval	garbage;

	switch (i)
	{
		case kRefUVGen:  
			fUVGen = (UVGen *)rtarg; 
			if( fUVGen )
				fUVGen->Update( TimeValue( 0 ), garbage );
			break;
		case kRefBitmap:
			fBitmapPB = (IParamBlock2 *)rtarg;
			// KLUDGE: If the paramblock is being set chances are we are being created or
			// loaded.  In the case of load, we want to refresh our textures.
			if (fBitmapPB)
				RefreshBitmaps();
			break;
	}
}

int	plStaticEnvLayer::NumParamBlocks()
{
	return 1;
}

IParamBlock2* plStaticEnvLayer::GetParamBlock(int i)
{
	switch (i)
	{
	case 0:	return fBitmapPB;
	default: return NULL;
	}
}

IParamBlock2* plStaticEnvLayer::GetParamBlockByID(BlockID id)
{
	if (fBitmapPB->ID() == id)
		return fBitmapPB;
	else
		return NULL;
}

//From ReferenceTarget 
RefTargetHandle plStaticEnvLayer::Clone(RemapDir &remap) 
{
	plStaticEnvLayer *mnew = TRACKED_NEW plStaticEnvLayer();
	*((MtlBase*)mnew) = *((MtlBase*)this); // copy superclass stuff
	mnew->ReplaceReference(kRefBitmap, remap.CloneRef(fBitmapPB));
	mnew->ReplaceReference(kRefUVGen, remap.CloneRef(fUVGen));
	BaseClone(this, mnew, remap);
	return (RefTargetHandle)mnew;
}

int plStaticEnvLayer::NumSubs()
{
	return 2;
}

Animatable* plStaticEnvLayer::SubAnim(int i) 
{
	//TODO: Return 'i-th' sub-anim
	switch (i)
	{
		case kRefUVGen:		return fUVGen;
		case kRefBitmap:	return fBitmapPB;
		default: return NULL;
	}
}

TSTR plStaticEnvLayer::SubAnimName(int i) 
{
	switch (i)
	{
		case kRefUVGen:		return "UVGen";
		case kRefBitmap:	return fBitmapPB->GetLocalName();
		default: return "";
	}
}

RefResult plStaticEnvLayer::NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, 
   PartID& partID, RefMessage message) 
{
	switch (message)
	{
	case REFMSG_CHANGE:
		{
			fIValid.SetEmpty();

			if (hTarget == fBitmapPB)
			{
				// see if this message came from a changing parameter in the pblock,
				// if so, limit rollout update to the changing item and update any active viewport texture
				ParamID changingParam = fBitmapPB->LastNotifyParamID();
				fBitmapPB->GetDesc()->InvalidateUI(changingParam);

				if (changingParam != -1)
					IChanged();
			}
		}
		break;

	case REFMSG_UV_SYM_CHANGE:
		IDiscardTexHandle();  
		break;
	}

	return REF_SUCCEED;
}

void plStaticEnvLayer::IChanged()
{
	IDiscardTexHandle();
	// Texture wasn't getting updated in the viewports, and this fixes it.
	// Don't know if it's the right way though.
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);

	// And this is so the SceneWatcher gets notified that the material on some of it's
	// referenced objects changed.
	NotifyDependents(FOREVER, PART_ALL, REFMSG_USER_MAT);
}

#define TEX_HDR_CHUNK 0x5000
#define MAX_ASS_CHUNK 0x5500

IOResult plStaticEnvLayer::Save(ISave *isave) 
{
	IOResult res;

	isave->BeginChunk(TEX_HDR_CHUNK);
	res = MtlBase::Save(isave);
	if (res != IO_OK)
		return res;
	isave->EndChunk();

	return IO_OK;
}	

IOResult plStaticEnvLayer::Load(ILoad *iload) 
{
	IOResult res;
	while (IO_OK == (res = iload->OpenChunk()))
	{
		if (iload->CurChunkID() == TEX_HDR_CHUNK)
		{
			res = MtlBase::Load(iload);
		}
		iload->CloseChunk();
		if (res != IO_OK) 
			return res;
	}

	return IO_OK;
}

inline Point2 CompUV(float x, float y, float z) 
{
	return Point2( 0.5f * ( x / z + 1.0f ), 0.5f * ( y / z + 1.0f ) );
}

AColor plStaticEnvLayer::EvalColor(ShadeContext& sc)
{
	if (!sc.doMaps) 
		return AColor(0.0f, 0.0f, 0.0f, 1.0f);

	AColor color;
	if (sc.GetCache(this, color)) 
		return color;

	if (gbufID) 
		sc.SetGBufferID(gbufID);

	// Evaluate the Bitmap

//	Point3	v = sc.VectorTo( sc.V(), REF_OBJECT );//WORLD );
	Point3	v = sc.VectorTo( sc.Normal(), REF_OBJECT );
	float	wx,wy,wz;
	Color	rcol;
	Bitmap	*refmap = NULL;
	Point3	rv;
	Point2	uv;
	int		size;

	wx = (float)fabs( v.x );  
	wy = (float)fabs( v.y );
	wz = (float)fabs( v.z ); 
	if( wx >= wy && wx >= wz )
	{
		if( v.x < 0 )
		{
			refmap = fBitmaps[ kLeftFace ];
			uv = CompUV( -v.y, -v.z,  v.x );
		}
		else
		{
			refmap = fBitmaps[ kRightFace ];
			uv = CompUV( v.y, -v.z, -v.x );
		}
	}
	else if( wy >= wx && wy >= wz ) 
	{
		if( v.y > 0 )
		{
			refmap = fBitmaps[ kBackFace ];
			uv = CompUV( -v.x, -v.z, -v.y );
		}
		else
		{
			refmap = fBitmaps[ kFrontFace ];
			uv = CompUV(  v.x, -v.z,  v.y );
		}
	}
	else if( wz >= wx && wz >= wy ) 
	{
		if( v.z < 0 )
		{	
			refmap = fBitmaps[ kBottomFace ];
			uv = CompUV( -v.x, -v.y,  v.z );
		}
		else     
		{	
			refmap = fBitmaps[ kTopFace ];
			uv = CompUV( -v.x,  v.y, -v.z );
		}
	}

	if( refmap == NULL )
		color.White();
	else
	{
		if( uv.x < 0.0f )
			uv.x = 0.0f; 
		else if( uv.x > 1.0f )
			uv.x = 1.0f;
		if( uv.y < 0.0f )
			uv.y = 0.0f; 
		else if( uv.y > 1.0f )
			uv.y = 1.0f;
		size = refmap->Width();
		int x = (int)( uv.x * (float)( size - 1 ) );
		int y = (int)( ( 1.0f - uv.y ) * (float)( size - 1 ) );

		BMM_Color_64 c;
		refmap->GetLinearPixels( x, y, 1, &c );
		color = AColor( c.r / 65535.f, c.g / 65535.f, c.b / 65535.f, c.a / 65535.f );
	}

	// Invert color if specified
	if( fBitmapPB->GetInt( kBmpInvertColor ) )
	{
		color.r = 1.0f - color.r;
		color.g = 1.0f - color.g;
		color.b = 1.0f - color.b;
	}
	// Discard color if specified
	if( fBitmapPB->GetInt( kBmpDiscardColor ) )
		color.r = color.g = color.b = 1.0f;

	// Invert alpha if specified
	if( fBitmapPB->GetInt( kBmpInvertAlpha ) )
		color.a = 1.0f - color.a;
	// Discard alpha if specified
	if( fBitmapPB->GetInt( kBmpDiscardAlpha ) )
		color.a = 1.0f;

	// If RGB output is set to alpha, show RGB as grayscale of the alpha
	if( fBitmapPB->GetInt( kBmpRGBOutput ) == 1 )
		color = AColor( color.a, color.a, color.a, 1.0f );

	sc.PutCache(this, color); 
	return color;
}

float plStaticEnvLayer::EvalMono(ShadeContext& sc)
{
	if (fBitmapPB->GetInt(kBmpMonoOutput) == 1)
		return EvalColor(sc).a;

	return Intens(EvalColor(sc));
}

Point3 plStaticEnvLayer::EvalNormalPerturb(ShadeContext& sc)
{
	// Return the perturbation to apply to a normal for bump mapping
	return Point3(0, 0, 0);
}

ULONG plStaticEnvLayer::LocalRequirements(int subMtlNum)
{
	if( fBitmapPB->GetInt( kBmpUseMAXAtmosphere ) )
		return MTLREQ_VIEW_DEP;

	return MTLREQ_VIEW_DEP | MTLREQ_NOATMOS;
}

void plStaticEnvLayer::IDiscardTexHandle() 
{
	if (fTexHandle)
	{
		fTexHandle->DeleteThis();
		fTexHandle = NULL;
	}
}

void plStaticEnvLayer::ActivateTexDisplay(BOOL onoff)
{
	if (!onoff)
		IDiscardTexHandle();
}

BITMAPINFO *plStaticEnvLayer::GetVPDisplayDIB(TimeValue t, TexHandleMaker& thmaker, Interval &valid, BOOL mono, BOOL forceW, BOOL forceH)
{
						// FIXME
	fTexTime = 0;//CalcFrame(t);
//	texValid = clipValid;
	BITMAPINFO *bmi = NULL;
	int xflags = 0;

	if (fBitmapPB->GetInt(kBmpRGBOutput) == 1)
		xflags |= EX_RGB_FROM_ALPHA;
	bmi = thmaker.BitmapToDIB(fBitmaps[ 0 ], fUVGen->SymFlags(), xflags, forceW, forceH);

	return bmi;
}

DWORD plStaticEnvLayer::GetActiveTexHandle(TimeValue t, TexHandleMaker& thmaker) 
{
	// FIXME: ignore validity for now
	if (fTexHandle && fIValid.InInterval(t))// && texTime == CalcFrame(t)) 
		return fTexHandle->GetHandle();
	else
	{
		IDiscardTexHandle();
		
		fTexTime = 0;//CalcFrame(t);
		fTexHandle = thmaker.MakeHandle(GetVPDisplayDIB(t, thmaker, fIValid));
		if (fTexHandle)
			return fTexHandle->GetHandle();
		else
			return 0;
	}
}

const char *plStaticEnvLayer::GetTextureName( int which )
{
//	if (fBitmapPB->GetInt(kBmpUseBitmap))
	{
		PBBitmap *pbbm = fBitmapPB->GetBitmap( kBmpFrontBitmap + which );
		if (pbbm)
			return pbbm->bi.Name();
	}

	return NULL;
}

//// Set/GetBaseFilename //////////////////////////////////////////////////////

void	plStaticEnvLayer::SetBaseFilename( const TCHAR *name, TimeValue t )
{
	fBitmapPB->SetValue( kBmpBaseFilename, t, (TCHAR *)name );
}

const TCHAR	*plStaticEnvLayer::GetBaseFilename( TimeValue t )
{
	Interval		valid;
	TCHAR			*buffer;

	fBitmapPB->GetValue( kBmpBaseFilename, t, buffer, valid );
	return (const TCHAR *)buffer;
}

//// IGetViewTM ///////////////////////////////////////////////////////////////

Matrix3	plStaticEnvLayer::IGetViewTM( int i )
{
	Matrix3 m;
	m.IdentityMatrix();
	switch( i ) 
	{
		case kTopFace:
			m.RotateX( -PI );	
			break;
		case kBottomFace:
			break;
		case kLeftFace:
			m.RotateX( -.5f * PI );	
			m.RotateY( -.5f * PI );
			break;
		case kRightFace:
			m.RotateX( -.5f * PI );	
			m.RotateY( +.5f * PI );
			break;
		case kFrontFace:
			m.RotateX( -.5f * PI );	
			m.RotateY( PI );
			break;
		case kBackFace:
			m.RotateX( -.5f * PI );	
			break;
	}
	return m;
}

//// IWriteBM /////////////////////////////////////////////////////////////////

int	plStaticEnvLayer::IWriteBM( BitmapInfo *bi, Bitmap *bm, TCHAR *name )
{
	bi->SetName( name );
	if( bm->OpenOutput( bi ) == BMMRES_SUCCESS )
	{
		if( bm->Write( bi, BMM_SINGLEFRAME ) == BMMRES_SUCCESS ) 
		{
			bm->Close( bi );
			return 1;
		}
	}

	return 0;
}

//// RenderCubicMap ///////////////////////////////////////////////////////////
//	Generates the 6 faces for a cubic map based on a picked node

void	plStaticEnvLayer::RenderCubicMap( INode *node )
{
	int			res, size;
	BOOL		success = 0;
	TSTR		fname, fullname;
	Bitmap		*bm = NULL;
	TSTR		path, filename, ext, thisFilename;
	BitmapInfo	biOutFile;

	static TCHAR	suffixes[ 6 ][ 4 ] = { "_FR", "_BK", "_LF", "_RT", "_UP", "_DN" };


	Interface *ip = GetCOREInterface();
	size = fBitmapPB->GetInt( kBmpTextureSize, ip->GetTime() );
	if( size <= 0 ) 
	{
		return;
	}

	thisFilename = fBitmapPB->GetStr( kBmpBaseFilename, ip->GetTime() );
	if( thisFilename.isNull() )
	{
		return;
	}

	SplitFilename( thisFilename, &path, &filename, &ext );

	BOOL	wasHid = node->IsNodeHidden();
	node->Hide( TRUE );

	// Create a blank bitmap
	biOutFile.SetWidth( size );
	biOutFile.SetHeight( size );
	biOutFile.SetType( BMM_TRUE_64 );
	biOutFile.SetAspect( 1.0f );
	biOutFile.SetCurrentFrame( 0 );
	bm = TheManager->Create( &biOutFile );

	Matrix3 nodeTM = node->GetNodeTM( ip->GetTime() );
	Matrix3 tm;	
	INode *root = ip->GetRootNode();		
	bm->Display( GetString( IDS_CUBIC_RENDER_TITLE ) );

	/// Set up rendering contexts
	ViewParams vp;
	vp.projType = PROJ_PERSPECTIVE;
	vp.hither = .001f;
	vp.yon = 1.0e30f;
	vp.fov = PI/2.0f;
	if( fBitmapPB->GetInt( kBmpUseMAXAtmosphere ) )
	{
		vp.nearRange = 0;
		vp.farRange = fBitmapPB->GetFloat( kBmpFarDistance );
	}
	else
	{
		vp.nearRange = vp.farRange = 1.0e30f;
	}
	BOOL	saveUseEnvMap = ip->GetUseEnvironmentMap();
	ip->SetUseEnvironmentMap( false );

	res = ip->OpenCurRenderer( &vp ); 
	for( int i = 0; i < 6; i++ )
	{
		tm = IGetViewTM( i );
		tm.PreTranslate( -nodeTM.GetTrans() ); 
		vp.affineTM = tm;

		// Construct filename
		thisFilename.printf( _T( "%s\\%s%s%s" ), path, filename, suffixes[ i ], ext );

		res = ip->CurRendererRenderFrame( ip->GetTime(), bm, NULL, 1.0f, &vp );
		if( !res ) 
			goto fail;

		if( !IWriteBM( &biOutFile, bm, thisFilename ) ) 
			goto fail;
	}

	success = 1;
fail:
	ip->CloseCurRenderer();	
	ip->SetUseEnvironmentMap( saveUseEnvMap );

	bm->DeleteThis();
	node->Hide( wasHid );
	if( success )
	{
		for(int i = 0; i < 6; i++ )
		{
			BitmapInfo	bi;
			thisFilename.printf( _T( "%s\\%s%s%s" ), path, filename, suffixes[ i ], ext );
			bi.SetName( thisFilename );

			PBBitmap	pbBitmap( bi );
			fBitmapPB->SetValue( kBmpFrontBitmap + i, ip->GetTime(), &pbBitmap );
		}
		fBitmapPB->GetMap()->UpdateUI( ip->GetTime() );
	}
}

PBBitmap *plStaticEnvLayer::GetPBBitmap(int index /* = 0 */)
{ 
	return fBitmapPB->GetBitmap( ParamID( kBmpFrontBitmap + index ) ); 
}

void plStaticEnvLayer::ISetPBBitmap( PBBitmap *pbbm, int index )
{
	fBitmapPB->SetValue( ParamID( kBmpFrontBitmap + index ), 0, pbbm );
}