/*
 * 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 SkMeshPriv_DEFINED
#define SkMeshPriv_DEFINED

#include "include/core/SkMesh.h"

#ifdef SK_ENABLE_SKSL
#include "include/core/SkData.h"
#include "src/core/SkSLTypeShared.h"

#if SK_SUPPORT_GPU
#include "include/gpu/GrDirectContext.h"
#include "include/private/gpu/ganesh/GrTypesPriv.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/GrDrawingManager.h"
#include "src/gpu/ganesh/GrGpuBuffer.h"
#include "src/gpu/ganesh/GrGpuBuffer.h"
#include "src/gpu/ganesh/GrResourceCache.h"
#include "src/gpu/ganesh/GrResourceProvider.h"
#endif

struct SkMeshSpecificationPriv {
    using Varying   = SkMeshSpecification::Varying;
    using Attribute = SkMeshSpecification::Attribute;
    using ColorType = SkMeshSpecification::ColorType;

    static SkSpan<const Varying> Varyings(const SkMeshSpecification& spec) {
        return SkSpan(spec.fVaryings);
    }

    static const SkSL::Program* VS(const SkMeshSpecification& spec) { return spec.fVS.get(); }
    static const SkSL::Program* FS(const SkMeshSpecification& spec) { return spec.fFS.get(); }

    static int Hash(const SkMeshSpecification& spec) { return spec.fHash; }

    static ColorType GetColorType(const SkMeshSpecification& spec) { return spec.fColorType; }
    static bool HasColors(const SkMeshSpecification& spec) {
        return GetColorType(spec) != ColorType::kNone;
    }

    static SkColorSpace* ColorSpace(const SkMeshSpecification& spec) {
        return spec.fColorSpace.get();
    }

    static SkAlphaType AlphaType(const SkMeshSpecification& spec) { return spec.fAlphaType; }

    static SkSLType VaryingTypeAsSLType(Varying::Type type) {
        switch (type) {
            case Varying::Type::kFloat:  return SkSLType::kFloat;
            case Varying::Type::kFloat2: return SkSLType::kFloat2;
            case Varying::Type::kFloat3: return SkSLType::kFloat3;
            case Varying::Type::kFloat4: return SkSLType::kFloat4;
            case Varying::Type::kHalf:   return SkSLType::kHalf;
            case Varying::Type::kHalf2:  return SkSLType::kHalf2;
            case Varying::Type::kHalf3:  return SkSLType::kHalf3;
            case Varying::Type::kHalf4:  return SkSLType::kHalf4;
        }
        SkUNREACHABLE;
    }

#if SK_SUPPORT_GPU
    static GrVertexAttribType AttrTypeAsVertexAttribType(Attribute::Type type) {
        switch (type) {
            case Attribute::Type::kFloat:        return kFloat_GrVertexAttribType;
            case Attribute::Type::kFloat2:       return kFloat2_GrVertexAttribType;
            case Attribute::Type::kFloat3:       return kFloat3_GrVertexAttribType;
            case Attribute::Type::kFloat4:       return kFloat4_GrVertexAttribType;
            case Attribute::Type::kUByte4_unorm: return kUByte4_norm_GrVertexAttribType;
        }
        SkUNREACHABLE;
    }
#endif

    static SkSLType AttrTypeAsSLType(Attribute::Type type) {
        switch (type) {
            case Attribute::Type::kFloat:        return SkSLType::kFloat;
            case Attribute::Type::kFloat2:       return SkSLType::kFloat2;
            case Attribute::Type::kFloat3:       return SkSLType::kFloat3;
            case Attribute::Type::kFloat4:       return SkSLType::kFloat4;
            case Attribute::Type::kUByte4_unorm: return SkSLType::kHalf4;
        }
        SkUNREACHABLE;
    }

    static int PassthroughLocalCoordsVaryingIndex(const SkMeshSpecification& spec) {
        return spec.fPassthroughLocalCoordsVaryingIndex;
    }

    /**
     * A varying is dead if it is never referenced OR it is only referenced as a passthrough for
     * local coordinates. In the latter case it's index will returned as
     * PassthroughLocalCoordsVaryingIndex. Our analysis is not very sophisticated so this is
     * determined conservatively.
     */
    static bool VaryingIsDead(const SkMeshSpecification& spec, int v) {
        SkASSERT(v >= 0 && SkToSizeT(v) < spec.fVaryings.size());
        return (1 << v) & spec.fDeadVaryingMask;
    }
};

struct SkMeshPriv {
    class Buffer {
    public:
        virtual ~Buffer() = 0;

        Buffer() = default;
        Buffer(const Buffer&) = delete;

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

        virtual const void* peek() const { return nullptr; }

#if SK_SUPPORT_GPU
        virtual sk_sp<const GrGpuBuffer> asGpuBuffer() const { return nullptr; }
#endif
    };

    class IB : public Buffer, public SkMesh::IndexBuffer  {};
    class VB : public Buffer, public SkMesh::VertexBuffer {};

    template <typename Base> class CpuBuffer final : public Base {
    public:
        ~CpuBuffer() override = default;

        static sk_sp<Base> Make(const void* data, size_t size);

        const void* peek() const override { return fData->data(); }

        size_t size() const override { return fData->size(); }

    private:
        CpuBuffer(sk_sp<SkData> data) : fData(std::move(data)) {}

        bool onUpdate(GrDirectContext*, const void* data, size_t offset, size_t size) override;

        sk_sp<SkData> fData;
    };

    using CpuIndexBuffer  = CpuBuffer<IB>;
    using CpuVertexBuffer = CpuBuffer<VB>;

#if SK_SUPPORT_GPU
    template <typename Base, GrGpuBufferType> class GpuBuffer final : public Base {
    public:
        GpuBuffer() = default;

        ~GpuBuffer() override;

        static sk_sp<Base> Make(GrDirectContext*, const void* data, size_t size);

        sk_sp<const GrGpuBuffer> asGpuBuffer() const override { return fBuffer; }

        size_t size() const override { return fBuffer->size(); }

    private:
        bool onUpdate(GrDirectContext*, const void* data, size_t offset, size_t size) override;

        sk_sp<GrGpuBuffer> fBuffer;
        GrDirectContext::DirectContextID fContextID;
    };

    using GpuIndexBuffer  = GpuBuffer<IB, GrGpuBufferType::kIndex >;
    using GpuVertexBuffer = GpuBuffer<VB, GrGpuBufferType::kVertex>;
#endif  // SK_SUPPORT_GPU

private:
#if SK_SUPPORT_GPU
    static bool UpdateGpuBuffer(GrDirectContext*,
                                sk_sp<GrGpuBuffer>,
                                const void*,
                                size_t offset,
                                size_t size);
#endif
};

inline SkMeshPriv::Buffer::~Buffer() = default;

template <typename Base> sk_sp<Base> SkMeshPriv::CpuBuffer<Base>::Make(const void* data,
                                                                       size_t size) {
    SkASSERT(size);
    sk_sp<SkData> storage;
    if (data) {
        storage = SkData::MakeWithCopy(data, size);
    } else {
        storage = SkData::MakeZeroInitialized(size);
    }
    return sk_sp<Base>(new CpuBuffer<Base>(std::move(storage)));
}

template <typename Base> bool SkMeshPriv::CpuBuffer<Base>::onUpdate(GrDirectContext* dc,
                                                                    const void* data,
                                                                    size_t offset,
                                                                    size_t size) {
    if (dc) {
        return false;
    }
    std::memcpy(SkTAddOffset<void>(fData->writable_data(), offset), data, size);
    return true;
}

#if SK_SUPPORT_GPU

template <typename Base, GrGpuBufferType Type> SkMeshPriv::GpuBuffer<Base, Type>::~GpuBuffer() {
    GrResourceCache::ReturnResourceFromThread(std::move(fBuffer), fContextID);
}

template <typename Base, GrGpuBufferType Type>
sk_sp<Base> SkMeshPriv::GpuBuffer<Base, Type>::Make(GrDirectContext* dc,
                                                    const void* data,
                                                    size_t size) {
    SkASSERT(dc);

    sk_sp<GrGpuBuffer> buffer = dc->priv().resourceProvider()->createBuffer(
            size,
            Type,
            kStatic_GrAccessPattern,
            data ? GrResourceProvider::ZeroInit::kNo : GrResourceProvider::ZeroInit::kYes);
    if (!buffer) {
        return nullptr;
    }

    if (data && !buffer->updateData(data, 0, size, /*preserve=*/false)) {
        return nullptr;
    }

    auto result = new GpuBuffer;
    result->fBuffer    = std::move(buffer);
    result->fContextID = dc->directContextID();
    return sk_sp<Base>(result);
}


template <typename Base, GrGpuBufferType Type>
bool SkMeshPriv::GpuBuffer<Base, Type>::onUpdate(GrDirectContext* dc,
                                                 const void* data,
                                                 size_t offset,
                                                 size_t size) {
    return UpdateGpuBuffer(dc, fBuffer, data, offset, size);
}

#endif  // SK_SUPPORT_GPU

#endif  // SK_ENABLE_SKSL

#endif
