/*
 * Copyright 2024 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "include/gpu/ganesh/mtl/GrMtlBackendSurface.h"

#include "include/gpu/ganesh/GrTypes.h"
#include "include/gpu/ganesh/mtl/GrMtlTypes.h"
#include "include/private/base/SkAssert.h"
#include "src/gpu/ganesh/GrBackendSurfacePriv.h"
#include "src/gpu/ganesh/mtl/GrMtlCppUtil.h"
#include "src/gpu/ganesh/mtl/GrMtlUtil.h"
#include "src/gpu/mtl/MtlUtilsPriv.h"

#if !__has_feature(objc_arc)
#error This file must be compiled with Arc. Use -fobjc-arc flag
#endif

class GrMtlBackendFormatData final : public GrBackendFormatData {
public:
    GrMtlBackendFormatData(GrMTLPixelFormat format) : fFormat((MTLPixelFormat)format) {}

    GrMTLPixelFormat asMtlFormat() const { return (GrMTLPixelFormat)fFormat; }

private:
    SkTextureCompressionType compressionType() const override {
        return GrMtlFormatToCompressionType(fFormat);
    }

    size_t bytesPerBlock() const override { return skgpu::MtlFormatBytesPerBlock(fFormat); }

    int stencilBits() const override { return GrMtlFormatStencilBits(fFormat); }

    uint32_t channelMask() const override { return skgpu::MtlFormatChannels(fFormat); }

    GrColorFormatDesc desc() const override { return GrMtlFormatDesc(fFormat); }

    bool equal(const GrBackendFormatData* that) const override {
        SkASSERT(!that || that->type() == GrBackendApi::kMetal);
        if (auto otherMtl = static_cast<const GrMtlBackendFormatData*>(that)) {
            return fFormat == otherMtl->fFormat;
        }
        return false;
    }

    std::string toString() const override {
#if defined(SK_DEBUG) || GPU_TEST_UTILS
        return skgpu::MtlFormatToString(fFormat);
#else
        return "";
#endif
    }

    void copyTo(AnyFormatData& formatData) const override {
        formatData.emplace<GrMtlBackendFormatData>(fFormat);
    }

#if defined(SK_DEBUG)
    GrBackendApi type() const override { return GrBackendApi::kMetal; }
#endif

    MTLPixelFormat fFormat;
};

static const GrMtlBackendFormatData* get_and_cast_data(const GrBackendFormat& format) {
    auto data = GrBackendSurfacePriv::GetBackendData(format);
    SkASSERT(!data || data->type() == GrBackendApi::kMetal);
    return static_cast<const GrMtlBackendFormatData*>(data);
}

namespace GrBackendFormats {

GrBackendFormat MakeMtl(GrMTLPixelFormat format) {
    return GrBackendSurfacePriv::MakeGrBackendFormat(
            GrTextureType::k2D, GrBackendApi::kMetal, GrMtlBackendFormatData(format));
}

GrMTLPixelFormat AsMtlFormat(const GrBackendFormat& format) {
    if (format.isValid() && format.backend() == GrBackendApi::kMetal) {
        const GrMtlBackendFormatData* data = get_and_cast_data(format);
        SkASSERT(data);
        return data->asMtlFormat();
    }
    // MTLPixelFormatInvalid == 0
    return GrMTLPixelFormat(0);
}

}  // namespace GrBackendFormats

class GrMtlBackendTextureData final : public GrBackendTextureData {
public:
    GrMtlBackendTextureData(const GrMtlTextureInfo& info) : fTexInfo(info) {}

    const GrMtlTextureInfo& info() const { return fTexInfo; }

private:
    void copyTo(AnyTextureData& textureData) const override {
        textureData.emplace<GrMtlBackendTextureData>(fTexInfo);
    }

    bool isProtected() const override { return false; }

    bool equal(const GrBackendTextureData* that) const override {
        return this->isSameTexture(that);
    }

    bool isSameTexture(const GrBackendTextureData* that) const override {
        SkASSERT(!that || that->type() == GrBackendApi::kMetal);
        if (auto otherMtl = static_cast<const GrMtlBackendTextureData*>(that)) {
            return fTexInfo == otherMtl->fTexInfo;
        }
        return false;
    }

    GrBackendFormat getBackendFormat() const override {
        return GrBackendFormats::MakeMtl(GrGetMTLPixelFormatFromMtlTextureInfo(fTexInfo));
    }

#if defined(SK_DEBUG)
    GrBackendApi type() const override { return GrBackendApi::kMetal; }
#endif

    GrMtlTextureInfo fTexInfo;
};

static const GrMtlBackendTextureData* get_and_cast_data(const GrBackendTexture& texture) {
    auto data = GrBackendSurfacePriv::GetBackendData(texture);
    SkASSERT(!data || data->type() == GrBackendApi::kMetal);
    return static_cast<const GrMtlBackendTextureData*>(data);
}

namespace GrBackendTextures {

GrBackendTexture MakeMtl(int width,
                         int height,
                         skgpu::Mipmapped mipmapped,
                         const GrMtlTextureInfo& mtlInfo,
                         std::string_view label) {
    return GrBackendSurfacePriv::MakeGrBackendTexture(width,
                                                      height,
                                                      label,
                                                      mipmapped,
                                                      GrBackendApi::kMetal,
                                                      GrTextureType::k2D,
                                                      GrMtlBackendTextureData(mtlInfo));
}

bool GetMtlTextureInfo(const GrBackendTexture& tex, GrMtlTextureInfo* outInfo) {
    if (!tex.isValid() || tex.backend() != GrBackendApi::kMetal) {
        return false;
    }
    const GrMtlBackendTextureData* data = get_and_cast_data(tex);
    SkASSERT(data);
    *outInfo = data->info();
    return true;
}

}  // namespace GrBackendTextures

class GrMtlBackendRenderTargetData final : public GrBackendRenderTargetData {
public:
    GrMtlBackendRenderTargetData(const GrMtlTextureInfo& info) : fTexInfo(info) {}

    const GrMtlTextureInfo& info() const { return fTexInfo; }

private:
    GrBackendFormat getBackendFormat() const override {
        return GrBackendFormats::MakeMtl(GrGetMTLPixelFormatFromMtlTextureInfo(fTexInfo));
    }

    bool isProtected() const override { return false; }

    bool equal(const GrBackendRenderTargetData* that) const override {
        SkASSERT(!that || that->type() == GrBackendApi::kMetal);
        if (auto otherMtl = static_cast<const GrMtlBackendRenderTargetData*>(that)) {
            return fTexInfo == otherMtl->fTexInfo;
        }
        return false;
    }

    void copyTo(AnyRenderTargetData& rtData) const override {
        rtData.emplace<GrMtlBackendRenderTargetData>(fTexInfo);
    }

#if defined(SK_DEBUG)
    GrBackendApi type() const override { return GrBackendApi::kMetal; }
#endif

    GrMtlTextureInfo fTexInfo;
};

static const GrMtlBackendRenderTargetData* get_and_cast_data(const GrBackendRenderTarget& rt) {
    auto data = GrBackendSurfacePriv::GetBackendData(rt);
    SkASSERT(!data || data->type() == GrBackendApi::kMetal);
    return static_cast<const GrMtlBackendRenderTargetData*>(data);
}

namespace GrBackendRenderTargets {

GrBackendRenderTarget MakeMtl(int width, int height, const GrMtlTextureInfo& mtlInfo) {
    return GrBackendSurfacePriv::MakeGrBackendRenderTarget(
            width,
            height,
            std::max(1, GrMtlTextureInfoSampleCount(mtlInfo)),
            /*stencilBits=*/0,
            GrBackendApi::kMetal,
            /*framebufferOnly=*/false,  // TODO: set this from mtlInfo.fTexture->framebufferOnly
            GrMtlBackendRenderTargetData(mtlInfo));
}

bool GetMtlTextureInfo(const GrBackendRenderTarget& rt, GrMtlTextureInfo* outInfo) {
    if (!rt.isValid() || rt.backend() != GrBackendApi::kMetal) {
        return false;
    }
    const GrMtlBackendRenderTargetData* data = get_and_cast_data(rt);
    SkASSERT(data);
    *outInfo = data->info();
    return true;
}

}  // namespace GrBackendRenderTargets
