#include "fmod_settings.h"

#ifdef FMOD_SUPPORT_FREEVERB

#include "fmod.h"
#include "fmod_dspi.h"
#include "fmod_dsp_reverb.h"
#include "fmod_systemi.h"

namespace FMOD
{

FMOD_DSP_DESCRIPTION_EX dspreverb;

#ifdef PLUGIN_EXPORTS

#ifdef __cplusplus
extern "C" {
#endif

    /*
        FMODGetDSPDescription is mandantory for every fmod plugin.  This is the symbol the registerplugin function searches for.
        Must be declared with F_API to make it export as stdcall.
    */
    F_DECLSPEC F_DLLEXPORT FMOD_DSP_DESCRIPTION_EX * F_API FMODGetDSPDescriptionEx()
    {
        return DSPReverb::getDescriptionEx();
    }

#ifdef __cplusplus
}
#endif

#endif  /* PLUGIN_EXPORTS */


FMOD_DSP_PARAMETERDESC dspreverb_param[6] =
{
    {    0.0f,    1.0f,    0.5f,  "Roomsize", "", "Roomsize.  0.0 to 1.0.  Default = 0.5" },
    {    0.0f,    1.0f,    0.5f,  "Damp",     "", "Damp.  0.0 to 1.0.  Default = 0.5" },
    {    0.0f,    1.0f,    0.33f, "Wet",      "", "Wet mix.  0.0 to 1.0.  Default = 0.33" },
    {    0.0f,    1.0f,    0.66f, "Dry",      "", "Dry mix.  0.0 to 1.0.  Default = 0.66" },
    {    0.0f,    1.0f,    1.0f,  "Width",    "", "Width.  0.0 to 1.0.  Default = 1.0" },
    {    0.0f,    1.0f,    0.0f,  "Mode",     "", "Mode.  0, 1.  Default = 0" }
};


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_DSP_DESCRIPTION_EX *DSPReverb::getDescriptionEx()
{
    FMOD_memset(&dspreverb, 0, sizeof(FMOD_DSP_DESCRIPTION_EX));

    FMOD_strcpy(dspreverb.name, "FMOD Reverb");
    dspreverb.version       = 0x00010100;
    dspreverb.create        = DSPReverb::createCallback;
    dspreverb.release       = DSPReverb::releaseCallback;
    dspreverb.reset         = DSPReverb::resetCallback;
    dspreverb.read          = DSPReverb::readCallback;

    dspreverb.numparameters = 6;
    dspreverb.paramdesc     = dspreverb_param;
    dspreverb.setparameter  = DSPReverb::setParameterCallback;
    dspreverb.getparameter  = DSPReverb::getParameterCallback;
#ifdef FMOD_SUPPORT_MEMORYTRACKER
    dspreverb.getmemoryused = &DSPReverb::getMemoryUsedCallback;
#endif

    dspreverb.mType         = FMOD_DSP_TYPE_REVERB;
    dspreverb.mCategory     = FMOD_DSP_CATEGORY_FILTER;
    dspreverb.mSize         = sizeof(DSPReverb);

    return &dspreverb;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
FMOD_RESULT DSPReverb::createInternal()
{
    int count;

    init();

    new (&mReverb) revmodel;

    for (count = 0; count < mDescription.numparameters; count++)
    {
        FMOD_RESULT result;
        
        result = setParameter(count, mDescription.paramdesc[count].defaultval);
        if (result != FMOD_OK)
        {
            return result;
        }
    }

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPReverb::releaseInternal()
{
    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPReverb::resetInternal()
{
    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPReverb::readInternal(float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int outchannels)
{   
    float *inleft = inbuffer;
    float *inright = inbuffer + 1;

    float *outleft = outbuffer;
    float *outright = outbuffer + 1;

    if (!inbuffer)
    {
        return FMOD_OK;
    }
    
    if ((speakermask & 0x3) == 0) /* Both speaker channels have been set to inactive */
    {
        FMOD_memcpy(outbuffer, inbuffer, inchannels*length*sizeof(float));
        return FMOD_OK;
    }

    if (inchannels > 2 || ((speakermask & 0x3) != 0x3))
    {
        // Prevent output buffer crosstalk by ensuring every sample is set
        FMOD_memcpy(outbuffer, inbuffer, inchannels*length*sizeof(float));
    }
    
    mReverb.processreplace(inleft, inright, outleft, outright, length, inchannels, speakermask);

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPReverb::setParameterInternal(int index, float value)
{
    mSystem->lockDSP();

    switch (index)
    {
        case FMOD_DSP_REVERB_ROOMSIZE:
        {
            if (value > 1.0f)
            {
                value = 1.0f;
            }
            else if (value < 0.0f)
            {
                value = 0.0f;
            }
            mReverb.setroomsize(value);
            break;
        }
        case FMOD_DSP_REVERB_DAMP:
        {
            if (value > 1.0f)
            {
                value = 1.0f;
            }
            else if (value < 0.0f)
            {
                value = 0.0f;
            }
            mReverb.setdamp(value);
            break;
        }
        case FMOD_DSP_REVERB_WETMIX:
        {
            if (value > 1.0f)
            {
                value = 1.0f;
            }
            else if (value < 0.0f)
            {
                value = 0.0f;
            }
            mReverb.setwet(value);
            break;
        }
        case FMOD_DSP_REVERB_DRYMIX:
        {
            if (value > 1.0f)
            {
                value = 1.0f;
            }
            else if (value < 0.0f)
            {
                value = 0.0f;
            }
            mReverb.setdry(value);
            break;
        }
        case FMOD_DSP_REVERB_WIDTH:
        {
            if (value > 1.0f)
            {
                value = 1.0f;
            }
            else if (value < 0.0f)
            {
                value = 0.0f;
            }
            mReverb.setwidth(value);
            break;
        }
        case FMOD_DSP_REVERB_MODE:
        {
            if (value >= freezemode)
            {
                value = 1.0f;
            }
            else
            {
                value = 0.0f;
            }
            mReverb.setmode(value);
            break;
        }
    }

    mSystem->unlockDSP();

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPReverb::getParameterInternal(int index, float *value, char *valuestr)
{
    switch (index)
    {
        case FMOD_DSP_REVERB_ROOMSIZE:
        {
            *value = mReverb.getroomsize();
            sprintf(valuestr, "%0.2f", *value);
            break;
        }
        case FMOD_DSP_REVERB_DAMP:
        {
            *value = mReverb.getdamp();
            sprintf(valuestr, "%0.2f", *value);
            break;
        }
        case FMOD_DSP_REVERB_WETMIX:
        {
            *value = mReverb.getwet();
            sprintf(valuestr, "%0.2f", *value);
            break;
        }
        case FMOD_DSP_REVERB_DRYMIX:
        {
            *value = mReverb.getdry();
            sprintf(valuestr, "%0.2f", *value);
            break;
        }
        case FMOD_DSP_REVERB_WIDTH:
        {
            *value = mReverb.getwidth();
            sprintf(valuestr, "%0.2f", *value);
            break;
        }
        case FMOD_DSP_REVERB_MODE:
        {
            *value = mReverb.getmode();
            if (*value >= freezemode)
            {
                *value = 1.0f;
                sprintf(valuestr, "FREEZE");
            }
            else
            {
                *value = 0.0f;
                sprintf(valuestr, "NORMAL");
            }
            break;
        }
    }

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
 
	[REMARKS]

    [PLATFORMS]
 
	[SEE_ALSO]
]
*/

#ifdef FMOD_SUPPORT_MEMORYTRACKER

FMOD_RESULT DSPReverb::getMemoryUsedImpl(MemoryTracker *tracker)
{
    // Size of this class is already accounted for (via description.mSize). Just add extra allocated memory here.

    return FMOD_OK;
}

#endif


/*
    ==============================================================================================================

    CALLBACK INTERFACE

    ==============================================================================================================
*/


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPReverb::createCallback(FMOD_DSP_STATE *dsp)
{
    DSPReverb *reverb = (DSPReverb *)dsp;

    return reverb->createInternal();
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPReverb::releaseCallback(FMOD_DSP_STATE *dsp)
{
    DSPReverb *reverb = (DSPReverb *)dsp;

    return reverb->releaseInternal();
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPReverb::resetCallback(FMOD_DSP_STATE *dsp)
{
    DSPReverb *reverb = (DSPReverb *)dsp;

    return reverb->resetInternal();
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPReverb::readCallback(FMOD_DSP_STATE *dsp, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int outchannels)
{
    DSPReverb *reverb = (DSPReverb *)dsp;

    return reverb->readInternal(inbuffer, outbuffer, length, inchannels, outchannels);
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPReverb::setParameterCallback(FMOD_DSP_STATE *dsp, int index, float value)
{
    DSPReverb *reverb = (DSPReverb *)dsp;

    return reverb->setParameterInternal(index, value);
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPReverb::getParameterCallback(FMOD_DSP_STATE *dsp, int index, float *value, char *valuestr)
{
    DSPReverb *reverb = (DSPReverb *)dsp;

    return reverb->getParameterInternal(index, value, valuestr);
}


#ifdef FMOD_SUPPORT_MEMORYTRACKER
/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]
    
	[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK DSPReverb::getMemoryUsedCallback(FMOD_DSP_STATE *dsp, MemoryTracker *tracker)
{
    DSPReverb *reverb = (DSPReverb *)dsp;    

    return reverb->DSPReverb::getMemoryUsed(tracker);
}
#endif

}

#endif
