//----------------------------------------------------------------------
// Copyright (C) 2006-2007 Electronic Arts
//----------------------------------------------------------------------

#include "job_util/image_processor.h"
#include "job_manager/atomic_allocator.h"

#include "job_util/align.h"

#ifdef EA_PLATFORM_PS3_SPU
    #include <cell/dma.h>
    #include <spu_printf.h>
#else
    #include <string.h>
    #include <stdio.h>
#endif

namespace EA
{
    namespace Jobs
    {

using namespace EA::Jobs::UtilDetail;

//----------------------------------------------------------------------
void ImageProcessor::GetNextReadOffset()
{
    switch (mDesc.GetDivision())
    {
    case FOR_EACH_DIVISION_EVEN:
        mReadOffset += mDesc.JobCount();
        break;
    case FOR_EACH_DIVISION_DYNAMIC:
        mReadOffset = static_cast<int>(AtomicIncrement(mDynamicCounterEa)) - 1;
        break;
    default:
        break;
    }
}

//----------------------------------------------------------------------
ImageProcessor::ImageProcessor(ImageDesc * imageDesc, char * buffer, uint32_t bufferSize) :
    mDescEa(uintptr_t(imageDesc)),
    mDynamicCounterEa(0),
    mRefCounterEa(0),
    mBuffer(buffer),
    mBufferSize(static_cast<size_t>(bufferSize)),
    mDone(false),
    mJobIndex(0),
    mDesc(*imageDesc)
{
    Initialize();
}


//----------------------------------------------------------------------
ImageProcessor::ImageProcessor(ImageDesc * imageDesc, uint32_t jobIndex, char * buffer, uint32_t bufferSize) :
    mDescEa(uintptr_t(imageDesc)),
    mDynamicCounterEa(0),
    mRefCounterEa(0),
    mBuffer(buffer),
    mBufferSize(static_cast<size_t>(bufferSize)),
    mDone(false),
    mJobIndex(static_cast<int>(jobIndex)),
    mDesc(*imageDesc)
{
    Initialize();
}

//----------------------------------------------------------------------
ImageProcessor::ImageProcessor(uintptr_t descEa, char * buffer, uint32_t bufferSize) :
    mDescEa(descEa),
    mDynamicCounterEa(0),
    mBuffer(buffer),
    mBufferSize(static_cast<size_t>(bufferSize)),
    mDone(false),
    mJobIndex(0)
    #if ! defined EA_PLATFORM_PS3_SPU
        , mDesc(*reinterpret_cast<ImageDesc *>(descEa))
    #endif
{
    EA_ASSERT(descEa);

    #if defined EA_PLATFORM_PS3_SPU
        int const descDmaTag = 31;
        EA_ASSERT(descEa % 16 == 0);
        cellDmaGet((void*)&mDesc, (uint64_t)descEa, sizeof(ImageDesc), descDmaTag, 0, 0);
        cellDmaWaitTagStatusAll(1 << descDmaTag);
    #endif

    mDynamicCounterEa = mDesc.GetDynamicCounterEa(descEa);
    mRefCounterEa = mDesc.GetRefCounterEa(descEa);

    Initialize();
}

//----------------------------------------------------------------------
ImageProcessor::ImageProcessor(uintptr_t descEa, uint32_t jobIndex, char * buffer, uint32_t bufferSize) :
    mDescEa(descEa),
    mDynamicCounterEa(0),
    mBuffer(buffer),
    mBufferSize(static_cast<size_t>(bufferSize)),
    mDone(false),
    mJobIndex(static_cast<int>(jobIndex))
    #if ! defined EA_PLATFORM_PS3_SPU
        , mDesc(*reinterpret_cast<ImageDesc *>(descEa))
    #endif
{
    EA_ASSERT(descEa);

    #if defined EA_PLATFORM_PS3_SPU
        int const descDmaTag = 31;
        EA_ASSERT(descEa % 16 == 0);
        cellDmaGet((void*)&mDesc, (uint64_t)descEa, sizeof(ImageDesc), descDmaTag, 0, 0);
        cellDmaWaitTagStatusAll(1 << descDmaTag);
    #endif

    mDynamicCounterEa = mDesc.GetDynamicCounterEa(descEa);
    mRefCounterEa = mDesc.GetRefCounterEa(descEa);

    Initialize();
}

void ImageProcessor::Initialize()
{
#ifdef EA_ASSERT_ENABLED
    static uint32_t const sizeOfSpuLocalStore = 256*1024;
    (void)sizeOfSpuLocalStore;
#endif

    mBufferSize &= ~(15); // round down to a multiple of 16

    EA_ASSERT((mBufferSize == 0 && mBuffer == 0) || mBufferSize >= 16);
    EA_ASSERT(mBufferSize % 16 == 0);
    EA_ASSERT((uintptr_t)mBuffer % 16 == 0);

    #if defined EA_PLATFORM_PS3_SPU
        EA_ASSERT((uintptr_t)mBuffer < (sizeOfSpuLocalStore - mBufferSize));
    #else
        EA_ASSERT((mBuffer == 0) || (uintptr_t)mBuffer > sizeOfSpuLocalStore); // assume's VM won't map ppu allocations to < sizeOfSpuLocalStore
    #endif

    EA_ASSERT(mDesc.Verify());

    EA_ASSERT(mDesc.JobCount() > 0);
    EA_ASSERT(mJobIndex < static_cast<int>(mDesc.JobCount()));

    for (size_t i = 0; i < ImageDesc::sMaxImageRegionDescs; ++i)
    {
        ImageDesc::ImageRegionDesc const & imageRegionDesc = mDesc.GetImageRegionDesc(i);

        if ( imageRegionDesc.mInPtr  || imageRegionDesc.mOutPtr)
        {
            EA_ASSERT(imageRegionDesc.mBytesPerLine > 0);
            EA_ASSERT(imageRegionDesc.mBytesPerLine % 16 == 0);
        }
    }

    if (mBuffer)
    {
        char* b = mBuffer;
        for (size_t i = 0; i < ImageDesc::sMaxImageRegionDescs; ++i)
        {
            ImageDesc::ImageRegionDesc const & imageRegionDesc = mDesc.GetImageRegionDesc(i);
            if ( imageRegionDesc.mInPtr || imageRegionDesc.mOutPtr )
            {
                size_t regionLineSize = imageRegionDesc.mBytesPerPixel * mDesc.GetRegionWidth();
                EA_ASSERT(regionLineSize % 16 == 0);

                size_t bufferSize = regionLineSize * mDesc.GetRegionHeight();

                for (size_t j = 0; j < sBufferingCount; ++j)
                {
                    mImageRegionOffsetInBuffer[i*sBufferingCount + j] = Align(b);
                    b += bufferSize;
                    EA_ASSERT(b <= (mBuffer + mBufferSize));
                }
            }
            else
            {
                for (size_t j = 0; j < sBufferingCount; ++j)
                    mImageRegionOffsetInBuffer[i * sBufferingCount + j] = 0;
            }
        }
    }

    mLastOffset = mDesc.GetLastOffset();
}

ImageProcessor::~ImageProcessor()
{
    if (mDesc.mAtomicAllocator && mRefCounterEa && AtomicDecrement(mRefCounterEa)==0)
    {
        #ifdef EA_PLATFORM_PS3_SPU
            //asm("stop 0x3fff\n");
            AtomicAllocator aa;
            aa.Dma(mDesc.mAtomicAllocator);
            //spu_printf("SPU FEDF 0x%08x\n", (uint32_t)mDescEa);
            aa.Free((ForEachDesc*)mDescEa);
        #else
            //printf("PPU FEDF 0x%08x\n", (uint32_t)mDescEa);
            AtomicAllocator* aa = (AtomicAllocator*)mDesc.mAtomicAllocator;
            aa->Free<ForEachDesc>((ForEachDesc*)mDescEa);
        #endif
    }
}

//----------------------------------------------------------------------
void ImageProcessor::First()
{
    mReadOffset = 0;
    mCurrentOffset = 0;
    mWritesPending = 0;
    mReadBufferIndex = 0;
    mWriteBufferIndex = 0;
    mCurrentBufferIndex = 0;

    switch (mDesc.GetDivision())
    {
    case FOR_EACH_DIVISION_EVEN:
        if (mJobIndex > mLastOffset)
        {
            mDone = true;
            return;
        }
        mReadOffset = mJobIndex - mDesc.JobCount();
        break;
    case FOR_EACH_DIVISION_DYNAMIC:
        break;
    default:
        break;
    }

    for (size_t i = 0; i < sBufferingCount - 1; ++i)
        Read();

    mCurrentBufferIndex = 0;
    mCurrentOffset = mBufferOffset[mCurrentBufferIndex];

    if (mCurrentOffset > mLastOffset)
    {
        mDone = true;
        return;
    }

    Wait(1UL<<mCurrentBufferIndex);
}

//----------------------------------------------------------------------
bool ImageProcessor::Done() const
{
    return mDone;
}

//----------------------------------------------------------------------
void ImageProcessor::Next()
{
    Write();
    Read();
    NextBuffer(mCurrentBufferIndex);
    mCurrentOffset = mBufferOffset[mCurrentBufferIndex];

    if (mCurrentOffset > mLastOffset)
    {
        while (mWritesPending > 0)
            Write();

        Wait(sDmaWaitAllMask);
        mDone = true;
    }
    else
    {
        Wait(1UL<<mCurrentBufferIndex);
    }
}

//----------------------------------------------------------------------
void ImageProcessor::NextBuffer(size_t & buffer)
{
    ++buffer;
    if (buffer == sBufferingCount)
        buffer = 0;
}

//----------------------------------------------------------------------
void ImageProcessor::Read()
{
    GetNextReadOffset();

    mBufferOffset[mReadBufferIndex] = mReadOffset;

    if (mReadOffset > mLastOffset)
        return;

    for (size_t i = 0; i < ImageDesc::sMaxImageRegionDescs; ++i)
    {
        ImageDesc::ImageRegionDesc const & imageRegionDesc = mDesc.GetImageRegionDesc(i);

        if (imageRegionDesc.mInPtr)
        {
            size_t bufferIndex = i * sBufferingCount + mReadBufferIndex;
            size_t regionLineSize = imageRegionDesc.mBytesPerPixel * mDesc.GetRegionWidth();

            const char * ea = imageRegionDesc.mInPtr + mDesc.GetOffsetForRegion(i, mReadOffset);
            #if ! defined EA_PLATFORM_PS3_SPU
                if (!mBuffer)
                    mImageRegionOffsetInBuffer[bufferIndex] = const_cast<char *>(ea);
            #endif

            char * la = mImageRegionOffsetInBuffer[bufferIndex];

            for (size_t j = 0; j < mDesc.GetRegionHeight(); ++j)
            {
                #if defined EA_PLATFORM_PS3_SPU
                    cellDmaLargeGetf(mImageRegionOffsetInBuffer[bufferIndex] + (j * regionLineSize),
                            reinterpret_cast<uint64_t>(ea), regionLineSize, mReadBufferIndex, 0, 0);
                #else
                    if (mBuffer)
                        memcpy(la, ea, regionLineSize);
                #endif

                la += regionLineSize;
                ea += imageRegionDesc.mBytesPerLine;
            }
        }
        #if ! defined EA_PLATFORM_PS3_SPU
            if (imageRegionDesc.mOutPtr && !mBuffer)
            {
                size_t bufferIndex = i * sBufferingCount + mReadBufferIndex;

                const char * ea = imageRegionDesc.mOutPtr + mDesc.GetOffsetForRegion(i, mReadOffset);
                mImageRegionOffsetInBuffer[bufferIndex] = const_cast<char *>(ea);
            }
        #endif
    }

    NextBuffer(mReadBufferIndex);
    ++mWritesPending;
}

//----------------------------------------------------------------------
void ImageProcessor::Write()
{
    EA_ASSERT(mWritesPending);

    #if ! defined EA_PLATFORM_PS3_SPU
        if (mBuffer)
    #endif
    {
        for (size_t i = 0; i < ImageDesc::sMaxImageRegionDescs; ++i)
        {
            ImageDesc::ImageRegionDesc const & imageRegionDesc = mDesc.GetImageRegionDesc(i);

            if (imageRegionDesc.mOutPtr)
            {
                size_t bufferIndex = i * sBufferingCount + mWriteBufferIndex;
                int writeOffset = mBufferOffset[mWriteBufferIndex];

                size_t regionLineSize = imageRegionDesc.mBytesPerPixel * mDesc.GetRegionWidth();

                char * ea = imageRegionDesc.mOutPtr + mDesc.GetOffsetForRegion(i, writeOffset);
                char * la = mImageRegionOffsetInBuffer[bufferIndex];

                for (size_t j = 0; j < mDesc.GetRegionHeight(); ++j)
                {

                    #if defined EA_PLATFORM_PS3_SPU
                        cellDmaLargePutf(la, reinterpret_cast<uint64_t>(ea), regionLineSize, mWriteBufferIndex, 0, 0);
                    #else
                        memcpy(ea, la, regionLineSize);
                    #endif

                    la += regionLineSize;
                    ea += imageRegionDesc.mBytesPerLine;
                }
            }
        }
        NextBuffer(mWriteBufferIndex);
    }

    --mWritesPending;
}

//----------------------------------------------------------------------
void ImageProcessor::Wait(size_t dmaTagMask)
{
    #if defined EA_PLATFORM_PS3_SPU
        cellDmaWaitTagStatusAll(dmaTagMask);
    #else
        (void)dmaTagMask;
    #endif
}

//----------------------------------------------------------------------
size_t ImageProcessor::GetWidth() const
{
    return mDesc.GetWidth();
}

//----------------------------------------------------------------------
size_t ImageProcessor::GetHeight() const
{
    return mDesc.GetHeight();
}

//----------------------------------------------------------------------
size_t ImageProcessor::GetRegionWidth() const
{
    return mDesc.GetRegionWidth();
}

//----------------------------------------------------------------------
size_t ImageProcessor::GetRegionHeight() const
{
    return mDesc.GetRegionHeight();
}

//----------------------------------------------------------------------
size_t ImageProcessor::GetXOffset(size_t index) const
{
    EA_ASSERT(index < ImageDesc::sMaxImageRegionDescs);
    return mDesc.GetXOffsetForRegion(index, mCurrentOffset);
}

//----------------------------------------------------------------------
size_t ImageProcessor::GetYOffset(size_t index) const
{
    EA_ASSERT(index < ImageDesc::sMaxImageRegionDescs);
    return mDesc.GetYOffsetForRegion(index, mCurrentOffset);
}

//----------------------------------------------------------------------
size_t ImageProcessor::GetNextXOffset(size_t index) const
{
    EA_ASSERT(index < ImageDesc::sMaxImageRegionDescs);
    return mDesc.GetXOffsetForRegion(index, mReadOffset);
}

//----------------------------------------------------------------------
size_t ImageProcessor::GetNextYOffset(size_t index) const
{
    EA_ASSERT(index < ImageDesc::sMaxImageRegionDescs);
    return mDesc.GetYOffsetForRegion(index, mReadOffset);
}


}}
