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

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

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

class plAngleAttenLayerClassDesc : public ClassDesc2
{
public:
	int 			IsPublic()		{ return TRUE; }
	void*			Create(BOOL loading = FALSE) { return TRACKED_NEW plAngleAttenLayer(); }
	const TCHAR*	ClassName()		{ return GetString(IDS_ANGLE_ATTEN_LAYER); }
	SClass_ID		SuperClassID()	{ return TEXMAP_CLASS_ID; }
	Class_ID		ClassID()		{ return ANGLE_ATTEN_LAYER_CLASS_ID; }
	const TCHAR* 	Category()		{ return TEXMAP_CAT_COLMOD; }
	const TCHAR*	InternalName()	{ return _T("PlasmaAngleAttenLayer"); }
	HINSTANCE		HInstance()		{ return hInstance; }
};
static plAngleAttenLayerClassDesc plAngleAttenLayerDesc;
ClassDesc2* GetAngleAttenLayerDesc() { return &plAngleAttenLayerDesc; }

///////////////////////////////////////////////////////////////////////////////
//// ParamBlock Definition ////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

static const float kDefTransp0 = 60.f;
static const float kDefOpaque0 = 90.f;
static const float kDefTransp1 = 30.f;
static const float kDefOpaque1 = 0.f;

static ParamBlockDesc2 gAngleAttenParamBlk
(
	plAngleAttenLayer::kBlkAngles, _T("angles"),  0, GetAngleAttenLayerDesc(),//NULL,
	P_AUTO_CONSTRUCT + P_AUTO_UI, plAngleAttenLayer::kRefAngles,

	IDD_ANGLE_ATTEN_LAYER, IDS_ANGLE_ATTEN_LAYER_PROPS, 0, 0, nil,

	// Texture size
	plAngleAttenLayer::kTranspAngle0,	_T("transp0"),	TYPE_FLOAT,	0, 0,
		p_ui,			TYPE_SPINNER, EDITTYPE_INT, IDC_TRANSP_ANGLE_0, IDC_TRANSP_ANGLE_0_SPIN, SPIN_AUTOSCALE,
		p_range,		0.0, 180.0,
		p_default,		kDefTransp0,
		end,

	plAngleAttenLayer::kOpaqueAngle0,	_T("opaque0"),	TYPE_FLOAT,	0, 0,
		p_ui,			TYPE_SPINNER, EDITTYPE_INT, IDC_OPAQUE_ANGLE_0, IDC_OPAQUE_ANGLE_0_SPIN, SPIN_AUTOSCALE,
		p_range,		0.0, 180.0,
		p_default,		kDefOpaque0,
		end,

	plAngleAttenLayer::kDoubleFade,	_T("doubleFade"),	TYPE_BOOL,		0, 0,
		p_ui,			TYPE_SINGLECHEKBOX, IDC_DOUBLE_FADE,
		p_enable_ctrls,		2, plAngleAttenLayer::kOpaqueAngle1, plAngleAttenLayer::kTranspAngle1,
		p_default,		false,
		end,

	plAngleAttenLayer::kOpaqueAngle1,	_T("opaque1"),	TYPE_FLOAT,	0, 0,
		p_ui,			TYPE_SPINNER, EDITTYPE_INT, IDC_OPAQUE_ANGLE_1, IDC_OPAQUE_ANGLE_1_SPIN, SPIN_AUTOSCALE,
		p_range,		0.0, 180.0,
		p_default,		kDefTransp1,
		end,

	plAngleAttenLayer::kTranspAngle1,	_T("transp1"),	TYPE_FLOAT,	0, 0,
		p_ui,			TYPE_SPINNER, EDITTYPE_INT, IDC_TRANSP_ANGLE_1, IDC_TRANSP_ANGLE_1_SPIN, SPIN_AUTOSCALE,
		p_range,		0.0, 180.0,
		p_default,		kDefOpaque1,
		end,

	plAngleAttenLayer::kReflect,	_T("reflect"),	TYPE_BOOL,		0, 0,
		p_ui,			TYPE_SINGLECHEKBOX, IDC_REFLECT,
		p_default,		false,
		end,

	plAngleAttenLayer::kLoClamp,	_T("loClamp"),	TYPE_INT,	0, 0,
		p_ui,			TYPE_SPINNER, EDITTYPE_INT, IDC_LO_CLAMP, IDC_LO_CLAMP_SPIN, SPIN_AUTOSCALE,
		p_range,		0, 100,
		p_default,		0,
		end,

	plAngleAttenLayer::kHiClamp,	_T("hiClamp"),	TYPE_INT,	0, 0,
		p_ui,			TYPE_SPINNER, EDITTYPE_INT, IDC_HI_CLAMP, IDC_HI_CLAMP_SPIN, SPIN_AUTOSCALE,
		p_range,		0, 100,
		p_default,		100,
		end,

	end
);

plAngleAttenLayer::plAngleAttenLayer() :
	fParmsPB(NULL),
	fIValid(NEVER),
	fCosTransp0(0),
	fCosOpaque0(0),
	fCosTransp1(0),
	fCosOpaque1(0),
	fCosinesCached(false)
{
	plAngleAttenLayerDesc.MakeAutoParamBlocks(this);
}

plAngleAttenLayer::~plAngleAttenLayer()
{
}

//From MtlBase
void plAngleAttenLayer::Reset() 
{
	GetAngleAttenLayerDesc()->Reset(this, TRUE);	// reset all pb2's
	NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);

	fIValid.SetEmpty();
}

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

	}

	valid &= fIValid;
}

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

	Interval v = FOREVER;
	return v;
}

ParamDlg* plAngleAttenLayer::CreateParamDlg(HWND hwMtlEdit, IMtlParams *imp) 
{
	IAutoMParamDlg* masterDlg = plAngleAttenLayerDesc.CreateParamDlgs(hwMtlEdit, imp, this);

	return masterDlg;	
}

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

int plAngleAttenLayer::NumRefs()
{
	return 1;
}

//From ReferenceMaker
RefTargetHandle plAngleAttenLayer::GetReference(int i) 
{
	switch (i)
	{
		case kRefAngles:		return fParmsPB;
		default:				return NULL;
	}
}

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

	switch (i)
	{
		case kRefAngles:
			fParmsPB = (IParamBlock2 *)rtarg;
			break;
	}
}

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

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

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

//From ReferenceTarget 
RefTargetHandle plAngleAttenLayer::Clone(RemapDir &remap) 
{
	plAngleAttenLayer *mnew = TRACKED_NEW plAngleAttenLayer();
	*((MtlBase*)mnew) = *((MtlBase*)this); // copy superclass stuff
	mnew->ReplaceReference(kRefAngles, remap.CloneRef(fParmsPB));
	BaseClone(this, mnew, remap);
	return (RefTargetHandle)mnew;
}

int plAngleAttenLayer::NumSubs()
{
	return 1;
}

Animatable* plAngleAttenLayer::SubAnim(int i) 
{
	//TODO: Return 'i-th' sub-anim
	switch (i)
	{
		case kRefAngles:		return fParmsPB;
		default: return NULL;
	}
}

TSTR plAngleAttenLayer::SubAnimName(int i) 
{
	switch (i)
	{
		case kRefAngles:		return "Angles";
		default: return "";
	}
}

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

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

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

	}

	return REF_SUCCEED;
}

void plAngleAttenLayer::IChanged()
{
	// Cut and paste insanity from DynamicTextLayer.
	// 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);

	ICacheCosines();
}

void plAngleAttenLayer::ICacheCosines()
{
	fCosTransp0 = cosf(DegToRad(fParmsPB->GetFloat(kTranspAngle0)));
	fCosOpaque0 = cosf(DegToRad(fParmsPB->GetFloat(kOpaqueAngle0)));

	if( fParmsPB->GetInt(kDoubleFade) )
	{
		fCosTransp1 = cosf(DegToRad(fParmsPB->GetFloat(kTranspAngle1)));
		fCosOpaque1 = cosf(DegToRad(fParmsPB->GetFloat(kOpaqueAngle1)));
	}
	else
	{
		fCosTransp1 = fCosOpaque1 = 0;
	}
	fCosinesCached = true;
}

#define TEX_HDR_CHUNK 0x5000

IOResult plAngleAttenLayer::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 plAngleAttenLayer::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;
}


AColor plAngleAttenLayer::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( !fCosinesCached )
		ICacheCosines();

	if (gbufID) 
		sc.SetGBufferID(gbufID);

	// Evaluate the Bitmap

	Point3 normal = sc.Normal();

	if( fParmsPB->GetInt(kReflect) )
	{
		normal = sc.ReflectVector();
	}
	float dotZ = normal.z;

	float alpha = 1.f;
	if( fCosTransp0 != fCosOpaque0 )
	{
		float a = (dotZ - fCosTransp0) / (fCosOpaque0 - fCosTransp0);
		if( a < 0 )
			a = 0;
		else if( a > 1.f )
			a = 1.f;
		alpha *= a;
	}
	if( fParmsPB->GetInt(kDoubleFade) && (fCosTransp1 != fCosOpaque1) )
	{
		float a = (dotZ - fCosTransp1) / (fCosOpaque1 - fCosTransp1);
		if( a < 0 )
			a = 0;
		else if( a > 1.f )
			a = 1.f;
		if( fCosTransp0 < fCosTransp1 )
		{
			if( fCosTransp0 > fCosOpaque0 )
				alpha += a;
			else
				alpha *= a;
		}
		else
		{
			if( fCosTransp0 < fCosOpaque0 )
				alpha += a;
			else
				alpha *= a;
		}
	}
	color = AColor(1.f, 1.f, 1.f, alpha);

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

float plAngleAttenLayer::EvalMono(ShadeContext& sc)
{
	return Intens(EvalColor(sc));
}

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

ULONG plAngleAttenLayer::LocalRequirements(int subMtlNum)
{
	return MTLREQ_VIEW_DEP | MTLREQ_TRANSP;
}

void plAngleAttenLayer::ActivateTexDisplay(BOOL onoff)
{
}

BITMAPINFO *plAngleAttenLayer::GetVPDisplayDIB(TimeValue t, TexHandleMaker& thmaker, Interval &valid, BOOL mono, BOOL forceW, BOOL forceH)
{
	return nil;
						// FIXME
}

DWORD plAngleAttenLayer::GetActiveTexHandle(TimeValue t, TexHandleMaker& thmaker) 
{
	return 0;
}

const char *plAngleAttenLayer::GetTextureName( int which )
{
	return NULL;
}

int plAngleAttenLayer::GetLoClamp()
{
	return fParmsPB->GetInt(kLoClamp);
}

int plAngleAttenLayer::GetHiClamp()
{
	return fParmsPB->GetInt(kHiClamp);
}

Box3 plAngleAttenLayer::GetFade()
{
	Point3 pmin, pmax;

	pmin.x  = fParmsPB->GetFloat(kTranspAngle0);
	pmin.y  = fParmsPB->GetFloat(kOpaqueAngle0);
	if( pmin.x < pmin.y )
		pmin.z = -1.f;
	else if( pmin.x > pmin.y )
		pmin.z = 1.f;
	else
		pmin.z = 0;

	if( fParmsPB->GetInt(kDoubleFade) )
	{
		pmax.x  = fParmsPB->GetFloat(kTranspAngle1);
		pmax.y  = fParmsPB->GetFloat(kOpaqueAngle1);
		if( pmax.x < pmax.y )
			pmax.z = -1.f;
		else if( pmax.x > pmax.y )
			pmax.z = 1.f;
		else
			pmax.z = 0;
	}
	else
	{
		pmax.x = pmax.y = pmax.z = 0;
	}
	return Box3(pmin, pmax);

}

BOOL plAngleAttenLayer::Reflect()
{
	return fParmsPB->GetInt(kReflect);
}