#include <rwcore.h>
#include <rpworld.h>

#include "rpplugin.h"
#include "rpdbgerr.h"
#include "rpmatfx.h"
#include "skinxbox.h"
#include "skinxboxplain.h"
#include "../matfx/effectPipes.h"

RwInt32 _rpSkinXboxGetMaterialMatfxHash( RpMaterial *mat, RwUInt32 geoFlags )
{    
    RwInt32 effect = RpMatFXMaterialGetEffects(mat);
    
    if ((effect == rpMATFXEFFECTDUAL) && (geoFlags & rxGEOMETRY_TEXTURED))
    {
        /* vertex shaders don't need to do anything special here */
        effect = rpMATFXEFFECTNULL;
    }

    return effect;
}

#include "emboss.h"
#include "embossplusenv.h"

static DWORD embossPixelShader, embossPlusEnvPixelShader;

RwBool
_rpSkinXboxPipelinesCreateExtra( RwUInt32 pipes )
{
    RWFUNCTION(RWSTRING("_rpSkinXboxPipelinesCreateExtra"));

    /*
    * And create pixel shaders for skins
    */
    D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF* )dwEmbossPixelShader,
      &embossPixelShader );
                  
    D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF* )dwEmbossplusenvPixelShader,
      &embossPlusEnvPixelShader );

    RWRETURN(TRUE);
}

void
_rpSkinXboxPipelinesDestroyExtra( void )
{
    D3DDevice_DeletePixelShader( embossPlusEnvPixelShader );
    D3DDevice_DeletePixelShader( embossPixelShader );
}
            
            
static DWORD cacheSrc, cacheDest, cacheCmpFunc, oldAlphaTest;

typedef RwInt32 (*_rpMatfxSkinMaterialSetUpFn)( RpMaterial *material, RwInt32 pass );

typedef void (*_rpMatfxSkinMaterialTearDownFn)( RpMaterial *material );

static RwInt32
_rpMatfxSkinMaterialSetUpNull( RpMaterial *material, RwInt32 pass )
{
   RWFUNCTION(RWSTRING("_rpMatfxSkinMaterialSetUpNull"));
   RWASSERT(pass == 1);

   /* no fancy material, default set up will be fine */
   _rpSkinXboxPlainMaterialSetUp(material, pass);
   RWRETURN(0);
}

static void
_rpMatfxSkinMaterialTearDownNull( RpMaterial *material )
{
    /* no fancy material, default set up will be fine */
    _rpSkinXboxPlainMaterialTearDown(material);
}

static RwInt32
_rpMatfxSkinMaterialSetUpEnv( RpMaterial *material, RwInt32 pass )
{
    MatFXEnvMapData *envMapData;
    RwUInt32        shinny;
    
    RWFUNCTION(RWSTRING("_rpMatfxSkinMaterialSetUpEnv"));
    RWASSERT(pass == 1);

    envMapData = MATFXXBOXENVMAPGETDATA(material);
    
    /* (bung matrix into constant registers) */
    _rpSkinSetEnvMatrix(envMapData->frame);

    /* Set the shinnyness */
    shinny = (RwUInt32)(envMapData->coef * 255);
    shinny = (((shinny & 0xFF) << 24) |
            ((shinny & 0xFF) << 16) |
            ((shinny & 0xFF) << 8) |
             (shinny & 0xFF));

    D3DDevice_SetRenderState(D3DRS_TEXTUREFACTOR, shinny);
    
    if (material->texture)
    {
      _rwXbRenderStateTexture(material->texture);

      /* temp = diffuse vertex color * diffuse texture color */
      D3DDevice_SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);
      D3DDevice_SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
      D3DDevice_SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TEXTURE);

       D3DDevice_SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE);
       D3DDevice_SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
       D3DDevice_SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
    }
    else
    {
      /* temp =  diffuse vertex color */
      D3DDevice_SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1);
      D3DDevice_SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);

       D3DDevice_SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG1);
       D3DDevice_SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
    }

    D3DDevice_SetTextureStageState(0, D3DTSS_RESULTARG, D3DTA_TEMP );

    /* env map * shinny */
    D3DDevice_SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_MODULATE);
    D3DDevice_SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
    D3DDevice_SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_TFACTOR);

    D3DDevice_SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG1);
    D3DDevice_SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
    D3DDevice_SetTextureStageState(1, D3DTSS_ALPHAARG2, D3DTA_TFACTOR);

    /* result = diffuse + env map * shinny */
    D3DDevice_SetTextureStageState(2, D3DTSS_COLOROP,   D3DTOP_ADD);
    D3DDevice_SetTextureStageState(2, D3DTSS_COLORARG1, D3DTA_TEMP );
    D3DDevice_SetTextureStageState(2, D3DTSS_COLORARG2, D3DTA_CURRENT);

    D3DDevice_SetTextureStageState(2, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG1);
    D3DDevice_SetTextureStageState(2, D3DTSS_ALPHAARG1, D3DTA_TEMP);
    D3DDevice_SetTextureStageState(2, D3DTSS_ALPHAARG2, D3DTA_CURRENT);

    _rwXbMatFXRenderStateTexture(envMapData->texture, 1);

    RWRETURN(0);
}

static void
_rpMatfxSkinMaterialTearDownEnv( RpMaterial *material )
{
    D3DDevice_SetTextureStageState(0, D3DTSS_RESULTARG, D3DTA_CURRENT );
    D3DDevice_SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_DISABLE );
    D3DDevice_SetTextureStageState(2, D3DTSS_COLOROP,   D3DTOP_DISABLE );
    D3DDevice_SetTextureStageState(1, D3DTSS_ALPHAOP,   D3DTOP_DISABLE );
    D3DDevice_SetTextureStageState(2, D3DTSS_ALPHAOP,   D3DTOP_DISABLE );
}

static
RwInt32 _rpMatfxSkinMaterialSetUpDual( RpMaterial *material, RwInt32 pass )
{
    RWFUNCTION(RWSTRING("_rpMatfxSkinMaterialSetUpDual"));

    if (pass == 1)
    {
      /* no fancy material, default set up will be fine */
      _rpSkinXboxPlainMaterialSetUp(material, pass);

      /* one more pass to go */
      RWRETURN(1);
    }
    else
    {
        MatFXDualData *dualData;
        dualData = MATFXXBOXDUALPASSGETDATA(material);

        RWASSERT( pass == 2 );
          
        _rwXbRenderStateTexture(dualData->texture);

        D3DDevice_SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );

        /*
         * Cache the blending mode to avoid screwing the logo
         */
        D3DDevice_GetRenderState(D3DRS_SRCBLEND, &cacheSrc);
        D3DDevice_GetRenderState(D3DRS_DESTBLEND, &cacheDest);
        D3DDevice_GetRenderState(D3DRS_ALPHAFUNC, &cacheCmpFunc);

        D3DDevice_SetRenderState( D3DRS_SRCBLEND, _RwXbDualPassConvTable[dualData->srcBlendMode] );
        D3DDevice_SetRenderState( D3DRS_DESTBLEND, _RwXbDualPassConvTable[dualData->dstBlendMode] );
        D3DDevice_SetRenderState( D3DRS_ALPHAFUNC, D3DCMP_ALWAYS );

        D3DDevice_SetTextureStageState(0, D3DTSS_COLOROP,   D3DTOP_MODULATE);	
        D3DDevice_SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
        D3DDevice_SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);

        D3DDevice_SetTextureStageState(0, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG1);
        D3DDevice_SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
        D3DDevice_SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE);
          
        /* no more passes */
        RWRETURN(0);
    }
}

static void
_rpMatfxSkinMaterialTearDownDual( RpMaterial *material )
{
    D3DDevice_SetTextureStageState(1, D3DTSS_COLOROP,   D3DTOP_DISABLE );

    D3DDevice_SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

    D3DDevice_SetRenderState(D3DRS_SRCBLEND, cacheSrc);
    D3DDevice_SetRenderState(D3DRS_DESTBLEND, cacheDest);
    D3DDevice_SetRenderState( D3DRS_ALPHAFUNC, cacheCmpFunc );     
}

static
RwInt32 _rpMatfxSkinMaterialSetUpBump( RpMaterial *material, RwInt32 pass )
{
    MatFXBumpMapData *bumpMapData;
    RwFrame			*bumpFrame;
    RwV3d			*lightPos;
    RwV4d           bumpPosFudge, bumpShift;

    RWFUNCTION(RWSTRING("_rpMatfxSkinMaterialSetUpBump"));

    bumpMapData = MATFXXBOXBUMPMAPGETDATA(material);

    RWASSERT(NULL != bumpMapData->texture);

    _rwXbRenderStateTexture(bumpMapData->texture);
    _rwXbMatFXRenderStateTexture(bumpMapData->texture, 1);

    D3DDevice_GetRenderState(D3DRS_ALPHATESTENABLE, &oldAlphaTest);
    D3DDevice_SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);

    /* same code used by bump env, it will set it's own pixel shader... */
    if (RpMatFXMaterialGetEffects(material) == rpMATFXEFFECTBUMPMAP) 
    {
        RwXboxSetCurrentPixelShader( embossPixelShader );
    }
    
    D3DDevice_SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

    bumpFrame = bumpMapData->frame;
    if (bumpFrame == NULL)
    {
      bumpFrame = RwCameraGetFrame(RwCameraGetCurrentCamera());
      RWASSERT(bumpFrame);
    }

    lightPos = RwMatrixGetAt(RwFrameGetLTM(bumpFrame));

    bumpPosFudge.x = lightPos->x;
    bumpPosFudge.y = lightPos->y;
    bumpPosFudge.z = lightPos->z;
    bumpPosFudge.w = 5.0f;

    bumpShift.x = bumpShift.y = MAX_BUMP_TEXTURE_SHIFT * bumpMapData->coef;

    _rpSkinSetBumpConstants( &bumpPosFudge, &bumpShift );

    /* no more passes */
    RWRETURN(0);
}

static void
_rpMatfxSkinMaterialTearDownBump( RpMaterial *material )
{
    RwXboxSetCurrentPixelShader( 0 );
    D3DDevice_SetRenderState(D3DRS_ALPHATESTENABLE, oldAlphaTest);
}

static RwInt32
_rpMatfxSkinMaterialSetUpBumpEnv( RpMaterial *material, RwInt32 pass )
{
    MatFXEnvMapData *envMapData;

    RWFUNCTION(RWSTRING("_rpMatfxSkinMaterialSetUpBumpEnv"));

    RWASSERT(pass == 1);

    envMapData = MATFXXBOXBUMPENVMAPGETDATA(material);

    /* load env map first because _rpMatfxSkinMaterialSetUpBump will correct renderstates */
    _rwXbMatFXRenderStateTexture( envMapData->texture, 2 );

    _rpMatfxSkinMaterialSetUpBump( material, pass );
    
    /* (bung env map matrix into vertex shader constant registers) */
    _rpSkinSetEnvMatrix(envMapData->frame);
    
    RwXboxSetCurrentPixelShader( embossPlusEnvPixelShader );
    
    {
    RwReal ShinyFloats[4]={envMapData->coef, envMapData->coef, envMapData->coef, envMapData->coef};
    D3DDevice_SetPixelShaderConstant(0, ShinyFloats, 1);
    }
    
    /* no more passes */
    RWRETURN(0);    
}

static void
_rpMatfxSkinMaterialTearDownBumpEnv( RpMaterial *material )
{
    _rpMatfxSkinMaterialTearDownBump(material);
}

RwInt32 _rpSkinXboxMaterialSetUp( RpMaterial *material, RwInt32 pass )
{
    static _rpMatfxSkinMaterialSetUpFn fns[] = {
    _rpMatfxSkinMaterialSetUpNull,
    _rpMatfxSkinMaterialSetUpBump,
    _rpMatfxSkinMaterialSetUpEnv,
    _rpMatfxSkinMaterialSetUpBumpEnv,
    _rpMatfxSkinMaterialSetUpDual
    };
    
    RWFUNCTION(RWSTRING("_rpMatfxSkinMaterialSetUp"));
    
    /* some yahoo's added a new effect, vertex shaders need updating to support it */
    RWASSERT( (RpMatFXMaterialGetEffects(material) >= 0 ) &&
             (RpMatFXMaterialGetEffects(material) <= 4) );
             
    RWRETURN( fns[RpMatFXMaterialGetEffects(material)](material, pass) );
}

void
_rpSkinXboxMaterialTearDown( RpMaterial *material )
{
    static _rpMatfxSkinMaterialTearDownFn fns[] = {
    _rpMatfxSkinMaterialTearDownNull,
    _rpMatfxSkinMaterialTearDownBump,
    _rpMatfxSkinMaterialTearDownEnv,
    _rpMatfxSkinMaterialTearDownBumpEnv,
    _rpMatfxSkinMaterialTearDownDual
    };
    
    RWFUNCTION(RWSTRING("_rpSkinXboxMaterialTearDown"));
    
    /* some yahoo's added a new effect, vertex shaders need updating to support it */
    RWASSERT( (RpMatFXMaterialGetEffects(material) >= 0 ) && 
             (RpMatFXMaterialGetEffects(material) <= 4) );

    fns[RpMatFXMaterialGetEffects(material)](material);

    RWRETURNVOID();    
}
