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

#ifndef GrMtlCommandBuffer_DEFINED
#define GrMtlCommandBuffer_DEFINED

#import <Metal/Metal.h>

#include "include/core/SkRefCnt.h"
#include "include/gpu/ganesh/GrTypes.h"
#include "src/gpu/GpuRefCnt.h"
#include "src/gpu/ganesh/GrBuffer.h"
#include "src/gpu/ganesh/GrManagedResource.h"
#include "src/gpu/ganesh/GrSurface.h"
#include "src/gpu/ganesh/mtl/GrMtlRenderCommandEncoder.h"
#include "src/gpu/ganesh/mtl/GrMtlUtil.h"

class GrMtlEvent;
class GrMtlGpu;
class GrMtlPipelineState;
class GrMtlOpsRenderPass;
class GrMtlRenderCommandEncoder;

GR_NORETAIN_BEGIN

class GrMtlCommandBuffer : public SkRefCnt {
public:
    static sk_sp<GrMtlCommandBuffer> Make(id<MTLCommandQueue> queue);
    ~GrMtlCommandBuffer() override;

    void releaseResources();

    bool commit(bool waitUntilCompleted);
    bool hasWork() { return fHasWork; }

    void addFinishedCallback(sk_sp<skgpu::RefCntedCallback> callback) {
        fFinishedCallbacks.push_back(std::move(callback));
    }

    id<MTLBlitCommandEncoder> getBlitCommandEncoder();
    // Tries to reuse current renderCommandEncoder if possible
    GrMtlRenderCommandEncoder* getRenderCommandEncoder(MTLRenderPassDescriptor*,
                                                       const GrMtlPipelineState*,
                                                       GrMtlOpsRenderPass* opsRenderPass);
    // Replaces current renderCommandEncoder with new one
    GrMtlRenderCommandEncoder* getRenderCommandEncoder(MTLRenderPassDescriptor*,
                                                       GrMtlOpsRenderPass*);

    void addCompletedHandler(MTLCommandBufferHandler block) {
        [fCmdBuffer addCompletedHandler:block];
    }

    void addResource(const sk_sp<const GrManagedResource>& resource) {
// Disable generic resource tracking for now
//        SkASSERT(resource);
//        fTrackedResources.push_back(std::move(resource));
    }

    void addGrBuffer(sk_sp<const GrBuffer> buffer) {
        fTrackedGrBuffers.push_back(std::move(buffer));
    }

    void addGrSurface(sk_sp<const GrSurface> surface) {
        fTrackedGrSurfaces.push_back(std::move(surface));
    }

    void encodeSignalEvent(sk_sp<GrMtlEvent>, uint64_t value);
    void encodeWaitForEvent(sk_sp<GrMtlEvent>, uint64_t value);

    void waitUntilCompleted() {
        [fCmdBuffer waitUntilCompleted];
    }
    bool isCompleted() {
        return fCmdBuffer.status == MTLCommandBufferStatusCompleted ||
               fCmdBuffer.status == MTLCommandBufferStatusError;
    }
    void callFinishedCallbacks() { fFinishedCallbacks.clear(); }

    void pushDebugGroup(NSString* string) {
        if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
            [fCmdBuffer pushDebugGroup:string];
        }
    }

    void popDebugGroup() {
        if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
            [fCmdBuffer popDebugGroup];
        }
    }

private:
    GrMtlCommandBuffer(id<MTLCommandBuffer> cmdBuffer)
        : fCmdBuffer(cmdBuffer)
        , fActiveBlitCommandEncoder(nil)
        , fActiveRenderCommandEncoder(nil)
        , fPreviousRenderPassDescriptor(nil)
        , fHasWork(false) {}

    void endAllEncoding();

    static const int kInitialTrackedResourcesCount = 32;

    skia_private::STArray<
        kInitialTrackedResourcesCount, sk_sp<const GrManagedResource>> fTrackedResources;
    skia_private::STArray<kInitialTrackedResourcesCount, sk_sp<const GrBuffer>> fTrackedGrBuffers;
    skia_private::STArray<16, gr_cb<const GrSurface>> fTrackedGrSurfaces;

    id<MTLCommandBuffer>        fCmdBuffer;
    id<MTLBlitCommandEncoder>   fActiveBlitCommandEncoder;
    std::unique_ptr<GrMtlRenderCommandEncoder> fActiveRenderCommandEncoder;
    MTLRenderPassDescriptor*    fPreviousRenderPassDescriptor;
    bool                        fHasWork;

    skia_private::TArray<sk_sp<skgpu::RefCntedCallback>> fFinishedCallbacks;

};

GR_NORETAIN_END

#endif
