/*
 * Copyright (c) 2019-2024 Valve Corporation
 * Copyright (c) 2019-2024 LunarG, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once
#include "sync/sync_commandbuffer.h"
#include "state_tracker/queue_state.h"

struct PresentedImage;
class QueueBatchContext;
struct QueueSubmitCmdState;
class QueueSyncState;
class SyncValidator;

using BatchContextPtr = std::shared_ptr<QueueBatchContext>;
using BatchContextConstPtr = std::shared_ptr<const QueueBatchContext>;
using CommandBufferConstPtr = std::shared_ptr<const syncval_state::CommandBuffer>;

namespace vvl {
class Semaphore;
}  // namespace vvl

struct AcquiredImage {
    std::shared_ptr<const syncval_state::ImageState> image;
    subresource_adapter::ImageRangeGenerator generator;
    ResourceUsageTag present_tag;
    ResourceUsageTag acquire_tag;
    bool Invalid() const;

    AcquiredImage() = default;
    AcquiredImage(const PresentedImage &presented, ResourceUsageTag acq_tag);
};

// Information associated with a semaphore signal
struct SignalInfo {
    // QueueSubmit signal
    SignalInfo(const std::shared_ptr<const vvl::Semaphore> &semaphore_state, const std::shared_ptr<QueueBatchContext> &batch,
               const SyncExecScope &exec_scope, uint64_t timeline_value);

    // SignalSemaphore signal
    SignalInfo(const std::shared_ptr<const vvl::Semaphore> &semaphore_state, uint64_t timeline_value);

    // AcquireNextImage signal
    SignalInfo(const std::shared_ptr<const vvl::Semaphore> &semaphore_state, const PresentedImage &presented,
               ResourceUsageTag acquire_tag);

    // Signaled semaphore. Not null.
    std::shared_ptr<const vvl::Semaphore> semaphore_state;

    // Batch from the signal's first scope. It is null for a host signal (vkSignalSemaphore)
    std::shared_ptr<QueueBatchContext> batch;

    // Use the first_scope.valid_accesses for the first access scope of non-host signals.
    // first_scope.queue is kQueueIdInvalid for a host signal (vkSignalSemaphore)
    SemaphoreScope first_scope;

    // Value signaled by a timeline semaphore
    uint64_t timeline_value = 0;

    // Swapchain specific signal info.
    // Batch field is the batch of the last present for the acquired image.
    // The AcquiredImage further limits the scope of the resolve operation, and the "barrier" will also
    // be special case (updating "PRESENTED" write with "ACQUIRE" read, as well as setting the barrier).
    //
    // NOTE: shared_ptr is used here as a memory saver. AcquiredImage is 224 bytes at the time
    // of writing and is not used in the queue submit signals. If we optimize ImageRangeGenerator
    // memory usage then shared_ptr can be replaced by std::optional to avoid allocation.
    std::shared_ptr<AcquiredImage> acquired_image;
};

// When the timeline wait is resolved, the previous signals can be removed
struct RemoveTimelineSignalsRequest {
    VkSemaphore semaphore = VK_NULL_HANDLE;

    // Remove all signals with a value *less than* this threshold (should not touch signals with equal value).
    uint64_t signal_threshold_value = 0;

    // NOTE: it's possible to have multiple queues with signals that match the wait value.
    // The specification defines that exactly one signal resolves the wait, and in the presence
    // of multiple such signals the implementation may choose any of them. It's the application
    // responsibility to be careful and not to create a race condition in such a scenario.
    //
    // The queue that signaled the resolving signal. Only the signals signaled by this queue should be processed.
    QueueId queue = 0;
};

// The requests to update SyncValidator's registry of binary/timeline signals.
// They are collected during validation phase and are applied in the record phase.
struct SignalsUpdate {
    vvl::unordered_map<VkSemaphore, SignalInfo> binary_signal_requests;
    vvl::unordered_set<VkSemaphore> binary_unsignal_requests;

    vvl::unordered_map<VkSemaphore, std::vector<SignalInfo>> timeline_signals;
    std::vector<RemoveTimelineSignalsRequest> remove_timeline_signals_requests;

    // Register submission batch signals.
    // Return true if at least one timeline signal was registered
    bool RegisterSignals(const BatchContextPtr &batch, const vvl::span<const VkSemaphoreSubmitInfo> &submit_signals);

    // Return resolving binary signal. Empty result in the case of a validation error
    std::optional<SignalInfo> OnBinaryWait(VkSemaphore semaphore);

    // Return resolving timeline signal. Empty result if it is a wait-before-signal
    std::optional<SignalInfo> OnTimelineWait(VkSemaphore semaphore, uint64_t wait_value);

    SignalsUpdate(const SyncValidator &sync_validator) : sync_validator_(sync_validator) {}

  private:
    void OnBinarySignal(const vvl::Semaphore &semaphore_state, const std::shared_ptr<QueueBatchContext> &batch,
                        const VkSemaphoreSubmitInfo &submit_signal);
    // Return false if signal is invalid (non-increasing value)
    bool OnTimelineSignal(const vvl::Semaphore &semaphore_state, const std::shared_ptr<QueueBatchContext> &batch,
                          const VkSemaphoreSubmitInfo &submit_signal);

  private:
    const SyncValidator &sync_validator_;
};

struct FenceHostSyncPoint {
    QueueId queue_id = kQueueIdInvalid;
    ResourceUsageTag tag = 0;
    AcquiredImage acquired;  // Iff queue == invalid and acquired.image valid.
};

struct TimelineHostSyncPoint {
    QueueId queue_id = 0;
    ResourceUsageTag tag = 0;
    uint64_t timeline_value = 0;
};

struct PresentedImageRecord {
    ResourceUsageTag tag;  // the global tag at presentation
    uint32_t image_index;
    uint32_t present_index;
    std::weak_ptr<const syncval_state::Swapchain> swapchain_state;
    std::shared_ptr<const syncval_state::ImageState> image;
};

struct PresentedImage : public PresentedImageRecord {
    std::shared_ptr<QueueBatchContext> batch;
    subresource_adapter::ImageRangeGenerator range_gen;

    PresentedImage() = default;
    void UpdateMemoryAccess(SyncAccessIndex usage, ResourceUsageTag tag, AccessContext &access_context) const;
    PresentedImage(const SyncValidator &sync_state, std::shared_ptr<QueueBatchContext> batch, VkSwapchainKHR swapchain,
                   uint32_t image_index, uint32_t present_index, ResourceUsageTag present_tag_);
    // For non-previsously presented images..
    PresentedImage(std::shared_ptr<const syncval_state::Swapchain> swapchain, uint32_t at_index);
    bool Invalid() const;
    void ExportToSwapchain(SyncValidator &);
    void SetImage(uint32_t at_index);
};
using PresentedImages = std::vector<PresentedImage>;

// Store references to ResourceUsageRecords with global tag range within a batch
class BatchAccessLog {
  public:
    struct BatchRecord {
        const QueueSyncState *queue = nullptr;
        uint64_t submit_index = 0;
        uint32_t batch_index = 0;
        uint32_t cb_index = 0;
        ResourceUsageTag base_tag = 0;
    };

    struct AccessRecord {
        const BatchRecord *batch;
        const ResourceUsageRecord *record;
        const DebugNameProvider *debug_name_provider;
        bool IsValid() const { return batch && record; }
    };

    struct CBSubmitLog : DebugNameProvider {
      public:
        CBSubmitLog() = default;
        CBSubmitLog(const CBSubmitLog &batch) = default;
        CBSubmitLog(CBSubmitLog &&other) = default;
        CBSubmitLog &operator=(const CBSubmitLog &other) = default;
        CBSubmitLog &operator=(CBSubmitLog &&other) = default;
        CBSubmitLog(const BatchRecord &batch, std::shared_ptr<const CommandExecutionContext::CommandBufferSet> cbs,
                    std::shared_ptr<const CommandExecutionContext::AccessLog> log);
        CBSubmitLog(const BatchRecord &batch, const CommandBufferAccessContext &cb,
                    const std::vector<std::string> &initial_label_stack);
        size_t Size() const { return log_->size(); }
        AccessRecord GetAccessRecord(ResourceUsageTag tag) const;

        // DebugNameProvider
        std::string GetDebugRegionName(const ResourceUsageRecord &record) const override;

      private:
        BatchRecord batch_;
        std::shared_ptr<const CommandExecutionContext::CommandBufferSet> cbs_;
        std::shared_ptr<const CommandExecutionContext::AccessLog> log_;
        // label stack at the point when command buffer is submitted to the queue
        std::vector<std::string> initial_label_stack_;
    };

    void Import(const BatchRecord &batch, const CommandBufferAccessContext &cb_access,
                const std::vector<std::string> &initial_label_stack);
    void Import(const BatchAccessLog &other);
    void Insert(const BatchRecord &batch, const ResourceUsageRange &range,
                std::shared_ptr<const CommandExecutionContext::AccessLog> log);

    void Trim(const ResourceUsageTagSet &used);
    // AccessRecord lookup is based on global tags
    AccessRecord GetAccessRecord(ResourceUsageTag tag) const;
    BatchAccessLog() {}

  private:
    using CBSubmitLogRangeMap = sparse_container::range_map<ResourceUsageTag, CBSubmitLog>;
    CBSubmitLogRangeMap log_map_;
};

// Batch that has wait-before-signal dependencies.
struct UnresolvedBatch {
    BatchContextPtr batch;
    uint64_t submit_index = 0;
    uint32_t batch_index = 0;
    std::vector<CommandBufferConstPtr> command_buffers;

    // Waits-before-signals that prevent this batch from being resolved.
    // When the wait is resolved it is removed from this list and the batch
    // from the resolving signal is stored in resolved_dependencies array.
    std::vector<VkSemaphoreSubmitInfo> unresolved_waits;

    // The batches from the resolved dependencies. They are used for async validaton.
    // This includes the batches from the resolved waits and also the last batch
    // (prior batch on the same queue).
    std::vector<BatchContextConstPtr> resolved_dependencies;

    // Signals to signal when all the waits are resolved.
    std::vector<VkSemaphoreSubmitInfo> signals;

    // Queue's label stack at the beginning of this batch
    std::vector<std::string> label_stack;
};

// Helper struct to resolve wait-before-signal
struct UnresolvedQueue {
    std::shared_ptr<QueueSyncState> queue_state;
    std::vector<UnresolvedBatch> unresolved_batches;
    // whether unresolved state should be updated for this queue
    bool update_unresolved = false;
};

class QueueBatchContext : public CommandExecutionContext, public std::enable_shared_from_this<QueueBatchContext> {
  public:
    class PresentResourceRecord : public AlternateResourceUsage::RecordBase {
      public:
        using Base_ = AlternateResourceUsage::RecordBase;
        Base_::Record MakeRecord() const override;
        ~PresentResourceRecord() override {}
        PresentResourceRecord(const PresentedImageRecord &presented) : presented_(presented) {}
        std::ostream &Format(std::ostream &out, const SyncValidator &sync_state) const override;

      private:
        PresentedImageRecord presented_;
    };

    class AcquireResourceRecord : public AlternateResourceUsage::RecordBase {
      public:
        using Base_ = AlternateResourceUsage::RecordBase;
        Base_::Record MakeRecord() const override;
        AcquireResourceRecord(const PresentedImageRecord &presented, ResourceUsageTag tag, vvl::Func command)
            : presented_(presented), acquire_tag_(tag), command_(command) {}
        std::ostream &Format(std::ostream &out, const SyncValidator &sync_state) const override;

      private:
        PresentedImageRecord presented_;
        ResourceUsageTag acquire_tag_;
        vvl::Func command_;
    };

    using Ptr = std::shared_ptr<QueueBatchContext>;
    using ConstPtr = std::shared_ptr<const QueueBatchContext>;

    QueueBatchContext(const SyncValidator &sync_state, const QueueSyncState &queue_state);
    QueueBatchContext(const SyncValidator &sync_state);
    QueueBatchContext() = delete;
    ~QueueBatchContext();
    void Trim();

    std::string FormatUsage(ResourceUsageTagEx tag_ex) const override;
    void AddUsageRecordExtraProperties(ResourceUsageTag tag, ReportKeyValues &extra_properties) const override;
    AccessContext *GetCurrentAccessContext() override { return current_access_context_; }
    const AccessContext *GetCurrentAccessContext() const override { return current_access_context_; }
    SyncEventsContext *GetCurrentEventsContext() override { return &events_context_; }
    const SyncEventsContext *GetCurrentEventsContext() const override { return &events_context_; }
    const QueueSyncState *GetQueueSyncState() { return queue_state_; }
    QueueId GetQueueId() const override;
    ExecutionType Type() const override { return kSubmitted; }
    ResourceUsageRange GetTagRange() const { return tag_range_; }

    ResourceUsageTag SetupBatchTags(uint32_t tag_count);
    void ResetEventsContext() { events_context_.Clear(); }

    // For Submit
    std::vector<BatchContextConstPtr> ResolveSubmitWaits(vvl::span<const VkSemaphoreSubmitInfo> wait_semaphores,
                                                         std::vector<VkSemaphoreSubmitInfo> &unresolved_waits,
                                                         SignalsUpdate &signals_update);

    bool ValidateSubmit(const std::vector<CommandBufferConstPtr> &command_buffers, uint64_t submit_index, uint32_t batch_index,
                        std::vector<std::string> &current_label_stack, const ErrorObject &error_obj);
    void ResolveSubmittedCommandBuffer(const AccessContext &recorded_context, ResourceUsageTag offset);

    // For Present
    std::vector<ConstPtr> ResolvePresentWaits(vvl::span<const VkSemaphore> wait_semaphores, const PresentedImages &presented_images,
                                              SignalsUpdate &signals_update);
    bool DoQueuePresentValidate(const Location &loc, const PresentedImages &presented_images);
    void DoPresentOperations(const PresentedImages &presented_images);
    void LogPresentOperations(const PresentedImages &presented_images, uint64_t submit_index);

    // For Acquire
    void SetupAccessContext(const PresentedImage &presented);
    void DoAcquireOperation(const PresentedImage &presented);
    void LogAcquireOperation(const PresentedImage &presented, vvl::Func command);

    VulkanTypedHandle Handle() const override;

    template <typename Predicate>
    void ApplyPredicatedWait(Predicate &predicate);
    void ApplyTaggedWait(QueueId queue_id, ResourceUsageTag tag);
    void ApplyAcquireWait(const AcquiredImage &acquired);
    void OnResourceDestroyed(const ResourceAccessRange &resource_range);

    void BeginRenderPassReplaySetup(ReplayState &replay, const SyncOpBeginRenderPass &begin_op);
    void NextSubpassReplaySetup(ReplayState &replay);
    void EndRenderPassReplayCleanup(ReplayState &replay);

    [[nodiscard]] std::vector<ConstPtr> RegisterAsyncContexts(const std::vector<ConstPtr> &batches_resolved);
    void ResolveLastBatch(const QueueBatchContext::ConstPtr &last_batch);

    void ResolveSubmitSemaphoreWait(const SignalInfo &signal_info, VkPipelineStageFlags2 wait_mask);
    void ImportTags(const QueueBatchContext &from);

  private:
    void ResolvePresentSemaphoreWait(const SignalInfo &signal_info, const PresentedImages &presented_images);

  private:
    const QueueSyncState *queue_state_ = nullptr;
    ResourceUsageRange tag_range_ = ResourceUsageRange(0, 0);  // Range of tags referenced by cbs_referenced

    AccessContext access_context_;
    AccessContext *current_access_context_;
    SyncEventsContext events_context_;
    BatchAccessLog batch_log_;
    std::vector<ResourceUsageTag> queue_sync_tag_;
};

class QueueSyncState {
  public:
    QueueSyncState(const std::shared_ptr<vvl::Queue> &queue_state, QueueId id) : id_(id), queue_state_(queue_state) {}

    VulkanTypedHandle Handle() const { return queue_state_->Handle(); }
    const vvl::Queue *GetQueueState() const { return queue_state_.get(); }
    VkQueueFlags GetQueueFlags() const { return queue_state_->queue_family_properties.queueFlags; }
    QueueId GetQueueId() const { return id_; }
    // Method is const but updates mutable sumbit_index atomically.
    uint64_t ReserveSubmitId() const;

    // Last batch state management.
    // The Validate phase makes a request to update last batch by calling SetPendingLastBatch.
    // Then the Record phase actually updates the last batch by calling ApplyPendingLastBatch.
    // Pending last batch is a mutable state. It relies on the queue external synchronization.
    QueueBatchContext::ConstPtr LastBatch() const { return last_batch_; }
    QueueBatchContext::Ptr LastBatch() { return last_batch_; }
    void SetPendingLastBatch(QueueBatchContext::Ptr &&last) const;
    void ApplyPendingLastBatch();
    QueueBatchContext::Ptr PendingLastBatch() const { return pending_last_batch_; }

    // Unresolved batches state management.
    // The Validate phase makes request to update the list of unresolved batches by calling SetPendingUnresolvedBatches.
    // Then the Record phase actually updates the list of unresolved batches by calling ApplyPendingLastBatch.
    // Pending unresovled batches is a mutable state. It relies on the queue external synchronization.
    const std::vector<UnresolvedBatch> &UnresolvedBatches() const { return unresolved_batches_; }
    void SetPendingUnresolvedBatches(std::vector<UnresolvedBatch> &&unresolved_batches) const;
    void ApplyPendingUnresolvedBatches();
    const std::vector<UnresolvedBatch> &PendingUnresolvedBatches() const { return pending_unresolved_batches_; }

    // Called by the Validate methods to ensure no pending state is left.
    // Pending state is automatically cleared in PostRecord calls,
    // the only exception is when validation error happens.
    void ClearPending() const;

  private:
    const QueueId id_;
    std::shared_ptr<vvl::Queue> queue_state_;
    mutable std::atomic<uint64_t> submit_index_ = 0;

    QueueBatchContext::Ptr last_batch_;

    // The first batch in the unresolved batches list is always due to the wait-before-signal dependency.
    // All subsequent batches from the same queue must also be stored here because they can't be processed
    // until the wait-before-signal dependency is resolved (respect submission order). When the first batch
    // is resolved, we start processing other queued batches until we uncoutner a batch with unresolved
    // wait-before-signal (it becomes the new head of the list) or the list is empty.
    std::vector<UnresolvedBatch> unresolved_batches_;

    mutable QueueBatchContext::Ptr pending_last_batch_;
    mutable std::vector<UnresolvedBatch> pending_unresolved_batches_;
    mutable bool update_unresolved_batches_ = false;
};

struct QueueSubmitCmdState {
    std::shared_ptr<const QueueSyncState> queue;
    SignalsUpdate signals_update;
    QueueSubmitCmdState(const SyncValidator &sync_validator) : signals_update(sync_validator) {}
};
