//
// Copyright 2016 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// Stream.cpp: Implements the egl::Stream class, representing the stream
// where frames are streamed in. Implements EGLStreanKHR.

#include "libANGLE/Stream.h"

#include <EGL/eglext.h>
#include <platform/PlatformMethods.h>

#include "common/debug.h"
#include "common/mathutil.h"
#include "common/platform.h"
#include "common/utilities.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/renderer/DisplayImpl.h"
#include "libANGLE/renderer/StreamProducerImpl.h"

namespace egl
{

Stream::Stream(Display *display, const AttributeMap &attribs)
    : mLabel(nullptr),
      mDisplay(display),
      mProducerImplementation(nullptr),
      mState(EGL_STREAM_STATE_CREATED_KHR),
      mProducerFrame(0),
      mConsumerFrame(0),
      mConsumerLatency(attribs.getAsInt(EGL_CONSUMER_LATENCY_USEC_KHR, 0)),
      mConsumerAcquireTimeout(attribs.getAsInt(EGL_CONSUMER_ACQUIRE_TIMEOUT_USEC_KHR, 0)),
      mPlaneCount(0),
      mConsumerType(ConsumerType::NoConsumer),
      mProducerType(ProducerType::NoProducer)
{
    for (auto &plane : mPlanes)
    {
        plane.textureUnit = -1;
        plane.texture     = nullptr;
    }
}

Stream::~Stream()
{
    SafeDelete(mProducerImplementation);
    for (auto &plane : mPlanes)
    {
        if (plane.texture != nullptr)
        {
            plane.texture->releaseStream();
        }
    }
}

void Stream::setLabel(EGLLabelKHR label)
{
    mLabel = label;
}

EGLLabelKHR Stream::getLabel() const
{
    return mLabel;
}

void Stream::setConsumerLatency(EGLint latency)
{
    mConsumerLatency = latency;
}

EGLint Stream::getConsumerLatency() const
{
    return mConsumerLatency;
}

EGLuint64KHR Stream::getProducerFrame() const
{
    return mProducerFrame;
}

EGLuint64KHR Stream::getConsumerFrame() const
{
    return mConsumerFrame;
}

EGLenum Stream::getState() const
{
    return mState;
}

void Stream::setConsumerAcquireTimeout(EGLint timeout)
{
    mConsumerAcquireTimeout = timeout;
}

EGLint Stream::getConsumerAcquireTimeout() const
{
    return mConsumerAcquireTimeout;
}

Stream::ProducerType Stream::getProducerType() const
{
    return mProducerType;
}

Stream::ConsumerType Stream::getConsumerType() const
{
    return mConsumerType;
}

EGLint Stream::getPlaneCount() const
{
    return mPlaneCount;
}

rx::StreamProducerImpl *Stream::getImplementation()
{
    return mProducerImplementation;
}

Error Stream::createConsumerGLTextureExternal(const AttributeMap &attributes, gl::Context *context)
{
    ASSERT(mState == EGL_STREAM_STATE_CREATED_KHR);
    ASSERT(mConsumerType == ConsumerType::NoConsumer);
    ASSERT(mProducerType == ProducerType::NoProducer);
    ASSERT(context != nullptr);

    const auto &glState = context->getState();
    EGLenum bufferType  = attributes.getAsInt(EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER);
    if (bufferType == EGL_RGB_BUFFER)
    {
        mPlanes[0].texture = glState.getTargetTexture(gl::TextureType::External);
        ASSERT(mPlanes[0].texture != nullptr);
        mPlanes[0].texture->bindStream(this);
        mConsumerType = ConsumerType::GLTextureRGB;
        mPlaneCount   = 1;
    }
    else
    {
        mPlaneCount = attributes.getAsInt(EGL_YUV_NUMBER_OF_PLANES_EXT, 2);
        ASSERT(mPlaneCount <= 3);
        for (EGLint i = 0; i < mPlaneCount; i++)
        {
            // Fetch all the textures
            mPlanes[i].textureUnit = attributes.getAsInt(EGL_YUV_PLANE0_TEXTURE_UNIT_NV + i, -1);
            if (mPlanes[i].textureUnit != EGL_NONE)
            {
                mPlanes[i].texture =
                    glState.getSamplerTexture(mPlanes[i].textureUnit, gl::TextureType::External);
                ASSERT(mPlanes[i].texture != nullptr);
            }
        }

        // Bind them to the stream
        for (EGLint i = 0; i < mPlaneCount; i++)
        {
            if (mPlanes[i].textureUnit != EGL_NONE)
            {
                mPlanes[i].texture->bindStream(this);
            }
        }
        mConsumerType = ConsumerType::GLTextureYUV;
    }

    mContext = context;
    mState   = EGL_STREAM_STATE_CONNECTING_KHR;

    return NoError();
}

Error Stream::createProducerD3D11Texture(const AttributeMap &attributes)
{
    ASSERT(mState == EGL_STREAM_STATE_CONNECTING_KHR);
    ASSERT(mConsumerType == ConsumerType::GLTextureRGB ||
           mConsumerType == ConsumerType::GLTextureYUV);
    ASSERT(mProducerType == ProducerType::NoProducer);

    mProducerImplementation =
        mDisplay->getImplementation()->createStreamProducerD3DTexture(mConsumerType, attributes);
    mProducerType = ProducerType::D3D11Texture;
    mState        = EGL_STREAM_STATE_EMPTY_KHR;

    return NoError();
}

// Called when the consumer of this stream starts using the stream
Error Stream::consumerAcquire(const gl::Context *context)
{
    ASSERT(mState == EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR ||
           mState == EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR);
    ASSERT(mConsumerType == ConsumerType::GLTextureRGB ||
           mConsumerType == ConsumerType::GLTextureYUV);
    ASSERT(mProducerType == ProducerType::D3D11Texture);

    mState = EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR;
    mConsumerFrame++;

    // Bind the planes to the gl textures
    for (int i = 0; i < mPlaneCount; i++)
    {
        if (mPlanes[i].texture != nullptr)
        {
            ANGLE_TRY(ResultToEGL(mPlanes[i].texture->acquireImageFromStream(
                context, mProducerImplementation->getGLFrameDescription(i))));
        }
    }

    return NoError();
}

Error Stream::consumerRelease(const gl::Context *context)
{
    ASSERT(mState == EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR ||
           mState == EGL_STREAM_STATE_OLD_FRAME_AVAILABLE_KHR);
    ASSERT(mConsumerType == ConsumerType::GLTextureRGB ||
           mConsumerType == ConsumerType::GLTextureYUV);
    ASSERT(mProducerType == ProducerType::D3D11Texture);

    // Release the images
    for (int i = 0; i < mPlaneCount; i++)
    {
        if (mPlanes[i].texture != nullptr)
        {
            ANGLE_TRY(ResultToEGL(mPlanes[i].texture->releaseImageFromStream(context)));
        }
    }

    return NoError();
}

bool Stream::isConsumerBoundToContext(const gl::Context *context) const
{
    ASSERT(context != nullptr);
    return (context == mContext);
}

Error Stream::validateD3D11Texture(const void *texture, const AttributeMap &attributes) const
{
    ASSERT(mConsumerType == ConsumerType::GLTextureRGB ||
           mConsumerType == ConsumerType::GLTextureYUV);
    ASSERT(mProducerType == ProducerType::D3D11Texture);
    ASSERT(mProducerImplementation != nullptr);

    return mProducerImplementation->validateD3DTexture(texture, attributes);
}

Error Stream::postD3D11Texture(void *texture, const AttributeMap &attributes)
{
    ASSERT(mConsumerType == ConsumerType::GLTextureRGB ||
           mConsumerType == ConsumerType::GLTextureYUV);
    ASSERT(mProducerType == ProducerType::D3D11Texture);

    mProducerImplementation->postD3DTexture(texture, attributes);
    mProducerFrame++;

    mState = EGL_STREAM_STATE_NEW_FRAME_AVAILABLE_KHR;

    return NoError();
}

// This is called when a texture object associated with this stream is destroyed. Even if multiple
// textures are bound, one being destroyed invalidates the stream, so all the remaining textures
// will be released and the stream will be invalidated.
void Stream::releaseTextures()
{
    for (auto &plane : mPlanes)
    {
        if (plane.texture != nullptr)
        {
            plane.texture->releaseStream();
            plane.texture = nullptr;
        }
    }
    mConsumerType = ConsumerType::NoConsumer;
    mProducerType = ProducerType::NoProducer;
    mState        = EGL_STREAM_STATE_DISCONNECTED_KHR;
}

}  // namespace egl
