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

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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 "HeadSpin.h"

#include "MaxMain/MaxAPI.h"

#include "../resource.h"

#include "plCompositeMtl.h"
#include "plPassMtl.h"
#include "plCompositeMtlDlg.h"

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

#include "plCompositeMtlPBDec.h"

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

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

    if (!loading) 
        Reset();

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

void plCompositeMtl::IGetClassName(MSTR& s) const
{
    s = GetString(IDS_COMP_MTL);
}

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

ParamDlg* plCompositeMtl::CreateParamDlg(HWND hwMtlEdit, IMtlParams *imp) 
{
    fMtlDlg = 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();
}

MSTR plCompositeMtl::ISubAnimName(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 nullptr;
}

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 nullptr;
}

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

    return nullptr;
}

RefResult plCompositeMtl::NotifyRefChanged(MAX_REF_INTERVAL changeInt, RefTargetHandle hTarget,
   PartID& partID, RefMessage message MAX_REF_PROPAGATE)
{
    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 nullptr;
}

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

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

MSTR 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 = 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 == nullptr || 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 = 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 == nullptr)
            *target = 1.0f;
        else
            *target = alpha->x;
    }
    else if (method == kCompBlendVertexIllumRed || method == kCompBlendInverseVtxIllumRed)
    {
        if (illum == nullptr)
            *target = 1.0f;
        else
            *target = illum->x;
    }
    else if (method == kCompBlendVertexIllumGreen || method == kCompBlendInverseVtxIllumGreen)
    {
        if (illum == nullptr)
            *target = 1.0f;
        else
            *target = illum->y;
    }
    else if (method == kCompBlendVertexIllumBlue || method == kCompBlendInverseVtxIllumBlue)
    {
        if (illum == nullptr)
            *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;
}
*/