/*==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==*/
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
//  plDXDeviceRefs.cpp - Functions for the various DX DeviceRef classes      //
//  Cyan, Inc.                                                               //
//                                                                           //
//// Version History //////////////////////////////////////////////////////////
//                                                                           //
//  4.25.2001 mcn - Created.                                                 //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////

#include "hsTypes.h"

#include <d3d9.h>
#include <ddraw.h>

#include "hsWinRef.h"

#include "plDXPipeline.h"
#include "plDXDeviceRef.h"
#include "plDXBufferRefs.h"
#include "plDXLightRef.h"
#include "plDXTextureRef.h"
#include "plDXRenderTargetRef.h"
#include "plGBufferGroup.h"
#include "plDrawable/plGeometrySpan.h"
#include "plDrawable/plDrawableSpans.h"
#include "plGLight/plLightInfo.h"
#include "plRenderTarget.h"
#include "plCubicRenderTarget.h"
#include "plDynamicEnvMap.h"

#include "plProfile.h"
#include "plStatusLog/plStatusLog.h"

plProfile_CreateMemCounter("Vertices", "Memory", MemVertex);
plProfile_CreateMemCounter("Indices", "Memory", MemIndex);
plProfile_CreateMemCounter("Textures", "Memory", MemTexture);

///////////////////////////////////////////////////////////////////////////////
//// Generic plDXDeviceRef Functions /////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

plDXDeviceRef::plDXDeviceRef()
{
    fNext = nil;
    fBack = nil;
}

plDXDeviceRef::~plDXDeviceRef()
{
    if( fNext != nil || fBack != nil )
        Unlink();
}

void    plDXDeviceRef::Unlink( void )
{
    hsAssert( fBack, "plDXDeviceRef not in list" );
    if( fNext )
        fNext->fBack = fBack;
    *fBack = fNext;

    fBack = nil;
    fNext = nil;
}

void    plDXDeviceRef::Link( plDXDeviceRef **back )
{
    hsAssert( fNext == nil && fBack == nil, "Trying to link a plDXDeviceRef that's already linked" );

    fNext = *back;
    if( *back )
        (*back)->fBack = &fNext;
    fBack = back;
    *back = this;
}


///////////////////////////////////////////////////////////////////////////////
//// plDXVertex/IndexBufferRef Funktions /////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//// Destructors //////////////////////////////////////////////////////////////

plDXVertexBufferRef::~plDXVertexBufferRef()
{
    Release();
}

plDXIndexBufferRef::~plDXIndexBufferRef()
{
    Release();
}

//// Releases /////////////////////////////////////////////////////////////////

void    plDXVertexBufferRef::Release( void )
{
    if( fD3DBuffer != nil )
    {
        ReleaseObject(fD3DBuffer);
        if (!Volatile())
        {
            plProfile_DelMem(MemVertex, fCount * fVertexSize);
            PROFILE_POOL_MEM(D3DPOOL_MANAGED, fCount * fVertexSize, false, "VtxBuff");
            plDXPipeline::FreeManagedVertex(fCount * fVertexSize);
        }
    }
    delete [] fData;
    fData = nil;

    SetDirty( true );
}

void    plDXIndexBufferRef::Release( void )
{
    if( fD3DBuffer != nil )
    {
        plProfile_DelMem(MemIndex, fCount * sizeof(UInt16));
        PROFILE_POOL_MEM(fPoolType, fCount * sizeof(UInt16), false, "IndexBuff");
        ReleaseObject( fD3DBuffer );
    }

    SetDirty( true );
}

///////////////////////////////////////////////////////////////////////////////
//// plDXTextureRef Funktions ////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//// Set //////////////////////////////////////////////////////////////////////

plDXTextureRef& plDXTextureRef::Set( D3DFORMAT ft, UInt32 ml, UInt32 mw, UInt32 mh, UInt32 np, 
                                                     UInt32 sz, UInt32 manSize, UInt32* lSz, void* pd, hsBool ed, hsBool renderTarget )
{
    if( fDataSize > 0 )
        plProfile_DelMem(MemTexture, fDataSize + sizeof(plDXTextureRef));

    if( ( fFormatType != ft || fMMLvs != ml || fMaxWidth != mw || fMaxHeight != mh ) && fD3DTexture != nil )
        ReleaseObject( fD3DTexture );
    if( !fD3DTexture )
        fUseTime = 0;

    fFormatType = ft;
    fMMLvs      = ml;
    fMaxWidth   = mw;
    fMaxHeight  = mh;
    fNumPix     = np;
    fDataSize   = manSize;
    if( fLevelSizes != nil )
        delete [] fLevelSizes;
    if( lSz )
        fLevelSizes = lSz;
    else
    {
        fLevelSizes = TRACKED_NEW UInt32[1];
        fLevelSizes[0] = sz;
    }
    fData       = pd;
    fFlags = ( ed ? kExternData : 0 ) | ( renderTarget ? kRenderTarget : 0 );

    plProfile_NewMem(MemTexture, fDataSize + sizeof(plDXTextureRef));

    return *this;
}

//// Constructor & Destructor /////////////////////////////////////////////////

plDXTextureRef::plDXTextureRef( D3DFORMAT ft, UInt32 ml, UInt32 mw, UInt32 mh, UInt32 np, 
                                                UInt32 sz, UInt32 manSize, UInt32* lSz, void* pd, hsBool ed, hsBool renderTarget )
{
    fLevelSizes = nil;
    fOwner = nil;
    fD3DTexture = nil;
    fDataSize = 0;
    fFlags = 0;
    fFormatType = D3DFMT_UNKNOWN;
    fMMLvs = 0;
    fMaxWidth = 0;
    fMaxHeight = 0;
    Set( ft, ml, mw, mh, np, sz, manSize, lSz, pd, ed, renderTarget );
}

plDXTextureRef::~plDXTextureRef() 
{
    Release();

    delete [] fLevelSizes; 
}

//// Release //////////////////////////////////////////////////////////////////

void    plDXTextureRef::Release( void )
{
    plProfile_DelMem(MemTexture, fDataSize + sizeof(plDXTextureRef));
    plProfile_Extern(ManagedMem);
    PROFILE_POOL_MEM(D3DPOOL_MANAGED, fDataSize, false, (fOwner ? fOwner->GetKey() ? fOwner->GetKey()->GetUoid().GetObjectName() : "(UnknownTexture)" : "(UnknownTexture)"));
    plDXPipeline::FreeManagedTexture(fDataSize);
    fDataSize = 0;

    ReleaseObject( fD3DTexture );
    SetDirty( true );
}


///////////////////////////////////////////////////////////////////////////////
//// plDXLightRef Funktions //////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//// UpdateD3DInfo ////////////////////////////////////////////////////////////

#define SET_D3DCOLORVALUE( v, color ) { v.r = color.r; v.g = color.g; v.b = color.b; v.a = color.a; }

void    plDXLightRef::UpdateD3DInfo( IDirect3DDevice9 *dev, plDXLightSettings *settings )
{
    plDirectionalLightInfo  *dirOwner;
    plOmniLightInfo         *omniOwner;
    plSpotLightInfo         *spotOwner;
    const float             maxRange = 32767.f;

    
    /// Properties that are set for all types
    fD3DDevice = dev;
    fParentSettings = settings;

    memset( &fD3DInfo, 0, sizeof( D3DLIGHT9 ) );
    SET_D3DCOLORVALUE( fD3DInfo.Diffuse, fOwner->GetDiffuse() );
    SET_D3DCOLORVALUE( fD3DInfo.Ambient, fOwner->GetAmbient() );
    SET_D3DCOLORVALUE( fD3DInfo.Specular, fOwner->GetSpecular() );

    if( ( omniOwner = plOmniLightInfo::ConvertNoRef( fOwner ) ) != nil )
    {
        fD3DInfo.Type = D3DLIGHT_POINT;

        hsPoint3 position = omniOwner->GetWorldPosition();
        fD3DInfo.Position.x = position.fX;
        fD3DInfo.Position.y = position.fY;
        fD3DInfo.Position.z = position.fZ;

        if( omniOwner->GetRadius() == 0 )
            fD3DInfo.Range = maxRange;
        else
            fD3DInfo.Range = omniOwner->GetRadius();
        fD3DInfo.Attenuation0 = omniOwner->GetConstantAttenuation();
        fD3DInfo.Attenuation1 = omniOwner->GetLinearAttenuation();
        fD3DInfo.Attenuation2 = omniOwner->GetQuadraticAttenuation();

        // If the light is a spot, but it has a projected texture, then 
        // the cone attenuation is handled by the texture. We're only using
        // the D3D light for distance attenuation and the N*L term. So
        // we can just leave the D3D light as the cheaper and more stable 
        // Omni light. This sort of obviates the change below. - mf
        if( !omniOwner->GetProjection()
            && (spotOwner = plSpotLightInfo::ConvertNoRef(fOwner)) )
        {
            fD3DInfo.Type = D3DLIGHT_SPOT;

            hsVector3 direction = spotOwner->GetWorldDirection();
            fD3DInfo.Direction.x = direction.fX;
            fD3DInfo.Direction.y = direction.fY;
            fD3DInfo.Direction.z = direction.fZ;

            fD3DInfo.Falloff = spotOwner->GetFalloff();
            fD3DInfo.Theta = spotOwner->GetSpotInner() * 2;
//          fD3DInfo.Phi = spotOwner->GetProjection() ? hsScalarPI : spotOwner->GetSpotOuter() * 2;
            // D3D doesn't seem to like a Phi of PI, even though that's supposed to be the
            // largest legal value. Symptom is an erratic, intermitant, unpredictable failure
            // of the light to light, with bizarreness like lighting one object but not the object
            // next to it, alternating which object it fails on each frame (or less often).
            // So, whatever. - mf
            fD3DInfo.Phi = spotOwner->GetSpotOuter() * 2; 
        }
    }
    else if( ( dirOwner = plDirectionalLightInfo::ConvertNoRef( fOwner ) ) != nil )
    {   
        fD3DInfo.Type = D3DLIGHT_DIRECTIONAL;

        hsVector3 direction = dirOwner->GetWorldDirection();
        fD3DInfo.Direction.x = direction.fX;
        fD3DInfo.Direction.y = direction.fY;
        fD3DInfo.Direction.z = direction.fZ;
    }
    else
    {
        hsAssert( false, "Unrecognized light type passed to plDXLightRef::UpdateD3DInfo()" );
        return;
    }

    fD3DDevice->SetLight( fD3DIndex, &fD3DInfo );
    fScale = 1.f;
}

//// Destructor ///////////////////////////////////////////////////////////////

plDXLightRef::~plDXLightRef()
{
    Release();
}

//// Release //////////////////////////////////////////////////////////////////

void    plDXLightRef::Release( void )
{
    // Ensure that this light is disabled
    if( fD3DDevice )
    {
        fD3DDevice->LightEnable( fD3DIndex, false );
        fD3DDevice = nil;
    }

    if( fParentSettings )
    {
        fParentSettings->fEnabledFlags.SetBit( fD3DIndex, false );
        fParentSettings->ReleaseD3DIndex( fD3DIndex );
        fParentSettings = nil;
    }
    fD3DIndex = 0;

    SetDirty( true );
}


///////////////////////////////////////////////////////////////////////////////
//// plDXRenderTargetRef Functions ///////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

//// Constructor //////////////////////////////////////////////////////////////

plDXRenderTargetRef::plDXRenderTargetRef( D3DFORMAT tp, UInt32 ml, plRenderTarget *owner, hsBool releaseDepthOnDelete )
                    : plDXTextureRef( tp, ml, owner->GetWidth(), owner->GetHeight(),
                                        owner->GetWidth() * owner->GetHeight(),
                                        owner->GetWidth() * owner->GetHeight() * ( owner->GetPixelSize() >> 3 ),
                                        0,
                                        nil, 
                                        nil, true, true )
{
    fD3DColorSurface = nil;
    fD3DDepthSurface = nil;
    fReleaseDepth = releaseDepthOnDelete;
    fOwner = owner;

    if( owner->GetFlags() & plRenderTarget::kIsTexture )
        fFlags |= kOffscreenRT;

    if( owner->GetFlags() & plRenderTarget::kIsProjected )
    {
        if( owner->GetFlags() & plRenderTarget::kIsOrtho )
            fFlags |= kOrthoProjection;
        else
            fFlags |= kPerspProjection;
    }
    
    if( plCubicRenderTarget::ConvertNoRef( owner ) != nil )
        fFlags |= kCubicMap;
}

//// Set //////////////////////////////////////////////////////////////////////

plDXRenderTargetRef& plDXRenderTargetRef::Set( D3DFORMAT tp, UInt32 ml, plRenderTarget *owner )
{
    fOwner = owner;

    plDXTextureRef::Set( tp, ml, owner->GetWidth(), owner->GetHeight(),
                                owner->GetWidth() * owner->GetHeight(),
                                owner->GetWidth() * owner->GetHeight() * ( owner->GetPixelSize() >> 3 ),
                                0,
                                nil, 
                                nil, true, true );

    if( owner->GetFlags() & plRenderTarget::kIsTexture )
        fFlags |= kOffscreenRT;

    if( owner->GetFlags() & plRenderTarget::kIsProjected )
    {
        if( owner->GetFlags() & plRenderTarget::kIsOrtho )
            fFlags |= kOrthoProjection;
        else
            fFlags |= kPerspProjection;
    }

    if( plCubicRenderTarget::ConvertNoRef( owner ) != nil )
        fFlags |= kCubicMap;

    return *this;
}

//// SetTexture ///////////////////////////////////////////////////////////////

void    plDXRenderTargetRef::SetTexture( IDirect3DSurface9 *surface, IDirect3DSurface9 *depth )
{
    fD3DColorSurface = surface;
    fD3DTexture = nil;
    fD3DDepthSurface = depth;
}

void    plDXRenderTargetRef::SetTexture( IDirect3DTexture9 *surface, IDirect3DSurface9 *depth )
{
    fD3DTexture = surface;
    fD3DColorSurface = nil;
    fD3DDepthSurface = depth;
}

void    plDXRenderTargetRef::SetTexture( IDirect3DCubeTexture9 *surface, IDirect3DSurface9 *depth )
{
    int                     i;
    IDirect3DSurface9       *surf;
    plDXRenderTargetRef *ref;
    plCubicRenderTarget     *cubic;
    D3DCUBEMAP_FACES        faces[ 6 ] = {  D3DCUBEMAP_FACE_NEGATIVE_X,     // Left
                                            D3DCUBEMAP_FACE_POSITIVE_X,     // Right
                                            D3DCUBEMAP_FACE_POSITIVE_Z,     // Front
                                            D3DCUBEMAP_FACE_NEGATIVE_Z,     // Back
                                            D3DCUBEMAP_FACE_POSITIVE_Y,     // Top
                                            D3DCUBEMAP_FACE_NEGATIVE_Y };   // Bottom


    fD3DTexture = surface;
    fD3DDepthSurface = depth;
    fD3DColorSurface = nil;

    /// Get the faces and assign to each of the child targets
    cubic = plCubicRenderTarget::ConvertNoRef( fOwner );
    for( i = 0; i < 6; i++ )
    {
        if( surface->GetCubeMapSurface( faces[ i ], 0, &surf ) != D3D_OK )
        {
            hsAssert( false, "Unable to get cube map surface" );
            continue;
        }
        ref = (plDXRenderTargetRef *)cubic->GetFace( i )->GetDeviceRef();

        ref->SetTexture( surf, depth );
    }
}

//// Destructor ///////////////////////////////////////////////////////////////

plDXRenderTargetRef::~plDXRenderTargetRef()
{
    Release();
}

//// Release //////////////////////////////////////////////////////////////////

void    plDXRenderTargetRef::Release( void )
{
    int                     i;
    plCubicRenderTarget     *cubic;
    plDXRenderTargetRef *ref;


    /// Get rid of the children's deviceRefs
    if( fFlags & kCubicMap )
    {
        cubic = plCubicRenderTarget::ConvertNoRef( fOwner );
        for( i = 0; i < 6; i++ )
        {
            ref = (plDXRenderTargetRef *)cubic->GetFace( i )->GetDeviceRef();
            ref->Release();
            ref->SetDirty( true );
        }
        // No need to call D3DSURF_MEMDEL on our fD3DTexture. It'll get
        // accounted for by our children's surfaces.
    }
    else
    {
        // We do internal accounting here. Actual release of fD3DTexture
        // happens in plDXTextureRef::Release()
        D3DSURF_MEMDEL((IDirect3DTexture9*)fD3DTexture);
    }

    D3DSURF_MEMDEL(fD3DColorSurface);
    ReleaseObject( fD3DColorSurface );
    if( fReleaseDepth )
    {
        // TODO:
        // We don't know who all is sharing this depth surface, so we can't
        // confidently say this memory is free now. We're reffing and releasing
        // it properly as far as DirectX is concerned, but our internal memory
        // counter is ignoring it.
        //D3DSURF_MEMDEL(fD3DDepthSurface);
        ReleaseObject( fD3DDepthSurface );
    }

    plDXTextureRef::Release();

    SetDirty( true );
}