#include "fmod_settings.h"

#ifdef FMOD_SUPPORT_EAX

#include "fmod_output_dsound.h"
#include "fmod_eax4.h"
#include "fmod_localcriticalsection.h"
#include "fmod_sample_dsound.h"
#include "fmod_systemi.h"


#include <stdio.h>

namespace FMOD
{

bool gFXslot2set = false;
bool gFXslot3set = false;


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

	[SEE_ALSO]
]
*/
bool OutputDSound::querySupportEAX4(FMOD_GUID guid, unsigned long ulProperty)
{
    HRESULT       hr;
    unsigned long ulSupport;
    GUID          g;

    FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::querySupportEAX4", "check properties %08X\n", ulProperty));

    FMOD_memcpy(&g, &guid, sizeof(GUID));

    hr = mBufferReverb->QuerySupport(g, ulProperty, &ulSupport);
	if ( FAILED(hr) ) 
    {
		return false;
    }

	if ( (ulSupport&(KSPROPERTY_SUPPORT_GET|KSPROPERTY_SUPPORT_SET)) == 
                    (KSPROPERTY_SUPPORT_GET|KSPROPERTY_SUPPORT_SET) )
	{
		FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::querySupportEAX4", "success.\n"));

		return true;
	}

    FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::querySupportEAX4", "failed.\n"));

	return false;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

	[SEE_ALSO]
]
*/
bool OutputDSound::initEAX4()
{
    FMOD_RESULT             result;
	ULONG					support = 0;
	HRESULT					hr = DS_OK;
    GUID                    guid;
    FMOD_CODEC_WAVEFORMAT   waveformat;

    FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::initEAX4", "Querying EAX4\n"));

    if (mBufferReverb)
	{
		mBufferReverb->Release();
		mBufferReverb = NULL;
	}

    /*
        Create EAX sample
    */

    FMOD_memset(&waveformat, 0, sizeof(FMOD_CODEC_WAVEFORMAT));
    waveformat.frequency = 44100;
    waveformat.lengthpcm = 1024;
    waveformat.channels  = 1;
    waveformat.format    = FMOD_SOUND_FORMAT_PCM16;

    result = createSample(FMOD_3D | FMOD_UNIQUE, &waveformat, (Sample **)&mSampleReverb);
    if (result != FMOD_OK)
    {
        FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::initEAX4", "could not create hardware sample.\n"));
        return false;
    }

    mSampleReverb->mSystem = mSystem;


    /*
	    OBTAIN EAX INTERFACE TO BUFFER 
	*/
    FMOD_memcpy(&guid, &FMOD_IID_IKsPropertySet, sizeof(GUID));

    if(mSampleReverb->mBuffer3D->QueryInterface(guid, (void **)&mBufferReverb) != DS_OK)
    {
        FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::initEAX4", "failed (1).\n"));

        mBufferReverb = NULL;
		return false;
	}

	/*
	    QUERY FOR EAX support
	*/
    support = querySupportEAX4(FMOD_EAXPROPERTYID_EAX40_Context, EAXCONTEXT_ALLPARAMETERS);
    if (support) support = querySupportEAX4(FMOD_EAXPROPERTYID_EAX40_FXSlot0, EAXFXSLOT_ALLPARAMETERS);  
    if (support) support = querySupportEAX4(FMOD_EAXPROPERTYID_EAX40_FXSlot1, EAXFXSLOT_ALLPARAMETERS);  
    if (support) support = querySupportEAX4(FMOD_EAXPROPERTYID_EAX40_FXSlot2, EAXFXSLOT_ALLPARAMETERS);  
	if (support) support = querySupportEAX4(FMOD_EAXPROPERTYID_EAX40_FXSlot3, EAXFXSLOT_ALLPARAMETERS);  
	if (support) support = querySupportEAX4(FMOD_EAXPROPERTYID_EAX40_Source, EAXSOURCE_ALLPARAMETERS);

    if (support) 
	{
		FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::initEAX4", "EAX 4.0 - success\n"));
	}
    else
    {
        FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::initEAX4", "failed (2).\n"));

        return false;
    }


    /*
        Turn down reverb to start with
    */
    {
        long room = EAXREVERB_MINROOM;

        FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::initEAX4", "setting reverb environment to OFF\n"));

        FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot0, sizeof(GUID));
        hr = mBufferReverb->Set(guid, EAXREVERB_ROOM, 0, 0, &room, sizeof(long));
        if (FAILED(hr))
        {
            FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::initEAX4", "failed (3).\n"));
            return false;
        }
    }
    
    FLOG((FMOD_DEBUG_LEVEL_ALL, __FILE__, __LINE__, "OutputDSound::initEAX4", "found!\n"));

	return true;
}



/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

	[SEE_ALSO]
]
*/
FMOD_RESULT OutputDSound::closeEAX4()
{
    GUID guid;
    long lock = EAXFXSLOT_UNLOCKED;

	if (mBufferReverb)
    {
        /*
            Unlock FX slots
        */
        FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot2, sizeof(GUID));
        mBufferReverb->Set(guid, EAXFXSLOT_LOCKED, 0, 0, &lock, sizeof(long));

        FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot3, sizeof(GUID));
        mBufferReverb->Set(guid, EAXFXSLOT_LOCKED, 0, 0, &lock, sizeof(long));

		mBufferReverb->Release();
        mBufferReverb = NULL;
    }

    if (mSampleReverb)
    {
        mSampleReverb->release();
		mSampleReverb = NULL;
    }

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

	[SEE_ALSO]
]
*/
FMOD_RESULT OutputDSound::setPropertiesEAX4(const FMOD_REVERB_PROPERTIES *prop)
{
    HRESULT hr;
    EAXREVERBPROPERTIES   eaxprop = {0};
    EAXFXSLOTPROPERTIES   fxslot;
    GUID    guid;

    if (!prop)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    switch(prop->Instance)
    {
        case 1:
        {
            FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot2, sizeof(GUID));

            if (!gFXslot2set)
            {
                FMOD_memcpy(&fxslot.guidLoadEffect, &FMOD_EAX_REVERB_EFFECT, sizeof(GUID));
                fxslot.lLock   = EAXFXSLOT_LOCKED;
                fxslot.lVolume = 0;
                fxslot.ulFlags = EAXFXSLOTFLAGS_ENVIRONMENT;
    
                hr = mBufferReverb->Set(guid, EAXFXSLOT_ALLPARAMETERS, NULL, 0, &fxslot, sizeof(EAXFXSLOTPROPERTIES));
                if (FAILED(hr))
                {
                    ULONG   ulReceived;
                    GUID    effect;

                    /*
                        Check if the slot contains a reverb. If so, we can still use that slot.
                    */
                    hr = mBufferReverb->Get(guid, EAXFXSLOT_LOADEFFECT, NULL, 0, &effect, sizeof(GUID), &ulReceived);
                    if (FAILED(hr))
                    {
                        return FMOD_ERR_REVERB_INSTANCE;
                    }
                    
                    if (memcmp(&effect, &fxslot.guidLoadEffect, sizeof(GUID)))
                    {
                        return FMOD_ERR_REVERB_INSTANCE;
                    }
                }

                gFXslot2set = true;
            }

            break;
        }
        case 2:
        {
            FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot3, sizeof(GUID));

            if (!gFXslot3set)
            {
                FMOD_memcpy(&fxslot.guidLoadEffect, &FMOD_EAX_REVERB_EFFECT, sizeof(GUID));
                fxslot.lLock   = EAXFXSLOT_LOCKED;
                fxslot.lVolume = 0;
                fxslot.ulFlags = EAXFXSLOTFLAGS_ENVIRONMENT;
    
                hr = mBufferReverb->Set(guid, EAXFXSLOT_ALLPARAMETERS, NULL, 0, &fxslot, sizeof(EAXFXSLOTPROPERTIES));
                if (FAILED(hr))
                {
                    ULONG   ulReceived;
                    GUID    effect;

                    /*
                        Check if the slot contains a reverb. If so, we can still use that slot.
                    */
                    hr = mBufferReverb->Get(guid, EAXFXSLOT_LOADEFFECT, NULL, 0, &effect, sizeof(GUID), &ulReceived);
                    if (FAILED(hr))
                    {
                        return FMOD_ERR_REVERB_INSTANCE;
                    }
                    
                    if (memcmp(&effect, &fxslot.guidLoadEffect, sizeof(GUID)))
                    {
                        return FMOD_ERR_REVERB_INSTANCE;
                    }
                }

                gFXslot3set = true;
            }

            break;
        }
        default:
            FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot0, sizeof(GUID));
            break;
    }

    eaxprop.ulEnvironment            = prop->Environment < 0 ? 0 : prop->Environment;
    eaxprop.flEnvironmentSize        = prop->EnvSize;           
    eaxprop.flEnvironmentDiffusion   = prop->EnvDiffusion;      
    eaxprop.lRoom                    = prop->Room;              
    eaxprop.lRoomHF                  = prop->RoomHF;            
    eaxprop.lRoomLF                  = prop->RoomLF;            
    eaxprop.flDecayTime              = prop->DecayTime;         
    eaxprop.flDecayHFRatio           = prop->DecayHFRatio;      
    eaxprop.flDecayLFRatio           = prop->DecayLFRatio;      
    eaxprop.lReflections             = prop->Reflections;       
    eaxprop.flReflectionsDelay       = prop->ReflectionsDelay;  
    eaxprop.vReflectionsPan.x        = prop->ReflectionsPan[0]; 
    eaxprop.vReflectionsPan.y        = prop->ReflectionsPan[1]; 
    eaxprop.vReflectionsPan.z        = prop->ReflectionsPan[2]; 
    eaxprop.lReverb                  = prop->Reverb;            
    eaxprop.flReverbDelay            = prop->ReverbDelay;       
    eaxprop.vReverbPan.x             = prop->ReverbPan[0];
    eaxprop.vReverbPan.y             = prop->ReverbPan[1];
    eaxprop.vReverbPan.z             = prop->ReverbPan[2];
    eaxprop.flEchoTime               = prop->EchoTime;          
    eaxprop.flEchoDepth              = prop->EchoDepth;         
    eaxprop.flModulationTime         = prop->ModulationTime;    
    eaxprop.flModulationDepth        = prop->ModulationDepth;   
    eaxprop.flAirAbsorptionHF        = prop->AirAbsorptionHF;   
    eaxprop.flHFReference            = prop->HFReference;       
    eaxprop.flLFReference            = prop->LFReference;       
    eaxprop.flRoomRolloffFactor      = prop->RoomRolloffFactor; 
    eaxprop.ulFlags                  = prop->Flags & 0xFF; 
    
    hr = mBufferReverb->Set(guid, EAXREVERB_ALLPARAMETERS, NULL, 0, &eaxprop, sizeof(EAXREVERBPROPERTIES));
    if (FAILED(hr))
    {
        return FMOD_ERR_INVALID_PARAM;
    }

	return FMOD_OK;
}



/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

	[SEE_ALSO]
]
*/
FMOD_RESULT	OutputDSound::getPropertiesEAX4(FMOD_REVERB_PROPERTIES *prop)
{
    HRESULT hr;
    EAXREVERBPROPERTIES   eaxprop;
	ULONG   ulReceived;
    GUID    guid;

    if (!prop)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    switch(prop->Instance)
    {
        case 1:
            FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot2, sizeof(GUID));
            break;
        case 2:
            FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot3, sizeof(GUID));
            break;
        default:
            FMOD_memcpy(&guid, &FMOD_EAXPROPERTYID_EAX40_FXSlot0, sizeof(GUID));
            break;
    }

	hr = mBufferReverb->Get(guid, EAXREVERB_ALLPARAMETERS, NULL, 0, &eaxprop, sizeof(EAXREVERBPROPERTIES), &ulReceived);
    if (FAILED(hr))
    {
     	return FMOD_ERR_UNSUPPORTED;
    }

    prop->Environment       = eaxprop.ulEnvironment            ;
    prop->EnvSize           = eaxprop.flEnvironmentSize        ;
    prop->EnvDiffusion      = eaxprop.flEnvironmentDiffusion   ;
    prop->Room              = eaxprop.lRoom                    ;
    prop->RoomHF            = eaxprop.lRoomHF                  ;
    prop->RoomLF            = eaxprop.lRoomLF                  ;
    prop->DecayTime         = eaxprop.flDecayTime              ;
    prop->DecayHFRatio      = eaxprop.flDecayHFRatio           ;
    prop->DecayLFRatio      = eaxprop.flDecayLFRatio           ;
    prop->Reflections       = eaxprop.lReflections             ;
    prop->ReflectionsDelay  = eaxprop.flReflectionsDelay       ;
    prop->ReflectionsPan[0] = eaxprop.vReflectionsPan.x        ;
    prop->ReflectionsPan[1] = eaxprop.vReflectionsPan.y        ;
    prop->ReflectionsPan[2] = eaxprop.vReflectionsPan.z        ;
    prop->Reverb            = eaxprop.lReverb                  ;
    prop->ReverbDelay       = eaxprop.flReverbDelay            ;
    prop->ReverbPan[0]      = eaxprop.vReverbPan.x             ;
    prop->ReverbPan[1]      = eaxprop.vReverbPan.y             ;
    prop->ReverbPan[2]      = eaxprop.vReverbPan.z             ;
    prop->EchoTime          = eaxprop.flEchoTime               ;
    prop->EchoDepth         = eaxprop.flEchoDepth              ;
    prop->ModulationTime    = eaxprop.flModulationTime         ;
    prop->ModulationDepth   = eaxprop.flModulationDepth        ;
    prop->AirAbsorptionHF   = eaxprop.flAirAbsorptionHF        ;
    prop->HFReference       = eaxprop.flHFReference            ;
    prop->LFReference       = eaxprop.flLFReference            ;
    prop->RoomRolloffFactor = eaxprop.flRoomRolloffFactor      ;
    prop->Flags             = eaxprop.ulFlags                  ;

	return FMOD_OK;
}

}

#endif  /* FMOD_SUPPORT_EAX */

