#include "fmod_settings.h"

#ifdef FMOD_SUPPORT_SOFTWARE

#include "fmod_dsp_connectionpool.h"
#include "fmod_localcriticalsection.h"
#include "fmod_memory.h"
#include "fmod_systemi.h"

namespace FMOD
{



/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPConnectionPool::init(SystemI *system, int numconnections, int numoutputlevels, int numinputlevels)
{
    DSP_LEVEL_TYPE *leveldata;
    int count;
    unsigned int alignment = 0;

    if (numconnections < 0)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    for (int i=0; i < DSP_MAX_CONNECTION_BLOCKS; i++)
    {
        mConnection[i] = 0;
        mLevelData[i]  = 0;
    }

    numconnections += 128;
    numconnections /= 128;
    numconnections *= 128;

    mNumConnections = numconnections; 
    mConnectionMemory[0] = (DSPConnectionI *)FMOD_Memory_Calloc(mNumConnections * sizeof(DSPConnectionI) + 16);
    if (!mConnectionMemory[0])
    {
        return FMOD_ERR_MEMORY;
    }
    mConnection[0] = (DSPConnectionI *)FMOD_ALIGNPOINTER(mConnectionMemory[0], 16);

    mNodeMemory[0] = (LinkedListNode *)FMOD_Memory_Calloc(mNumConnections * sizeof(LinkedListNode));
    if (!mNodeMemory[0])
    {
        return FMOD_ERR_MEMORY;
    }  

    mNumOutputLevels = numoutputlevels;
    mNumInputLevels = numinputlevels;
    
    #ifdef PLATFORM_PS3
    alignment = 128;
    #endif
    mLevelDataMemory[0] = (DSP_LEVEL_TYPE *)FMOD_Memory_Calloc(mNumConnections                                          /* How many connections are we going to allow maximum. */
                                            * (mNumOutputLevels < 2 ? 2 : mNumOutputLevels)                             /* Number of rows.  This should be at least 2 for panning. */
                                            * (mNumInputLevels < mNumOutputLevels ? mNumOutputLevels : mNumInputLevels) /* Because pan matricies can be [outputlevels][outputlevels] we need to have at least this */
                                            * sizeof(DSP_LEVEL_TYPE)                                                    /* Float values of course.  Maybe lower? */
                                            * 3                                                                         /* *3 = mLevel, mLevelCurrent, mLevelDelta */
                                            + alignment                                                                 /* PS3 - 128 byte aligned for DMA */
                                            );
    if (!mLevelDataMemory[0])
    {
        return FMOD_ERR_MEMORY;
    }
    #ifdef PLATFORM_PS3
    mLevelData[0] = (DSP_LEVEL_TYPE *)FMOD_ALIGNPOINTER(mLevelDataMemory[0], 128);
    #else
    mLevelData[0] = mLevelDataMemory[0];
    #endif
    leveldata = mLevelData[0];

    mFreeListHead.initNode();
    for (count = 0; count < mNumConnections; count++)
    {
        DSPConnectionI *conn = &mConnection[0][count];

        new (conn) DSPConnectionI;

        conn->init(leveldata, numoutputlevels, numinputlevels);
        conn->mNode = &mNodeMemory[0][count];
        conn->mNode->setData(conn);
        conn->mNode->addAfter(&mFreeListHead);
    }

    mSystem = system;

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPConnectionPool::close()
{
    int i;

    for (i = 0; i < DSP_MAX_CONNECTION_BLOCKS; i++)
    {
        if (mConnectionMemory[i])
        {
            FMOD_Memory_Free(mConnectionMemory[i]);
            mConnectionMemory[i] = 0;
        }
        mConnection[i] = 0;

        if (mLevelDataMemory[i])
        {
            FMOD_Memory_Free(mLevelDataMemory[i]);
            mLevelDataMemory[i] = 0;
        }
        mLevelData[i] = 0;

        if (mNodeMemory[i])
        {
            FMOD_Memory_Free(mNodeMemory[i]);
            mNodeMemory[i] = 0;
        }
    }

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPConnectionPool::alloc(DSPConnectionI **connection, bool protect)
{
    LocalCriticalSection crit(mSystem->mDSPConnectionCrit);
    DSPConnectionI *newconnection;
    unsigned int alignment = 0;
    
    if (!mSystem)
    {
        return FMOD_ERR_UNINITIALIZED;
    }

    if (!connection)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    if (protect)
    {
        crit.enter();
    }

    if (mFreeListHead.isEmpty())
    {
        int             newblock, count;
        DSP_LEVEL_TYPE *leveldata = 0;

        for (newblock = 0; newblock < DSP_MAX_CONNECTION_BLOCKS; newblock++)
        {
            if (!mConnectionMemory[newblock])
            {
                break;
            }
        }
        if (newblock >= DSP_MAX_CONNECTION_BLOCKS)
        {
            FLOG((FMOD_DEBUG_LEVEL_LOG, __FILE__, __LINE__, "DSPConnectionPool::alloc", "DSP_MAX_CONNECTION_BLOCKS exceeded! Memory will leak!\n"));
            return FMOD_ERR_INTERNAL;
        }

        mConnectionMemory[newblock] = (DSPConnectionI *)FMOD_Memory_Calloc(mNumConnections * sizeof(DSPConnectionI) + 16);
        if (!mConnectionMemory[newblock])
        {
            return FMOD_ERR_MEMORY;
        }
        mConnection[newblock] = (DSPConnectionI *)FMOD_ALIGNPOINTER(mConnectionMemory[newblock], 16);

        #ifdef PLATFORM_PS3
        alignment = 128;
        #endif
        mNodeMemory[newblock] = (LinkedListNode *)FMOD_Memory_Calloc(mNumConnections * sizeof(LinkedListNode));
        if (!mNodeMemory[newblock])
        {
            return FMOD_ERR_MEMORY;
        }

        mLevelDataMemory[newblock] = (DSP_LEVEL_TYPE *)FMOD_Memory_Calloc(mNumConnections                                      /* How many connections are we going to allow maximum. */
                                                   * (mNumOutputLevels < 2 ? 2 : mNumOutputLevels)                             /* Number of rows.  This should be at least 2 for panning. */
                                                   * (mNumInputLevels < mNumOutputLevels ? mNumOutputLevels : mNumInputLevels) /* Because pan matricies can be [outputlevels][outputlevels] we need to have at least this */
                                                   * sizeof(DSP_LEVEL_TYPE)                                                    /* Float values of course.  Maybe lower? */
                                                   * 3                                                                         /* *3 = mLevel, mLevelCurrent, mLevelDelta */
                                                   + alignment                                                                 /* PS3 - 128 byte aligned for DMA */
                                                   );
        if (!mLevelDataMemory[newblock])
        {
            return FMOD_ERR_MEMORY;
        }
        #ifdef PLATFORM_PS3
        mLevelData[newblock] = (DSP_LEVEL_TYPE *)FMOD_ALIGNPOINTER(mLevelDataMemory[newblock], 128);
        #else
        mLevelData[newblock] = mLevelDataMemory[newblock];
        #endif
        
        leveldata = mLevelData[newblock];

        for (count = 0; count < mNumConnections; count++)
        {
            DSPConnectionI *conn = &mConnection[newblock][count];

            new (conn) DSPConnectionI;

            conn->init(leveldata, mNumOutputLevels, mNumInputLevels);
            conn->mNode = &mNodeMemory[newblock][count];
            conn->mNode->setData(conn);
            conn->mNode->addAfter(&mFreeListHead);
        }
    }

    newconnection = (DSPConnectionI *)mFreeListHead.getNext()->getData();

    newconnection->mInputNode.setData(newconnection);
    newconnection->mOutputNode.setData(newconnection);

    newconnection->mNode->removeNode();
    newconnection->mNode->addAfter(&mUsedListHead);

    if (protect)
    {
        crit.leave();
    }

    *connection = newconnection;

    return FMOD_OK;
}



/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/
FMOD_RESULT DSPConnectionPool::free(DSPConnectionI *connection, bool protect)
{
    LocalCriticalSection crit(mSystem->mDSPConnectionCrit);

    if (!connection)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    if (protect)
    {
        crit.enter();
    }

    connection->mInputNode.removeNode();
    connection->mOutputNode.removeNode();

    connection->mInputUnit = 0;
    connection->mOutputUnit = 0;

    connection->mNode->removeNode();
    connection->mNode->addAfter(&mFreeListHead);
    connection->mNode->setData(connection);

    if (protect)
    {
        crit.leave();
    }

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/

#ifdef FMOD_SUPPORT_MEMORYTRACKER

FMOD_RESULT DSPConnectionPool::getMemoryUsedImpl(MemoryTracker *tracker)
{
    for (int i=0; i < DSP_MAX_CONNECTION_BLOCKS; i++)
    {
        if (mConnectionMemory[i])
        {
            tracker->add(false, FMOD_MEMBITS_DSPCONNECTION, mNumConnections * sizeof(DSPConnectionI) + 16);
        }

        if (mNodeMemory[i])
        {
            tracker->add(false, FMOD_MEMBITS_DSPCONNECTION, mNumConnections * sizeof(LinkedListNode));
        }

        if (mLevelDataMemory[i])
        {
            unsigned int alignment = 0;

            #ifdef PLATFORM_PS3
            alignment = 128;
            #endif

            tracker->add(false, FMOD_MEMBITS_DSPCONNECTION, mNumConnections                                                     /* How many connections are we going to allow maximum. */
                                                    * (mNumOutputLevels < 2 ? 2 : mNumOutputLevels)                             /* Number of rows.  This should be at least 2 for panning. */
                                                    * (mNumInputLevels < mNumOutputLevels ? mNumOutputLevels : mNumInputLevels) /* Because pan matricies can be [outputlevels][outputlevels] we need to have at least this */
                                                    * sizeof(DSP_LEVEL_TYPE)                                                    /* Float values of course.  Maybe lower? */
                                                    * 3                                                                         /* *3 = mLevel, mLevelCurrent, mLevelDelta */
                                                    + alignment                                                                 /* PS3 - 128 byte aligned for DMA */
                                                   );
        }
    }

    return FMOD_OK;
}

#endif

}

#endif
