/*==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 "plCompositeMtl.h"
#include "plPassMtl.h"
//#include "plCompositeMtlPB.h"
#include "plCompositeMtlDlg.h"

class plCompositeClassDesc : public ClassDesc2
{
public:
    int             IsPublic()      { return TRUE; }
    void*           Create(BOOL loading) { return TRACKED_NEW plCompositeMtl(loading); }
    const TCHAR*    ClassName()     { return GetString(IDS_COMP_MTL); }
    SClass_ID       SuperClassID()  { return MATERIAL_CLASS_ID; }
    Class_ID        ClassID()       { return COMP_MTL_CLASS_ID; }
    const TCHAR*    Category()      { return NULL; }
    const TCHAR*    InternalName()  { return _T("PlasmaComposite"); }
    HINSTANCE       HInstance()     { return hInstance; }
};
static plCompositeClassDesc plCompositeMtlDesc;
ClassDesc2* GetCompMtlDesc() { return &plCompositeMtlDesc; }

#include "plCompositeMtlPBDec.h"

const char *plCompositeMtl::BlendStrings[] = // Make sure these match up in order with the Blend enum (in the header)
{
    "Vertex Alpha",
    "Inverse Vtx Alpha",
    "Vertex Illum Red",
    "Inv. Vtx Illum Red",
    "Vertex Illum Green",
    "Inv. Vtx Illum Green",
    "Vertex Illum Blue",
    "Inv. Vtx Illum Blue"
};

plCompositeMtl::plCompositeMtl(BOOL loading) : fPassesPB(NULL)
{
    plCompositeMtlDesc.MakeAutoParamBlocks(this);

    if (!loading) 
        Reset();

    int i;
    for (i = 0; i < NSUBMTLS; i++)
    {
        plPassMtl *newMtl = TRACKED_NEW plPassMtl(false);
        fPassesPB->SetValue(kCompPasses, 0, newMtl, i);
        GetCOREInterface()->AssignNewName(fPassesPB->GetMtl(kCompPasses, 0, i));
    }
}

void plCompositeMtl::Reset() 
{
    fIValid.SetEmpty();
}

ParamDlg* plCompositeMtl::CreateParamDlg(HWND hwMtlEdit, IMtlParams *imp) 
{
    fMtlDlg = TRACKED_NEW plCompositeMtlDlg(hwMtlEdit, imp, this);

    return fMtlDlg; 
}

void plCompositeMtl::SetParamDlg(ParamDlg *dlg)
{
    fMtlDlg = (plCompositeMtlDlg*)dlg;
}

BOOL plCompositeMtl::SetDlgThing(ParamDlg* dlg)
{
    if (dlg == fMtlDlg)
    {
        fMtlDlg->SetThing(this);
        return TRUE;
    }

    return FALSE;
}

Interval plCompositeMtl::Validity(TimeValue t)
{
    Interval valid = FOREVER;       

/*  for (int i = 0; i < fSubTexmap.Count(); i++) 
    {
        if (fSubTexmap[i]) 
            valid &= fSubTexmap[i]->Validity(t);
    }
*/  
//  float u;
//  fPBlock->GetValue(pb_spin,t,u,valid);
    return valid;
}

// The index for a face on a composite material is really just a bitmask for all the submaterials. This function only
// computes it... The mesh creator will ask it to be created if it's needed.
int plCompositeMtl::ComputeMaterialIndex(float opac[][2], int vertCount)
{
    int index = 0;
    int i;//, j;
    int bitmask = 1;
    for (i = NumSubMtls() - 1, bitmask <<= i; i >= 0; i--, bitmask >>= 1)
    {
        index |= bitmask;
        /*
        if (i == 0)
            index |= bitmask; // it's not opaqued out, so include the base layer
        else if (fPassesPB->GetInt(kCompOn, 0, i - 1)) // is the checkbox ticked? (ie, are we using it?)
        {
            bool transparent = true;
            for (j = 0; j < vertCount; j++)
            {
                if (opac[j][i - 1] != 0.0)
                    transparent = false;
            }
            if (transparent)
                continue; // totally transparent for this face, skip it

            index |= bitmask; // include this material

            bool opaque = true;
            for (j = 0; j < vertCount; j++)
            {
                if (opac[j][i - 1] < 1.0)
                    opaque = false;
            }
            if (opaque && !((plPassMtlBase *)fPassesPB->GetMtl(kCompPasses, 0, i))->HasAlpha())
                break; // This material is totally opaque, no sense including anything underneath it
        }
        */
    }
    return index;
}

int plCompositeMtl::GetBlendStyle(int index)
{
    return fPassesPB->GetInt(kCompBlend, 0, index - 1);
}

// Determines whether all materials are only blending on one channel (and possibly its inverse) and therefore
// it's ok to overwrite the vertex alpha exported for the span using this material.
// Returns: -1 if we use multiple sources, otherwise the source we all agree on
int plCompositeMtl::CanWriteAlpha()
{
    int blend[3];
    blend[0] = ((((plPassMtlBase *)GetSubMtl(0))->GetOutputBlend() == plPassMtlBase::kBlendNone) ? -1 : kCompBlendVertexAlpha);
    blend[1] = (fPassesPB->GetInt(kCompOn, 0, 0) ? RemoveInverse(GetBlendStyle(1)) : -1);
    blend[2] = (fPassesPB->GetInt(kCompOn, 0, 1) ? RemoveInverse(GetBlendStyle(2)) : -1);

    int source = blend[0];
    int i;
    for (i = 1; i < 3; i++)
    {
        if (source < 0) 
        {
            source = blend[i];
            continue;
        }
        if (source >= 0 && blend[i] >= 0 && blend[i] != source) 
            return -1;
    }
    return source; 
}

/*===========================================================================*\
 |  Subanim & References support
\*===========================================================================*/

int plCompositeMtl::NumSubs()
{
    return NumSubMtls();
}

TSTR plCompositeMtl::SubAnimName(int i) 
{
    return GetSubMtlSlotName(i);
}

Animatable* plCompositeMtl::SubAnim(int i)
{
    return GetSubMtl(i);
}

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

RefTargetHandle plCompositeMtl::GetReference(int i)
{
    if (i == kRefPasses)
        return fPassesPB;

    return NULL;
}

void plCompositeMtl::SetReference(int i, RefTargetHandle rtarg)
{
    if (i == kRefPasses)
        fPassesPB = (IParamBlock2 *)rtarg;
}

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

IParamBlock2 *plCompositeMtl::GetParamBlock(int i)
{
    if (i == kRefPasses)
        return fPassesPB;

    return NULL;
}

IParamBlock2 *plCompositeMtl::GetParamBlockByID(BlockID id)
{
    if (fPassesPB->ID() == id)
        return fPassesPB;

    return NULL;
}

RefResult plCompositeMtl::NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, 
   PartID& partID, RefMessage message ) 
{
    switch (message)
    {
    case REFMSG_CHANGE:
        fIValid.SetEmpty();
        if (hTarget == fPassesPB)
        {
            ParamID changingParam = fPassesPB->LastNotifyParamID();
            fPassesPB->GetDesc()->InvalidateUI(changingParam);
        }
        break;
    }

    return REF_SUCCEED;
}

////////////////////////////////////////////////////////////////////////////////
// Subtexmap access

int plCompositeMtl::NumSubMtls()
{
//  return fPassesPB->GetInt(kMultCount);
    return NSUBMTLS;
}

Mtl *plCompositeMtl::GetSubMtl(int i)
{
    if (i < NumSubMtls())
        return fPassesPB->GetMtl(kCompPasses, 0, i);

    return NULL;
}

void plCompositeMtl::SetSubMtl(int i, Mtl *m)
{
    if (i < NumSubMtls())
        fPassesPB->SetValue(kCompPasses, 0, m, i);
}

TSTR plCompositeMtl::GetSubMtlSlotName(int i)
{
    TSTR str;
    str.printf("Pass %d", i+1);
    return str;
}

TSTR plCompositeMtl::GetSubMtlTVName(int i)
{
    return GetSubMtlSlotName(i);
}


/*===========================================================================*\
 |  Standard IO
\*===========================================================================*/

#define MTL_HDR_CHUNK 0x4000

IOResult plCompositeMtl::Save(ISave *isave)
{ 
    IOResult res;
    isave->BeginChunk(MTL_HDR_CHUNK);
    res = MtlBase::Save(isave);
    if (res!=IO_OK) return res;
    isave->EndChunk();

    return IO_OK;
}   

IOResult plCompositeMtl::Load(ILoad *iload)
{
    IOResult res;
    int id;
    while (IO_OK==(res=iload->OpenChunk()))
    {
        switch(id = iload->CurChunkID())
        {
            case MTL_HDR_CHUNK:
                res = MtlBase::Load(iload);
                break;
        }
        iload->CloseChunk();
        if (res!=IO_OK) 
            return res;
    }

    return IO_OK;
}


/*===========================================================================*\
 |  Updating and cloning
\*===========================================================================*/

RefTargetHandle plCompositeMtl::Clone(RemapDir &remap)
{
    plCompositeMtl *mnew = TRACKED_NEW plCompositeMtl(FALSE);
    *((MtlBase*)mnew) = *((MtlBase*)this); 
    mnew->ReplaceReference(kRefPasses, remap.CloneRef(fPassesPB));

    mnew->fIValid.SetEmpty();   
    BaseClone(this, mnew, remap);

    return (RefTargetHandle)mnew;
}

void plCompositeMtl::NotifyChanged() 
{
    NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
}

void plCompositeMtl::Update(TimeValue t, Interval& valid) 
{   
    if (!fIValid.InInterval(t))
    {
        fIValid.SetInfinite();
//      fPassesPB->GetValue(kMtlLayLayer1On, t, fMapOn[0], fIValid);

        for (int i = 0; i < NumSubMtls(); i++)
        {
            if (GetSubMtl(i))
                GetSubMtl(i)->Update(t, fIValid);
        }
    }

    valid &= fIValid;
}

/*===========================================================================*\
 |  Determine the characteristics of the material
\*===========================================================================*/
void plCompositeMtl::SetAmbient(Color c, TimeValue t) {}        
void plCompositeMtl::SetDiffuse(Color c, TimeValue t) {}        
void plCompositeMtl::SetSpecular(Color c, TimeValue t) {}
void plCompositeMtl::SetShininess(float v, TimeValue t) {}
                
Color plCompositeMtl::GetAmbient(int mtlNum, BOOL backFace) { return Color(0,0,0); }
Color plCompositeMtl::GetDiffuse(int mtlNum, BOOL backFace) { return Color(0,0,0); }
Color plCompositeMtl::GetSpecular(int mtlNum, BOOL backFace)    { return Color(0,0,0); }
float plCompositeMtl::GetXParency(int mtlNum, BOOL backFace)    { return 0.0f; }
float plCompositeMtl::GetShininess(int mtlNum, BOOL backFace)   { return 0.0f; }
float plCompositeMtl::GetShinStr(int mtlNum, BOOL backFace) { return 0.0f; }
float plCompositeMtl::WireSize(int mtlNum, BOOL backFace)       { return 0.0f; }

/*===========================================================================*\
 |  Actual shading takes place
\*===========================================================================*/

void plCompositeMtl::Shade(ShadeContext& sc) 
{
    // Get the background color
    Color backColor, backTrans;
    //sc.GetBGColor(backColor, backTrans);
    backColor.Black();
    backTrans.White();

    Point3 vtxIllum, vtxAlpha;
    plPassMtl::GetInterpVtxValue(MAP_ALPHA, sc, vtxAlpha);
    plPassMtl::GetInterpVtxValue(MAP_SHADING, sc, vtxIllum);
    int count = NumSubMtls();
    for (int i = 0; i < count; i++)
    {
        if (i > 0 && fPassesPB->GetInt(kCompOn, 0, i - 1) == 0) // Material is unchecked, skip it.
            continue;

        Mtl *mtl = GetSubMtl(i);
        if (mtl == NULL || mtl->ClassID() != PASS_MTL_CLASS_ID)
            continue;

        float opacity;
        if (i == 0)
        {
            opacity = 1.0f;
        }
        else
        {
            int blendMethod = fPassesPB->GetInt(kCompBlend, 0, i - 1);
            switch(blendMethod)
            {
            case kCompBlendVertexAlpha:
            case kCompBlendInverseVtxAlpha:
                opacity = vtxAlpha.x;
                break;
            case kCompBlendVertexIllumRed:
            case kCompBlendInverseVtxIllumRed:
                opacity = vtxIllum.x;
                break;
            case kCompBlendVertexIllumGreen:
            case kCompBlendInverseVtxIllumGreen:
                opacity = vtxIllum.y;
                break;
            case kCompBlendVertexIllumBlue:
            case kCompBlendInverseVtxIllumBlue:
                opacity = vtxIllum.z;
                break;
            default:
                opacity = 1.0f;
                break;
            }
            if (IsInverseBlend(blendMethod))
                opacity = 1 - opacity;
        }

        plPassMtl *passMtl = (plPassMtl*)mtl;
        passMtl->ShadeWithBackground(sc, backColor, false); // Don't include the vtx alpha, that's OUR job
        float currTrans = (1 - (1 - sc.out.t.r) * opacity);
        backTrans *= currTrans;
        backColor = backColor * currTrans + sc.out.c * opacity; 
    }

    sc.out.t = backTrans;
    sc.out.c = backColor;
}

float plCompositeMtl::EvalDisplacement(ShadeContext& sc)
{
    return 0.0f;
}

Interval plCompositeMtl::DisplacementValidity(TimeValue t)
{
    Interval iv;
    iv.SetInfinite();

    return iv;  
}

/*
void plCompositeMtl::SetNumSubMtls(int num)
{
    TimeValue t = GetCOREInterface()->GetTime();
    int curNum = fPassesPB->GetInt(kMultCount);

    fPassesPB->SetValue(kMultCount, 0, num);

    fPassesPB->SetCount(kMultPasses, num);
    fPassesPB->SetCount(kMultOn, num);

    for (int i = curNum; i < num; i++)
    {
        plPassMtl *newMtl = TRACKED_NEW plPassMtl(false);
        fPassesPB->SetValue(kMultPasses, t, newMtl, i);
        fPassesPB->SetValue(kMultOn, t, TRUE, i);
        GetCOREInterface()->AssignNewName(fPassesPB->GetMtl(kMultPasses, t, i));
    }
}
*/


void plCompositeMtl::SetOpacityVal(float *target, UVVert *alpha, UVVert *illum, int method)
{
    if (method == kCompBlendVertexAlpha || method == kCompBlendInverseVtxAlpha)
    {
        if (alpha == NULL)
            *target = 1.0f;
        else
            *target = alpha->x;
    }
    else if (method == kCompBlendVertexIllumRed || method == kCompBlendInverseVtxIllumRed)
    {
        if (illum == NULL)
            *target = 1.0f;
        else
            *target = illum->x;
    }
    else if (method == kCompBlendVertexIllumGreen || method == kCompBlendInverseVtxIllumGreen)
    {
        if (illum == NULL)
            *target = 1.0f;
        else
            *target = illum->y;
    }
    else if (method == kCompBlendVertexIllumBlue || method == kCompBlendInverseVtxIllumBlue)
    {
        if (illum == NULL)
            *target = 1.0f;
        else
            *target = illum->z;
    }
    else
    {
        *target = 1.0f;
    }

    if (method == kCompBlendInverseVtxAlpha ||
        method == kCompBlendInverseVtxIllumRed ||
        method == kCompBlendInverseVtxIllumGreen ||
        method == kCompBlendInverseVtxIllumBlue)
    {
        *target = 1.0f - *target;
    }
}
/*
int plCompositeMtl::UVChannelsNeeded(bool makeAlphaLayer)
{
    if (makeAlphaLayer)
        return 1;

    // Otherwise, we do have vertex alpha... can we get by without taking up a UV channel?
    int channels = 0;

    int i;
    for (i = 0; i < NumSubMtls() - 1; i++)
    {
        if (!fPassesPB->GetInt(kCompOn, 0, i))
            continue; // The material is unchecked, no need to see if it needs a channel

        int method = fPassesPB->GetInt(kCompBlend, 0, i);
        if (!(method == kCompBlendVertexAlpha || method1 == kCompBlendInverseVtxAlpha))
        {
            channels = 1;
            break;
        }
    }

    return channels;
}
*/