/*
 * Copyright 2021 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef skgpu_graphite_TextureInfo_DEFINED
#define skgpu_graphite_TextureInfo_DEFINED

#include "include/core/SkString.h"
#include "include/gpu/graphite/GraphiteTypes.h"
#include "include/private/base/SkAPI.h"
#include "include/private/base/SkAnySubclass.h"

struct SkISize;

namespace skgpu::graphite {

enum class TextureFormat : uint8_t;

/**
 * TextureInfo is a backend-agnostic wrapper around the properties of a texture, sans dimensions.
 * It is designed this way to be compilable w/o bringing in a specific backend's build files, and
 * without requiring heap allocations of virtual types.
 */
class SK_API TextureInfo {
private:
    class Data;
    friend class MtlTextureInfo;
    friend class DawnTextureInfo;
    friend class VulkanTextureInfo;

    // Size is the largest of the Data subclasses assuming a 64-bit compiler.
    inline constexpr static size_t kMaxSubclassSize = 112;
    using AnyTextureInfoData = SkAnySubclass<Data, kMaxSubclassSize>;

    // Base properties for all backend-specific properties. Clients managing textures directly
    // should use the public subclasses of Data directly, e.g. MtlTextureInfo/DawnTextureInfo.
    //
    // Each backend subclass must expose to TextureInfo[Priv]:
    //   static constexpr BackendApi kBackend;
    //   Protected isProtected() const;
    //   TextureFormat viewFormat() const;
    class Data {
    public:
        virtual ~Data() = default;

        Data(uint8_t sampleCount, skgpu::Mipmapped mipmapped)
                : fSampleCount(sampleCount)
                , fMipmapped(mipmapped) {}

        Data() = default;
        Data(const Data&) = default;

        Data& operator=(const Data&) = default;

        // NOTE: These fields are accessible via the backend-specific subclasses.

        // Must be a valid SampleCount value, or TextureInfo creation will fail to wrap the backend
        // specific data. This is not strongly typed so that it can be assigned directly from the
        // backend GPU API's representation, which is often just an integer.
        uint8_t fSampleCount = 1;
        Mipmapped fMipmapped = Mipmapped::kNo;

    private:
        friend class TextureInfo;
        friend class TextureInfoPriv;

        virtual SkString toBackendString() const = 0;

        virtual void copyTo(AnyTextureInfoData&) const = 0;
        // Passed in TextureInfo will have data of the same backend type and subclass, and
        // base properties of Data have already been checked for equality/compatibility.
        virtual bool isCompatible(const TextureInfo& that, bool requireExact) const = 0;
    };

public:
    TextureInfo() = default;
    ~TextureInfo() = default;

    TextureInfo(const TextureInfo&);
    TextureInfo& operator=(const TextureInfo&);

    bool operator==(const TextureInfo& that) const {
        return this->isCompatible(that, /*requireExact=*/true);
    }
    bool operator!=(const TextureInfo& that) const { return !(*this == that); }

    bool isValid() const { return fData.has_value(); }
    BackendApi backend() const {
        SkASSERT(fData.has_value() || fBackend == BackendApi::kUnsupported);
        return fBackend;
    }

    Protected isProtected() const { return fProtected; }
    SampleCount sampleCount() const {
        return fData.has_value() ? static_cast<SampleCount>(fData->fSampleCount) : SampleCount::k1;
    }
    Mipmapped mipmapped() const {
        return fData.has_value() ? fData->fMipmapped   : Mipmapped::kNo;
    }

    // Return true if `that` describes a texture that is compatible with this info and can validly
    // be used to fulfill a promise image that was created with this TextureInfo.
    bool canBeFulfilledBy(const TextureInfo& that) const {
        return this->isCompatible(that, /*requireExact=*/false);
    }

    // Return a string containing the full description of this TextureInfo.
    SkString toString() const;

private:
    friend class TextureInfoPriv;

    template <typename BackendTextureData,
              std::enable_if_t<std::is_base_of_v<Data, BackendTextureData>, bool> = true>
    explicit TextureInfo(const BackendTextureData& data)
            : fBackend(BackendTextureData::kBackend)
            , fViewFormat(data.viewFormat())
            , fProtected(data.isProtected()) {
        // TextureInfoPriv should not construct a TextureInfo if `data` would fail this assert.
        SkASSERT(data.fSampleCount == 1 || data.fSampleCount == 2 || data.fSampleCount == 4 ||
                 data.fSampleCount == 8 || data.fSampleCount == 16);
        fData.emplace<BackendTextureData>(data);
    }

    bool isCompatible(const TextureInfo& that, bool requireExact) const;

    skgpu::BackendApi  fBackend = BackendApi::kUnsupported;
    AnyTextureInfoData fData;

    // Derived properties from the backend data, cached to avoid a virtual function call
    TextureFormat fViewFormat;
    Protected fProtected = Protected::kNo;
};

} // namespace skgpu::graphite

#endif // skgpu_graphite_TextureInfo_DEFINED
