// Copyright 2020 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.

#include "src/dawn/native/CreatePipelineAsyncEvent.h"

#include <webgpu/webgpu.h>

#include <utility>

#include "dawn/native/dawn_platform_autogen.h"
#include "dawn/native/wgpu_structs_autogen.h"
#include "dawn/platform/DawnPlatform.h"
#include "src/dawn/common/FutureUtils.h"
#include "src/dawn/common/Ref.h"
#include "src/dawn/common/StringViewUtils.h"
#include "src/dawn/native/AsyncTask.h"
#include "src/dawn/native/ComputePipeline.h"
#include "src/dawn/native/Device.h"
#include "src/dawn/native/ErrorData.h"
#include "src/dawn/native/EventManager.h"
#include "src/dawn/native/Instance.h"
#include "src/dawn/native/RenderPipeline.h"
#include "src/dawn/native/utils/WGPUHelpers.h"
#include "src/dawn/platform/metrics/HistogramMacros.h"
#include "src/dawn/platform/tracing/TraceEvent.h"

namespace dawn::native {

template <>
const char* CreatePipelineAsyncEvent<
    ComputePipelineBase,
    WGPUCreateComputePipelineAsyncCallbackInfo>::kDawnHistogramMetricsSuccess =
    "CreateComputePipelineSuccess";
template <>
const char*
    CreatePipelineAsyncEvent<ComputePipelineBase,
                             WGPUCreateComputePipelineAsyncCallbackInfo>::kDawnHistogramMetricsUS =
        "CreateComputePipelineUS";
template <>
void CreatePipelineAsyncEvent<ComputePipelineBase, WGPUCreateComputePipelineAsyncCallbackInfo>::
    AddOrGetCachedPipeline() {
    DeviceBase* device = mPipeline->GetDevice();
    if (device->GetState() == DeviceBase::State::Alive) {
        mPipeline = device->AddOrGetCachedComputePipeline(std::move(mPipeline));
    }
}

template <>
const char* CreatePipelineAsyncEvent<
    RenderPipelineBase,
    WGPUCreateRenderPipelineAsyncCallbackInfo>::kDawnHistogramMetricsSuccess =
    "CreateRenderPipelineSuccess";
template <>
const char*
    CreatePipelineAsyncEvent<RenderPipelineBase,
                             WGPUCreateRenderPipelineAsyncCallbackInfo>::kDawnHistogramMetricsUS =
        "CreateRenderPipelineUS";
template <>
void CreatePipelineAsyncEvent<RenderPipelineBase,
                              WGPUCreateRenderPipelineAsyncCallbackInfo>::AddOrGetCachedPipeline() {
    DeviceBase* device = mPipeline->GetDevice();
    if (device->GetState() == DeviceBase::State::Alive) {
        mPipeline = device->AddOrGetCachedRenderPipeline(std::move(mPipeline));
    }
}

template <typename PipelineType, typename CreatePipelineAsyncCallbackInfo>
CreatePipelineAsyncEvent<PipelineType, CreatePipelineAsyncCallbackInfo>::CreatePipelineAsyncEvent(
    DeviceBase* device,
    const CreatePipelineAsyncCallbackInfo& callbackInfo,
    Ref<PipelineType> pipeline,
    bool readyAtCreation)
    : TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode), readyAtCreation),
      mCallback(callbackInfo.callback),
      mUserdata1(callbackInfo.userdata1),
      mUserdata2(callbackInfo.userdata2),
      mPipeline(std::move(pipeline)) {
    if (!readyAtCreation) {
        mScopedUseShaderPrograms = mPipeline->UseShaderPrograms();
    }
}

template <typename PipelineType, typename CreatePipelineAsyncCallbackInfo>
CreatePipelineAsyncEvent<PipelineType, CreatePipelineAsyncCallbackInfo>::CreatePipelineAsyncEvent(
    DeviceBase* device,
    const CreatePipelineAsyncCallbackInfo& callbackInfo,
    std::unique_ptr<ErrorData> error,
    StringView label)
    : TrackedEvent(static_cast<wgpu::CallbackMode>(callbackInfo.mode), Completed{}),
      mCallback(callbackInfo.callback),
      mUserdata1(callbackInfo.userdata1),
      mUserdata2(callbackInfo.userdata2),
      mPipeline(PipelineType::MakeError(device, label)),
      mError(std::move(error)) {}

template <typename PipelineType, typename CreatePipelineAsyncCallbackInfo>
CreatePipelineAsyncEvent<PipelineType,
                         CreatePipelineAsyncCallbackInfo>::~CreatePipelineAsyncEvent() {
    EnsureComplete(EventCompletionType::Shutdown);
}

template <typename PipelineType, typename CreatePipelineAsyncCallbackInfo>
void CreatePipelineAsyncEvent<PipelineType, CreatePipelineAsyncCallbackInfo>::InitializeImpl(
    bool isAsync) {
    DeviceBase* device = mPipeline->GetDevice();
    const std::string& label = mPipeline->GetLabel();
    const char* eventLabel = utils::GetLabelForTrace(label);
    if (isAsync) {
        TRACE_EVENT_FLOW_END1(device->GetPlatform(), General,
                              "CreatePipelineAsyncEvent::InitializeAsync", this, "label",
                              eventLabel);
    }
    TRACE_EVENT1(device->GetPlatform(), General, "CreatePipelineAsyncEvent::InitializeImpl",
                 "label", eventLabel);

    MaybeError maybeError;
    {
        SCOPED_DAWN_HISTOGRAM_TIMER_MICROS(device->GetPlatform(), kDawnHistogramMetricsUS);
        maybeError = mPipeline->Initialize(std::move(mScopedUseShaderPrograms));
    }
    DAWN_HISTOGRAM_BOOLEAN(device->GetPlatform(), kDawnHistogramMetricsSuccess,
                           maybeError.IsSuccess());
    if (maybeError.IsError()) {
        mError = maybeError.AcquireError();
    }

    // TODO(dawn:2451): API re-entrant callbacks in spontaneous mode that use the device could
    // deadlock itself.
    device->GetInstance()->GetEventManager()->SetFutureReady(this);
}

template <typename PipelineType, typename CreatePipelineAsyncCallbackInfo>
void CreatePipelineAsyncEvent<PipelineType, CreatePipelineAsyncCallbackInfo>::InitializeSync() {
    InitializeImpl(false);
}

template <typename PipelineType, typename CreatePipelineAsyncCallbackInfo>
void CreatePipelineAsyncEvent<PipelineType, CreatePipelineAsyncCallbackInfo>::InitializeAsync() {
    DeviceBase* device = mPipeline->GetDevice();
    const std::string& label = mPipeline->GetLabel();
    const char* eventLabel = utils::GetLabelForTrace(label);
    TRACE_EVENT_FLOW_BEGIN1(device->GetPlatform(), General,
                            "CreatePipelineAsyncEvent::InitializeAsync", this, "label", eventLabel);

    auto asyncTask = [event = Ref<CreatePipelineAsyncEvent>(this)] { event->InitializeImpl(true); };
    device->GetAsyncTaskManager()->PostTask<AsyncTask>(std::move(asyncTask));
}

template <typename PipelineType, typename CreatePipelineAsyncCallbackInfo>
void CreatePipelineAsyncEvent<PipelineType, CreatePipelineAsyncCallbackInfo>::Complete(
    EventCompletionType completionType) {
    auto userdata1 = mUserdata1.ExtractAsDangling();
    auto userdata2 = mUserdata2.ExtractAsDangling();

    if (completionType == EventCompletionType::Shutdown) {
        if (mCallback) {
            mCallback(WGPUCreatePipelineAsyncStatus_CallbackCancelled, nullptr,
                      ToOutputStringView("Instance dropped"), userdata1, userdata2);
        }
        return;
    }

    Ref<DeviceBase> device = mPipeline->GetDevice();

    WGPUCreatePipelineAsyncStatus status = WGPUCreatePipelineAsyncStatus_Success;
    std::string message;
    Ref<PipelineType> pipeline;

    // Gather the arguments to mCallback while holding the device lock to handle device loss and
    // cache lookups. Release the lock before calling mCallback so that reentrant callbacks are
    // safe.
    {
        // TODO(dawn:2353): Device losts later than this check could potentially lead to racing
        // condition when not using implicit synchronization.
        auto deviceGuard = device->GetGuard();

        if (device->IsLost()) {
            // Invalid async creation should "succeed" if the device is already lost.
            if (!mPipeline->IsError()) {
                const std::string& label = mPipeline->GetLabel();
                mPipeline = PipelineType::MakeError(device.Get(), label.c_str());
            }
            pipeline = std::move(mPipeline);
        } else if (mError != nullptr) {
            switch (mError->GetType()) {
                case InternalErrorType::Validation:
                    status = WGPUCreatePipelineAsyncStatus_ValidationError;
                    break;
                default:
                    status = WGPUCreatePipelineAsyncStatus_InternalError;
                    break;
            }
            message = mError->GetFormattedMessage();
        } else {
            AddOrGetCachedPipeline();
            pipeline = std::move(mPipeline);
        }
    }

    if (mCallback) {
        WGPUStringView messageView =
            message.empty() ? kEmptyOutputStringView : ToOutputStringView(message);
        mCallback(status, ToAPI(ReturnToAPI(std::move(pipeline))), messageView, userdata1,
                  userdata2);
    }
}

template class CreatePipelineAsyncEvent<ComputePipelineBase,
                                        WGPUCreateComputePipelineAsyncCallbackInfo>;
template class CreatePipelineAsyncEvent<RenderPipelineBase,
                                        WGPUCreateRenderPipelineAsyncCallbackInfo>;

}  // namespace dawn::native
