//----------------------------------------------------------------------
// Copyright (C) 2008 Electronic Arts    
//----------------------------------------------------------------------

#include "EABase/eabase.h"
#include "job_util/dma.h"
//#include "job_util/print.h"

#if defined(EA_PLATFORM_PS3_SPU)
    #include <spu_intrinsics.h>
#elif defined(EA_PLATFORM_PS3)
    #include <ppu_intrinsics.h>
#else
    #include "eathread/eathread.h"
#endif

#include <string.h> // for memmove

namespace EA { namespace Jobs { namespace JobUtilDetail {

#if defined EA_PLATFORM_PS3_SPU

// these numbers where arrived at via profiling, don't change them without
// re-profiling to make sure you've improved performance.
static uint32_t const gBlockSize = 0x800;
static size_t const gBufferCount = 2;

// we reserve 32 extra bytes because we can read up to 15 bytes before and 15
// bytes after the requested memory.  e.g.: reading 2 bytes at address 0x0f has
// to read from 0x00 to 0x20 because of 16-byte alignnent requirements.
char gDmaBuffer[gBufferCount][gBlockSize+32] __attribute__((aligned(128)));

#endif


void MemCpyJob(uintptr_t destEa, uintptr_t sourceEa, uintptr_t size, uintptr_t);

void MemCpyJob(uintptr_t destEa, uintptr_t sourceEa, uintptr_t size, uintptr_t)
{
#if defined EA_PLATFORM_PS3_SPU

    //Print("\n0x%08x 0x%08x %d\n", destEa, sourceEa, size);

    uintptr_t const sourceOffset = sourceEa & 15;
    uintptr_t const destOffset = destEa & 15;

    uintptr_t read[gBufferCount];
    uintptr_t totalRead = 0;
    uintptr_t totalWrite = 0;

    uintptr_t startEa;
    uintptr_t endEa;
    uintptr_t dmaSize;

    size_t bufferIndex;
    size_t blocks = (size / gBlockSize) + (size % gBlockSize ? 1 : 0); 

    for(size_t blockNumber = 0; blockNumber < (blocks + gBufferCount); blockNumber++)
    {
        if (blockNumber >= gBufferCount)
        {
            size_t putBlockNumber = blockNumber - gBufferCount;
            
            bufferIndex = putBlockNumber % gBufferCount;
            DmaWaitAll(1 << bufferIndex);

            size_t thisBlockSize = read[bufferIndex];
            uintptr_t thisDestEa = destEa + totalWrite;

            char * buffer = gDmaBuffer[bufferIndex];

            if (sourceOffset != destOffset) // shift data to match dest alignment.
                memmove(
                    buffer + destOffset,
                    buffer + sourceOffset,
                    thisBlockSize
                );
           
            //Print("W%d 0x%08x %d\n", putBlockNumber, thisDestEa, thisBlockSize);

            totalWrite += thisBlockSize;

            if (((thisBlockSize & 15) == 0) && (destOffset == 0))
            {
                EA_ASSERT((thisDestEa & 15) == 0);

                DmaPutAsync(
                    reinterpret_cast<void*>(thisDestEa),
                    reinterpret_cast<void*>(buffer),
                    thisBlockSize,
                    bufferIndex
                ); // fenced, a get on the same bufferIndex will queue
            }
            else
            {
                buffer += destOffset;

                if (destOffset) {
                    uintptr_t pre = (16 - destOffset);
                    if (pre > thisBlockSize)
                        pre = thisBlockSize;
                    
                    for(uintptr_t i=0; i<pre; ++i)
                        DmaPut<char>(buffer[i], reinterpret_cast<char*>(thisDestEa + i));
                    
                    buffer += pre;
                    thisDestEa += pre;
                    thisBlockSize -= pre;
                }

                if (thisBlockSize > 16)
                {
                    dmaSize = thisBlockSize & ~15;
                    EA_ASSERT((thisDestEa & 15) == 0); 
                    DmaPut(reinterpret_cast<void*>(thisDestEa), reinterpret_cast<void*>(buffer), dmaSize);
                  
                    buffer += dmaSize;
                    thisDestEa += dmaSize;
                    thisBlockSize -= dmaSize;
                }
              
                for(uintptr_t i=0;i<thisBlockSize;++i)
                    DmaPut<char>(buffer[i], reinterpret_cast<char*>(thisDestEa + i));
            }

            if (totalWrite == size)
                break;
        }

        if (blockNumber < blocks && totalRead < size)
        {
            bufferIndex = blockNumber % gBufferCount;

            size_t thisBlockSize = size - totalRead;
            if (thisBlockSize > gBlockSize) thisBlockSize = gBlockSize;

            read[bufferIndex] = thisBlockSize;

            uintptr_t thisSourceEa = sourceEa + totalRead;

            startEa = thisSourceEa & ~15;
            endEa = (thisSourceEa + thisBlockSize + 15) & ~15;

            dmaSize = endEa - startEa;

            totalRead += thisBlockSize;

            //Print("R%d 0x%08x %d\n", blockNumber, thisSourceEa, thisBlockSize);

            DmaGetAsync(gDmaBuffer[bufferIndex], reinterpret_cast<void*>(startEa), dmaSize, bufferIndex);
        }

    }

    DmaWaitAll(0xffffffff);

#else
    void * dst = reinterpret_cast<void*>(destEa);
    void * src = reinterpret_cast<void*>(sourceEa);
    memcpy(dst, src, size);
#endif

}

}}}

#if defined EA_PLATFORM_PS3_SPU

int main(uintptr_t p0, uintptr_t p1, uintptr_t p2, uintptr_t p3)
{
    EA::Jobs::JobUtilDetail::MemCpyJob(p0, p1, p2, p3);
}

#endif