/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "SurfaceStream.h"

#include "gfxPoint.h"
#include "SharedSurface.h"
#include "SharedSurfaceGL.h"
#include "SurfaceFactory.h"
#include "GeckoProfiler.h"

namespace mozilla {
namespace gfx {

SurfaceStreamType
SurfaceStream::ChooseGLStreamType(SurfaceStream::OMTC omtc,
                                  bool preserveBuffer)
{
    if (omtc == SurfaceStream::OffMainThread) {
        if (preserveBuffer)
            return SurfaceStreamType::TripleBuffer_Copy;
        else
            return SurfaceStreamType::TripleBuffer;
    } else {
        if (preserveBuffer)
            return SurfaceStreamType::SingleBuffer;
        else
            return SurfaceStreamType::TripleBuffer;
    }
}

SurfaceStream*
SurfaceStream::CreateForType(SurfaceStreamType type, mozilla::gl::GLContext* glContext, SurfaceStream* prevStream)
{
    SurfaceStream* result = nullptr;

    switch (type) {
        case SurfaceStreamType::SingleBuffer:
            result = new SurfaceStream_SingleBuffer(prevStream);
            break;
        case SurfaceStreamType::TripleBuffer_Copy:
            result = new SurfaceStream_TripleBuffer_Copy(prevStream);
            break;
        case SurfaceStreamType::TripleBuffer:
            result = new SurfaceStream_TripleBuffer(prevStream);
            break;
        default:
            MOZ_CRASH("Invalid Type.");
    }

    result->mGLContext = glContext;
    return result;
}

bool
SurfaceStream_TripleBuffer::CopySurfaceToProducer(SharedSurface* src, SurfaceFactory* factory)
{
    if (!mProducer) {
        New(factory, src->Size(), mProducer);
        if (!mProducer) {
            return false;
        }
    }

    MOZ_ASSERT(src->Size() == mProducer->Size(), "Size mismatch");

    SharedSurface::Copy(src, mProducer, factory);
    return true;
}

void
SurfaceStream::New(SurfaceFactory* factory, const gfx::IntSize& size,
                   SharedSurface*& surf)
{
    MOZ_ASSERT(!surf);
    surf = factory->NewSharedSurface(size);

    if (surf)
        mSurfaces.insert(surf);
}

void
SurfaceStream::Recycle(SurfaceFactory* factory, SharedSurface*& surf)
{
    if (surf) {
        mSurfaces.erase(surf);
        factory->Recycle(surf);
    }
    MOZ_ASSERT(!surf);
}

void
SurfaceStream::Delete(SharedSurface*& surf)
{
    if (surf) {
        mSurfaces.erase(surf);
        delete surf;
        surf = nullptr;
    }
    MOZ_ASSERT(!surf);
}

SharedSurface*
SurfaceStream::Surrender(SharedSurface*& surf)
{
    SharedSurface* ret = surf;

    if (surf) {
        mSurfaces.erase(surf);
        surf = nullptr;
    }
    MOZ_ASSERT(!surf);

    return ret;
}

SharedSurface*
SurfaceStream::Absorb(SharedSurface*& surf)
{
    SharedSurface* ret = surf;

    if (surf) {
        mSurfaces.insert(surf);
        surf = nullptr;
    }
    MOZ_ASSERT(!surf);

    return ret;
}

void
SurfaceStream::Scrap(SharedSurface*& scrap)
{
    if (scrap) {
        mScraps.push(scrap);
        scrap = nullptr;
    }
    MOZ_ASSERT(!scrap);
}

void
SurfaceStream::RecycleScraps(SurfaceFactory* factory)
{
    while (!mScraps.empty()) {
        SharedSurface* cur = mScraps.top();
        mScraps.pop();

        Recycle(factory, cur);
    }
}



SurfaceStream::~SurfaceStream()
{
    Delete(mProducer);

    while (!mScraps.empty()) {
        SharedSurface* cur = mScraps.top();
        mScraps.pop();

        Delete(cur);
    }

    MOZ_ASSERT(mSurfaces.empty());
}

SharedSurface*
SurfaceStream::SwapConsumer()
{
    MOZ_ASSERT(mIsAlive);

    SharedSurface* ret = SwapConsumer_NoWait();
    if (!ret)
        return nullptr;

    if (!ret->WaitSync()) {
        return nullptr;
    }

    return ret;
}

SharedSurface*
SurfaceStream::Resize(SurfaceFactory* factory, const gfx::IntSize& size)
{
    MonitorAutoLock lock(mMonitor);

    if (mProducer) {
        Scrap(mProducer);
    }

    New(factory, size, mProducer);
    return mProducer;
}

SurfaceStream_SingleBuffer::SurfaceStream_SingleBuffer(SurfaceStream* prevStream)
    : SurfaceStream(SurfaceStreamType::SingleBuffer, prevStream)
    , mConsumer(nullptr)
{
    if (!prevStream)
        return;

    SharedSurface* prevProducer = nullptr;
    SharedSurface* prevConsumer = nullptr;
    prevStream->SurrenderSurfaces(prevProducer, prevConsumer);

    if (prevConsumer == prevProducer)
        prevConsumer = nullptr;

    mProducer = Absorb(prevProducer);
    mConsumer = Absorb(prevConsumer);
}

SurfaceStream_SingleBuffer::~SurfaceStream_SingleBuffer()
{
    Delete(mConsumer);
}

void
SurfaceStream_SingleBuffer::SurrenderSurfaces(SharedSurface*& producer,
                                              SharedSurface*& consumer)
{
    mIsAlive = false;

    producer = Surrender(mProducer);
    consumer = Surrender(mConsumer);

    if (!consumer)
        consumer = producer;
}

SharedSurface*
SurfaceStream_SingleBuffer::SwapProducer(SurfaceFactory* factory,
                                         const gfx::IntSize& size)
{
    MonitorAutoLock lock(mMonitor);
    if (mConsumer) {
        Recycle(factory, mConsumer);
    }

    if (mProducer) {
        // Fence now, before we start (maybe) juggling Prod around.
        mProducer->Fence();

        // Size mismatch means we need to squirrel the current Prod
        // into Cons, and leave Prod empty, so it gets a new surface below.
        bool needsNewBuffer = mProducer->Size() != size;

        // Even if we're the right size, if the type has changed, and we don't
        // need to preserve, we should switch out for (presumedly) better perf.
        if (mProducer->Type() != factory->Type() &&
            !factory->Caps().preserve)
        {
            needsNewBuffer = true;
        }

        if (needsNewBuffer) {
            Move(mProducer, mConsumer);
        }
    }

    // The old Prod (if there every was one) was invalid,
    // so we need a new one.
    if (!mProducer) {
        New(factory, size, mProducer);
    }

    return mProducer;
}

SharedSurface*
SurfaceStream_SingleBuffer::SwapConsumer_NoWait()
{
    MonitorAutoLock lock(mMonitor);

    // Use Cons, if present.
    // Otherwise, just use Prod directly.
    SharedSurface* toConsume = mConsumer;
    if (!toConsume)
        toConsume = mProducer;

    return toConsume;
}



SurfaceStream_TripleBuffer_Copy::SurfaceStream_TripleBuffer_Copy(SurfaceStream* prevStream)
    : SurfaceStream(SurfaceStreamType::TripleBuffer_Copy, prevStream)
    , mStaging(nullptr)
    , mConsumer(nullptr)
{
    if (!prevStream)
        return;

    SharedSurface* prevProducer = nullptr;
    SharedSurface* prevConsumer = nullptr;
    prevStream->SurrenderSurfaces(prevProducer, prevConsumer);

    if (prevConsumer == prevProducer)
      prevConsumer = nullptr;

    mProducer = Absorb(prevProducer);
    mConsumer = Absorb(prevConsumer);
}

SurfaceStream_TripleBuffer_Copy::~SurfaceStream_TripleBuffer_Copy()
{
    Delete(mStaging);
    Delete(mConsumer);
}

void
SurfaceStream_TripleBuffer_Copy::SurrenderSurfaces(SharedSurface*& producer,
                                                   SharedSurface*& consumer)
{
    mIsAlive = false;

    producer = Surrender(mProducer);
    consumer = Surrender(mConsumer);

    if (!consumer)
        consumer = Surrender(mStaging);
}

SharedSurface*
SurfaceStream_TripleBuffer_Copy::SwapProducer(SurfaceFactory* factory,
                                              const gfx::IntSize& size)
{
    MonitorAutoLock lock(mMonitor);

    RecycleScraps(factory);
    if (mProducer) {
        if (mStaging) {
            // We'll re-use this for a new mProducer later on if
            // the size remains the same
            Recycle(factory, mStaging);
        }

        Move(mProducer, mStaging);
        mStaging->Fence();

        New(factory, size, mProducer);
        
        if (mProducer && mStaging->Size() == mProducer->Size())
            SharedSurface::Copy(mStaging, mProducer, factory);
    } else {
        New(factory, size, mProducer);
    }

    return mProducer;
}


SharedSurface*
SurfaceStream_TripleBuffer_Copy::SwapConsumer_NoWait()
{
    MonitorAutoLock lock(mMonitor);

    if (mStaging) {
        Scrap(mConsumer);
        Move(mStaging, mConsumer);
    }

    return mConsumer;
}

void SurfaceStream_TripleBuffer::Init(SurfaceStream* prevStream)
{
    if (!prevStream)
        return;

    SharedSurface* prevProducer = nullptr;
    SharedSurface* prevConsumer = nullptr;
    prevStream->SurrenderSurfaces(prevProducer, prevConsumer);

    if (prevConsumer == prevProducer)
        prevConsumer = nullptr;

    mProducer = Absorb(prevProducer);
    mConsumer = Absorb(prevConsumer);
}


SurfaceStream_TripleBuffer::SurfaceStream_TripleBuffer(SurfaceStreamType type, SurfaceStream* prevStream)
    : SurfaceStream(type, prevStream)
    , mStaging(nullptr)
    , mConsumer(nullptr)
{
    SurfaceStream_TripleBuffer::Init(prevStream);
}

SurfaceStream_TripleBuffer::SurfaceStream_TripleBuffer(SurfaceStream* prevStream)
    : SurfaceStream(SurfaceStreamType::TripleBuffer, prevStream)
    , mStaging(nullptr)
    , mConsumer(nullptr)
{
    SurfaceStream_TripleBuffer::Init(prevStream);
}

SurfaceStream_TripleBuffer::~SurfaceStream_TripleBuffer()
{
    Delete(mStaging);
    Delete(mConsumer);
}

void
SurfaceStream_TripleBuffer::SurrenderSurfaces(SharedSurface*& producer,
                                              SharedSurface*& consumer)
{
    mIsAlive = false;

    producer = Surrender(mProducer);
    consumer = Surrender(mConsumer);

    if (!consumer)
        consumer = Surrender(mStaging);
}

SharedSurface*
SurfaceStream_TripleBuffer::SwapProducer(SurfaceFactory* factory,
                                         const gfx::IntSize& size)
{
    PROFILER_LABEL("SurfaceStream_TripleBuffer", "SwapProducer");

    MonitorAutoLock lock(mMonitor);
    if (mProducer) {
        RecycleScraps(factory);

        if (mStaging)
            Scrap(mStaging);

        MOZ_ASSERT(!mStaging);
        Move(mProducer, mStaging);
        mStaging->Fence();
    }

    MOZ_ASSERT(!mProducer);
    New(factory, size, mProducer);

    return mProducer;
}

SharedSurface*
SurfaceStream_TripleBuffer::SwapConsumer_NoWait()
{
    MonitorAutoLock lock(mMonitor);
    if (mStaging) {
        Scrap(mConsumer);
        Move(mStaging, mConsumer);
        mMonitor.NotifyAll();
    }

    return mConsumer;
}

} /* namespace gfx */
} /* namespace mozilla */
