//
// Copyright 2021 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.
//
// ImageMtl.cpp:
//    Implements the class methods for ImageMtl.
//

#include "libANGLE/renderer/metal/ImageMtl.h"

#include "common/debug.h"
#include "libANGLE/Context.h"
#include "libANGLE/Display.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
#include "libANGLE/renderer/metal/DisplayMtl.h"
#include "libANGLE/renderer/metal/RenderBufferMtl.h"
#include "libANGLE/renderer/metal/TextureMtl.h"

namespace rx
{

// TextureImageSiblingMtl implementation
TextureImageSiblingMtl::TextureImageSiblingMtl(EGLClientBuffer buffer)
    : mBuffer(buffer), mGLFormat(GL_NONE)
{}

TextureImageSiblingMtl::~TextureImageSiblingMtl() {}

// Static
bool TextureImageSiblingMtl::ValidateClientBuffer(const DisplayMtl *display, EGLClientBuffer buffer)
{
    id<MTLTexture> texture = (__bridge id<MTLTexture>)(buffer);
    if (!texture || texture.device != display->getMetalDevice())
    {
        return false;
    }

    if (texture.textureType != MTLTextureType2D && texture.textureType != MTLTextureTypeCube)
    {
        return false;
    }

    angle::FormatID angleFormatId = mtl::Format::MetalToAngleFormatID(texture.pixelFormat);
    const mtl::Format &format     = display->getPixelFormat(angleFormatId);
    if (!format.valid())
    {
        ERR() << "Unrecognized format";
        // Not supported
        return false;
    }

    return true;
}

egl::Error TextureImageSiblingMtl::initialize(const egl::Display *display)
{
    DisplayMtl *displayMtl = mtl::GetImpl(display);
    if (initImpl(displayMtl) != angle::Result::Continue)
    {
        return egl::EglBadParameter();
    }

    return egl::NoError();
}

angle::Result TextureImageSiblingMtl::initImpl(DisplayMtl *displayMtl)
{
    mNativeTexture = mtl::Texture::MakeFromMetal((__bridge id<MTLTexture>)(mBuffer));

    angle::FormatID angleFormatId =
        mtl::Format::MetalToAngleFormatID(mNativeTexture->pixelFormat());
    mFormat = displayMtl->getPixelFormat(angleFormatId);

    if (mNativeTexture)
    {
        size_t resourceSize = EstimateTextureSizeInBytes(
            mFormat, mNativeTexture->widthAt0(), mNativeTexture->heightAt0(),
            mNativeTexture->depthAt0(), mNativeTexture->samples(), mNativeTexture->mipmapLevels());
        mNativeTexture->setEstimatedByteSize(resourceSize);
    }

    mGLFormat = gl::Format(mFormat.intendedAngleFormat().glInternalFormat);

    mRenderable = mFormat.getCaps().depthRenderable || mFormat.getCaps().colorRenderable;

    mTextureable = mFormat.getCaps().filterable || mFormat.hasDepthOrStencilBits();

    return angle::Result::Continue;
}

void TextureImageSiblingMtl::onDestroy(const egl::Display *display)
{
    mNativeTexture = nullptr;
}

gl::Format TextureImageSiblingMtl::getFormat() const
{
    return mGLFormat;
}

bool TextureImageSiblingMtl::isRenderable(const gl::Context *context) const
{
    return mRenderable;
}

bool TextureImageSiblingMtl::isTexturable(const gl::Context *context) const
{
    return mTextureable;
}

gl::Extents TextureImageSiblingMtl::getSize() const
{
    return mNativeTexture ? mNativeTexture->sizeAt0() : gl::Extents(0, 0, 0);
}

size_t TextureImageSiblingMtl::getSamples() const
{
    uint32_t samples = mNativeTexture ? mNativeTexture->samples() : 0;
    return samples > 1 ? samples : 0;
}

bool TextureImageSiblingMtl::isYUV() const
{
    // NOTE(hqle): not supporting YUV image yet.
    return false;
}

bool TextureImageSiblingMtl::hasProtectedContent() const
{
    return false;
}

// ImageMtl implementation
ImageMtl::ImageMtl(const egl::ImageState &state, const gl::Context *context) : ImageImpl(state) {}

ImageMtl::~ImageMtl() {}

void ImageMtl::onDestroy(const egl::Display *display)
{
    mNativeTexture = nullptr;
}

egl::Error ImageMtl::initialize(const egl::Display *display)
{
    if (mState.target == EGL_METAL_TEXTURE_ANGLE)
    {
        const TextureImageSiblingMtl *externalImageSibling =
            GetImplAs<TextureImageSiblingMtl>(GetAs<egl::ExternalImageSibling>(mState.source));

        mNativeTexture = externalImageSibling->getTexture();

        switch (mNativeTexture->textureType())
        {
            case MTLTextureType2D:
                mImageTextureType = gl::TextureType::_2D;
                break;
            case MTLTextureTypeCube:
                mImageTextureType = gl::TextureType::CubeMap;
                break;
            default:
                UNREACHABLE();
        }

        mImageLevel = 0;
        mImageLayer = 0;
    }
    else
    {
        UNREACHABLE();
        return egl::EglBadAccess();
    }

    return egl::NoError();
}

angle::Result ImageMtl::orphan(const gl::Context *context, egl::ImageSibling *sibling)
{
    if (sibling == mState.source)
    {
        mNativeTexture = nullptr;
    }

    return angle::Result::Continue;
}

}  // namespace rx
