// Copyright 2018 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef INCLUDE_DAWN_NATIVE_DAWNNATIVE_H_
#define INCLUDE_DAWN_NATIVE_DAWNNATIVE_H_

#include <webgpu/webgpu_cpp.h>

#include <string>
#include <string_view>
#include <vector>

#include "dawn/dawn_proc_table.h"
#include "dawn/native/dawn_native_export.h"

namespace dawn::platform {
class Platform;
}  // namespace dawn::platform

namespace dawn::native {

class InstanceBase;
class AdapterBase;

// Each toggle is assigned with a TogglesStage, indicating the validation and earliest usage
// time of the toggle.
enum class ToggleStage { Instance, Adapter, Device };

// A struct to record the information of a toggle. A toggle is a code path in Dawn device that
// can be manually configured to run or not outside Dawn, including workarounds, special
// features and optimizations.
struct ToggleInfo {
    const char* name;
    const char* description;
    const char* url;
    ToggleStage stage;
};

// A struct to record the information of a feature. A feature is a GPU feature that is not
// required to be supported by all Dawn backends and can only be used when it is enabled on the
// creation of device.
struct FeatureInfo {
    const char* name;
    const char* description;
    const char* url;
    // The enum of feature state, could be stable or experimental. Using an experimental feature
    // requires the AllowUnsafeAPIs toggle to be enabled.
    enum class FeatureState { Stable = 0, Experimental };
    FeatureState featureState;
};

// An adapter is an object that represent on possibility of creating devices in the system.
// Most of the time it will represent a combination of a physical GPU and an API. Not that the
// same GPU can be represented by multiple adapters but on different APIs.
//
// The underlying Dawn adapter is owned by the Dawn instance so this class is not RAII but just
// a reference to an underlying adapter.
class DAWN_NATIVE_EXPORT Adapter {
  public:
    Adapter();
    // NOLINTNEXTLINE(runtime/explicit)
    Adapter(AdapterBase* impl);
    ~Adapter();

    Adapter(const Adapter& other);
    Adapter& operator=(const Adapter& other);

    void SetUseTieredLimits(bool useTieredLimits);

    // Check that the Adapter is able to support importing external images. This is necessary
    // to implement the swapchain and interop APIs in Chromium.
    bool SupportsExternalImages() const;

    explicit operator bool() const;

    // Create a device on this adapter. On an error, nullptr is returned.
    WGPUDevice CreateDevice(const wgpu::DeviceDescriptor* deviceDescriptor);
    WGPUDevice CreateDevice(const WGPUDeviceDescriptor* deviceDescriptor = nullptr);

    // Returns the underlying WGPUAdapter object.
    WGPUAdapter Get() const;

    // Reset the backend device object for testing purposes.
    void ResetInternalDeviceForTesting();

  private:
    AdapterBase* mImpl = nullptr;
};

enum BackendValidationLevel { Full, Partial, Disabled };

// Can be chained in InstanceDescriptor
struct DAWN_NATIVE_EXPORT DawnInstanceDescriptor : wgpu::ChainedStruct {
    DawnInstanceDescriptor();
    uint32_t additionalRuntimeSearchPathsCount = 0;
    const char* const* additionalRuntimeSearchPaths = nullptr;
    dawn::platform::Platform* platform = nullptr;

    BackendValidationLevel backendValidationLevel = BackendValidationLevel::Disabled;
    bool beginCaptureOnStartup = false;

    WGPULoggingCallbackInfo loggingCallbackInfo = WGPU_LOGGING_CALLBACK_INFO_INIT;

    template <typename F,
              typename T,
              typename Cb = wgpu::LoggingCallback<T>,
              typename = std::enable_if_t<std::is_convertible_v<F, Cb*>>>
    void SetLoggingCallback(F callback, T userdata) {
        assert(loggingCallbackInfo.callback == nullptr);

        loggingCallbackInfo.callback = [](WGPULoggingType type, struct WGPUStringView message,
                                          void* callback_param, void* userdata_param) {
            auto cb = reinterpret_cast<Cb*>(callback_param);
            (*cb)(static_cast<wgpu::LoggingType>(type), message, static_cast<T>(userdata_param));
        };
        loggingCallbackInfo.userdata1 = reinterpret_cast<void*>(+callback);
        loggingCallbackInfo.userdata2 = reinterpret_cast<void*>(userdata);
    }

    template <typename L,
              typename Cb = wgpu::LoggingCallback<>,
              typename = std::enable_if_t<std::is_convertible_v<L, Cb>>>
    void SetLoggingCallback(L callback) {
        assert(loggingCallbackInfo.callback == nullptr);
        using F = wgpu::LoggingCallback<void>;
        static_assert(std::is_convertible_v<L, F*>, "Logging callback cannot be a binding lambda");

        loggingCallbackInfo.callback = [](WGPULoggingType type, struct WGPUStringView message,
                                          void* callback_param, void*) {
            auto cb = reinterpret_cast<F*>(callback_param);
            (*cb)(static_cast<wgpu::LoggingType>(type), message);
        };
        loggingCallbackInfo.userdata1 = reinterpret_cast<void*>(+callback);
        loggingCallbackInfo.userdata2 = nullptr;
    }

    // Equality operators, mostly for testing. Note that this tests
    // strict pointer-pointer equality if the struct contains member pointers.
    bool operator==(const DawnInstanceDescriptor& rhs) const;
};

// Represents a connection to dawn_native and is used for dependency injection, discovering
// system adapters and injecting custom adapters (like a Swiftshader Vulkan adapter).
//
// This is an RAII class for Dawn instances and also controls the lifetime of all adapters
// for this instance.
class DAWN_NATIVE_EXPORT Instance {
  public:
    explicit Instance(const WGPUInstanceDescriptor* desc = nullptr);
    explicit Instance(const wgpu::InstanceDescriptor* desc);
    explicit Instance(InstanceBase* impl);
    ~Instance();

    Instance(const Instance& other) = delete;
    Instance& operator=(const Instance& other) = delete;

    // Discovers and returns a vector of adapters.
    // All systems adapters that can be found are returned if no options are passed.
    // Otherwise, returns adapters based on the `options`. Adapter toggles descriptor can chained
    // after options.
    std::vector<Adapter> EnumerateAdapters(const WGPURequestAdapterOptions* options) const;
    std::vector<Adapter> EnumerateAdapters(
        const wgpu::RequestAdapterOptions* options = nullptr) const;

    const ToggleInfo* GetToggleInfo(const char* toggleName);

    // Enables backend validation layers
    void SetBackendValidationLevel(BackendValidationLevel validationLevel);

    uint64_t GetDeviceCountForTesting() const;
    // Backdoor to get the number of deprecation warnings for testing
    uint64_t GetDeprecationWarningCountForTesting() const;

    // Returns the underlying WGPUInstance object.
    WGPUInstance Get() const;

    // Make mImpl->mPlatform point to an object inside Dawn in case it becomes a dangling pointer
    void DisconnectDawnPlatform();

    // Used by DawnTest, NOT thread-safe.
    void SetPlatformForTesting(dawn::platform::Platform* platform);

  private:
    InstanceBase* mImpl = nullptr;
};

// Backend-agnostic API for dawn_native
DAWN_NATIVE_EXPORT const DawnProcTable& GetProcs();

// Query the names of all the toggles that are enabled in adapter
DAWN_NATIVE_EXPORT std::vector<const char*> GetTogglesUsed(const wgpu::Adapter& adapter);

// Query the names of all the toggles that are enabled in device
DAWN_NATIVE_EXPORT std::vector<const char*> GetTogglesUsed(WGPUDevice device);

// Backdoor to get the number of lazy clears for testing
DAWN_NATIVE_EXPORT size_t GetLazyClearCountForTesting(WGPUDevice device);

//  Query if texture has been initialized
DAWN_NATIVE_EXPORT bool IsTextureSubresourceInitialized(
    WGPUTexture texture,
    uint32_t baseMipLevel,
    uint32_t levelCount,
    uint32_t baseArrayLayer,
    uint32_t layerCount,
    WGPUTextureAspect aspect = WGPUTextureAspect_All);

// Backdoor to get the order of the ProcMap for testing
DAWN_NATIVE_EXPORT std::vector<std::string_view> GetProcMapNamesForTesting();

DAWN_NATIVE_EXPORT bool DeviceTick(WGPUDevice device);

DAWN_NATIVE_EXPORT bool InstanceProcessEvents(WGPUInstance instance);

// ErrorInjector functions used for testing only. Defined in dawn_native/ErrorInjector.cpp
DAWN_NATIVE_EXPORT void EnableErrorInjector();
DAWN_NATIVE_EXPORT void DisableErrorInjector();
DAWN_NATIVE_EXPORT void ClearErrorInjector();
DAWN_NATIVE_EXPORT uint64_t AcquireErrorInjectorCallCount();
DAWN_NATIVE_EXPORT void InjectErrorAt(uint64_t index);

// The different types of external images
enum ExternalImageType : uint16_t {
    OpaqueFD,
    DmaBuf,
    IOSurface,
    EGLImage,
    GLTexture,
    AHardwareBuffer,
    Last = AHardwareBuffer,
};

// Common properties of external images
struct DAWN_NATIVE_EXPORT ExternalImageDescriptor {
  public:
    const WGPUTextureDescriptor* cTextureDescriptor = nullptr;  // Must match image creation params
    bool isInitialized = false;  // Whether the texture is initialized on import
    ExternalImageType GetType() const;

  protected:
    explicit ExternalImageDescriptor(ExternalImageType type);

  private:
    ExternalImageType mType;
};

struct DAWN_NATIVE_EXPORT ExternalImageExportInfo {
  public:
    bool isInitialized = false;  // Whether the texture is initialized after export
    ExternalImageType GetType() const;

  protected:
    explicit ExternalImageExportInfo(ExternalImageType type);

  private:
    ExternalImageType mType;
};

DAWN_NATIVE_EXPORT bool CheckIsErrorForTesting(void* objectHandle);

DAWN_NATIVE_EXPORT std::string GetObjectLabelForTesting(void* objectHandle);

DAWN_NATIVE_EXPORT uint64_t GetAllocatedSizeForTesting(WGPUBuffer buffer);

DAWN_NATIVE_EXPORT std::vector<const ToggleInfo*> AllToggleInfos();

// Used to query the details of an feature. Return nullptr if featureName is not a valid
// name of an feature supported in Dawn.
DAWN_NATIVE_EXPORT const FeatureInfo* GetFeatureInfo(wgpu::FeatureName feature);

class DAWN_NATIVE_EXPORT MemoryDump {
  public:
    // Standard attribute |name|s for the AddScalar() and AddString() methods.
    // These match the expected names in Chromium memory-infra instrumentation.
    static const char kNameSize[];         // To represent allocated space.
    static const char kNameObjectCount[];  // To represent number of objects.

    // Standard attribute |unit|s for the AddScalar() and AddString() methods.
    // These match the expected names in Chromium memory-infra instrumentation.
    static const char kUnitsBytes[];    // Unit name to represent bytes.
    static const char kUnitsObjects[];  // Unit name to represent #objects.

    MemoryDump() = default;

    virtual void AddScalar(const char* name,
                           const char* key,
                           const char* units,
                           uint64_t value) = 0;

    virtual void AddString(const char* name, const char* key, const std::string& value) = 0;

    virtual void AddOwnerGUID(const char* name, uint64_t ownerGUID);

    MemoryDump(const MemoryDump&) = delete;
    MemoryDump& operator=(const MemoryDump&) = delete;

  protected:
    virtual ~MemoryDump() = default;
};
DAWN_NATIVE_EXPORT void DumpMemoryStatistics(WGPUDevice device, MemoryDump* dump);

// Intended for background tracing for UMA that returns the estimated memory usage.
struct DAWN_NATIVE_EXPORT MemoryUsageInfo {
    // Total memory usage.
    uint64_t totalUsage;
    // Total depth stencil textures' memory.
    uint64_t depthStencilTexturesUsage;
    // Total MSAA textures' memory.
    uint64_t msaaTexturesUsage;
    // Number of MSAA textures.
    uint64_t msaaTexturesCount;
    // Largest MSAA texture's memory.
    uint64_t largestMsaaTextureUsage;
    // Total textures' memory.
    uint64_t texturesUsage;
    // Total buffers' memory.
    uint64_t buffersUsage;
};
DAWN_NATIVE_EXPORT MemoryUsageInfo ComputeEstimatedMemoryUsageInfo(WGPUDevice device);

// Memory information gathered from backend specific allocators.
// - memory allocated by clients for objects such as buffers, textures.
// - heap memory used by the allocator for allocations.
struct DAWN_NATIVE_EXPORT AllocatorMemoryInfo {
    uint64_t totalUsedMemory = 0;
    uint64_t totalAllocatedMemory = 0;
    uint64_t totalLazyAllocatedMemory = 0;
    uint64_t totalLazyUsedMemory = 0;
};
DAWN_NATIVE_EXPORT AllocatorMemoryInfo GetAllocatorMemoryInfo(WGPUDevice device);

// Free any unused GPU memory like staging buffers, cached resources, etc. Returns true if there are
// still objects to delete and ReduceMemoryUsage() should be run again after a short delay to allow
// submitted work to complete.
DAWN_NATIVE_EXPORT bool ReduceMemoryUsage(WGPUDevice device);

// Perform tasks that are appropriate to do when idle like serializing pipeline
// caches, etc.
DAWN_NATIVE_EXPORT void PerformIdleTasks(const wgpu::Device& device);

DAWN_NATIVE_EXPORT bool IsDeviceLost(WGPUDevice device);

}  // namespace dawn::native

#endif  // INCLUDE_DAWN_NATIVE_DAWNNATIVE_H_
