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

#include "src/gpu/graphite/PaintParamsKey.h"

#include <cstring>
#include "src/gpu/graphite/KeyHelpers.h"
#include "src/gpu/graphite/ShaderCodeDictionary.h"

namespace skgpu::graphite {

using DataPayloadType  = PaintParamsKey::DataPayloadType;
using DataPayloadField = PaintParamsKey::DataPayloadField;

//--------------------------------------------------------------------------------------------------
static PaintParamsKey::Header read_header(SkSpan<const uint8_t> parentSpan, int headerOffset) {
    SkASSERT(headerOffset + sizeof(PaintParamsKey::Header) <= parentSpan.size());

    PaintParamsKey::Header header;
    memcpy(&header, &parentSpan[headerOffset], sizeof(PaintParamsKey::Header));
    SkASSERT(header.blockSize >= sizeof(PaintParamsKey::Header));
    SkASSERT(headerOffset + header.blockSize <= static_cast<int>(parentSpan.size()));

    return header;
}

//--------------------------------------------------------------------------------------------------
PaintParamsKeyBuilder::PaintParamsKeyBuilder(const ShaderCodeDictionary* dict)
        : fDict(dict) {}

#ifdef SK_DEBUG
void PaintParamsKeyBuilder::checkReset() {
    SkASSERT(!this->isLocked());
    SkASSERT(this->sizeInBytes() == 0);
    SkASSERT(fIsValid);
    SkASSERT(fStack.empty());
    SkASSERT(fBlendInfo == skgpu::BlendInfo());
}
#endif

// Block headers have the following structure:
//   4 bytes: code-snippet ID
//   1 byte: size of the block, in bytes (header, plus all data payload bytes)
// This call stores the header's offset in the key on the stack to be used in 'endBlock'
void PaintParamsKeyBuilder::beginBlock(int32_t codeSnippetID) {
    if (!this->isValid()) {
        return;
    }

    if (!fDict->isValidID(codeSnippetID)) {
        // SKGPU_LOG_W("Unknown code snippet ID.");
        this->makeInvalid();
        return;
    }

#ifdef SK_DEBUG
    if (!fStack.empty()) {
        // The children of a block should appear before any of the parent's data
        SkASSERT(fStack.back().fCurDataPayloadEntry == 0);
        fStack.back().fNumActualChildren++;
    }

    static constexpr DataPayloadField kHeader[2] = {
            {"snippetID", DataPayloadType::kInt, 1},
            {"blockSize", DataPayloadType::kByte, 1},
    };

    static const SkSpan<const DataPayloadField> kHeaderExpectations(kHeader);
#endif

    SkASSERT(!this->isLocked());

    fStack.push_back({ codeSnippetID, this->sizeInBytes(),
                       SkDEBUGCODE(kHeaderExpectations, 0) });

    this->addInt(codeSnippetID);
    this->addByte(0);  // this will be filled in when endBlock is called

#ifdef SK_DEBUG
    const ShaderSnippet* snippet = fDict->getEntry(codeSnippetID);

    fStack.back().fDataPayloadExpectations = snippet->fDataPayloadExpectations;
    fStack.back().fCurDataPayloadEntry = 0;
    fStack.back().fNumExpectedChildren = snippet->fNumChildren;
    fStack.back().fNumActualChildren = 0;
#endif
}

// Update the size byte of a block header
void PaintParamsKeyBuilder::endBlock() {
    if (!this->isValid()) {
        return;
    }

    if (fStack.empty()) {
        // SKGPU_LOG_W("Mismatched beginBlock/endBlocks.");
        this->makeInvalid();
        return;
    }

    // All the expected fields should be filled in at this point
    SkASSERT(fStack.back().fCurDataPayloadEntry ==
             SkTo<int>(fStack.back().fDataPayloadExpectations.size()));
    SkASSERT(fStack.back().fNumActualChildren == fStack.back().fNumExpectedChildren);
    SkASSERT(!this->isLocked());

    int headerOffset = fStack.back().fHeaderOffset;

#ifdef SK_DEBUG
    // We don't use `read_header` here because the header block size isn't valid yet.
    PaintParamsKey::Header header;
    memcpy(&header, &fData[headerOffset], sizeof(PaintParamsKey::Header));
    SkASSERT(header.codeSnippetID == fStack.back().fCodeSnippetID);
    SkASSERT(header.blockSize == 0);
#endif

    int blockSize = this->sizeInBytes() - headerOffset;
    if (blockSize > PaintParamsKey::kMaxBlockSize) {
        // SKGPU_LOG_W("Key's data payload is too large.");
        this->makeInvalid();
        return;
    }

    fData[headerOffset + PaintParamsKey::kBlockSizeOffsetInBytes] = blockSize;

    fStack.pop_back();

#ifdef SK_DEBUG
    if (!fStack.empty()) {
        // The children of a block should appear before any of the parent's data
        SkASSERT(fStack.back().fCurDataPayloadEntry == 0);
    }
#endif
}

#ifdef SK_DEBUG
void PaintParamsKeyBuilder::checkExpectations(DataPayloadType actualType, uint32_t actualCount) {
    StackFrame& frame = fStack.back();
    const auto& expectations = frame.fDataPayloadExpectations;

    // TODO: right now we reject writing 'n' bytes one at a time. We could allow it by tracking
    // the number of bytes written in the stack frame.
    SkASSERT(expectations[frame.fCurDataPayloadEntry].fType == actualType);
    SkASSERT(expectations[frame.fCurDataPayloadEntry].fCount == actualCount);

    frame.fCurDataPayloadEntry++;
}
#endif // SK_DEBUG

static int field_size(DataPayloadType type) {
    switch (type) {
        case DataPayloadType::kByte:   return 1;
        case DataPayloadType::kInt:    return 4;
        case DataPayloadType::kFloat4: return 16;
    }
    SkUNREACHABLE;
}

void PaintParamsKeyBuilder::addToKey(uint32_t count,
                                     const void* data,
                                     DataPayloadType payloadType) {
    if (!this->isValid()) {
        return;
    }

    if (fStack.empty()) {
        // SKGPU_LOG_W("Missing call to 'beginBlock'.");
        this->makeInvalid();
        return;
    }

    SkDEBUGCODE(this->checkExpectations(payloadType, count);)
    SkASSERT(!this->isLocked());

    fData.append(field_size(payloadType) * count, reinterpret_cast<const uint8_t*>(data));
}

void PaintParamsKeyBuilder::addBytes(uint32_t numBytes, const uint8_t* data) {
    this->addToKey(numBytes, data, DataPayloadType::kByte);
}

void PaintParamsKeyBuilder::addInts(uint32_t numInts, const int32_t* data) {
    this->addToKey(numInts, data, DataPayloadType::kInt);
}

void PaintParamsKeyBuilder::add(int numColors, const SkColor4f* colors) {
    this->addToKey(numColors, colors, DataPayloadType::kFloat4);
}

PaintParamsKey PaintParamsKeyBuilder::lockAsKey() {
    if (!fStack.empty()) {
        // SKGPU_LOG_W("Mismatched beginBlock/endBlocks.");
        this->makeInvalid();  // fall through
    }

    SkASSERT(!this->isLocked());

    // Partially reset for reuse. Note that the key resulting from this call will be holding a lock
    // on this builder and must be deleted before this builder is fully reset.
    fIsValid = true;
    fStack.clear();

    return PaintParamsKey(SkSpan(fData.begin(), fData.size()), this);
}

void PaintParamsKeyBuilder::makeInvalid() {
    SkASSERT(fIsValid);
    SkASSERT(!this->isLocked());

    fStack.clear();
    fData.clear();
    this->beginBlock(BuiltInCodeSnippetID::kError);
    this->endBlock();

    SkASSERT(fIsValid);
    fIsValid = false;
}

//--------------------------------------------------------------------------------------------------
PaintParamsKey::PaintParamsKey(SkSpan<const uint8_t> span,
                               PaintParamsKeyBuilder* originatingBuilder)
        : fData(span)
        , fOriginatingBuilder(originatingBuilder) {
    fOriginatingBuilder->lock();
}

PaintParamsKey::PaintParamsKey(SkSpan<const uint8_t> rawData)
        : fData(rawData)
        , fOriginatingBuilder(nullptr) {
}

PaintParamsKey::~PaintParamsKey() {
    if (fOriginatingBuilder) {
        fOriginatingBuilder->unlock();
    }
}

bool PaintParamsKey::operator==(const PaintParamsKey& that) const {
    return fData.size() == that.fData.size() &&
           !memcmp(fData.data(), that.fData.data(), fData.size());
}

PaintParamsKey::BlockReader PaintParamsKey::reader(const ShaderCodeDictionary* dict,
                                                   int headerOffset) const {
    return BlockReader(dict, fData, headerOffset);
}

#ifdef SK_DEBUG

// This just iterates over the top-level blocks calling block-specific dump methods.
void PaintParamsKey::dump(const ShaderCodeDictionary* dict) const {
    SkDebugf("--------------------------------------\n");
    SkDebugf("PaintParamsKey (%dB):\n", this->sizeInBytes());

    int curHeaderOffset = 0;
    while (curHeaderOffset < this->sizeInBytes()) {
        BlockReader reader = this->reader(dict, curHeaderOffset);
        reader.dump(dict, /*indent=*/0);
        curHeaderOffset += reader.blockSize();
    }
}
#endif // SK_DEBUG

void PaintParamsKey::AddBlockToShaderInfo(const ShaderCodeDictionary* dict,
                                          const PaintParamsKey::BlockReader& reader,
                                          ShaderInfo* result) {

    result->add(reader);
    result->addFlags(dict->getEntry(reader.codeSnippetId())->fSnippetRequirementFlags);

    // The child blocks appear right after the parent block's header in the key and go
    // right after the parent's SnippetEntry in the shader info
    for (int i = 0; i < reader.numChildren(); ++i) {
        BlockReader childReader = reader.child(dict, i);

        AddBlockToShaderInfo(dict, childReader, result);
    }
}

void PaintParamsKey::toShaderInfo(const ShaderCodeDictionary* dict,
                                  ShaderInfo* result) const {

    int curHeaderOffset = 0;
    while (curHeaderOffset < this->sizeInBytes()) {
        PaintParamsKey::BlockReader reader = this->reader(dict, curHeaderOffset);
        AddBlockToShaderInfo(dict, reader, result);
        curHeaderOffset += reader.blockSize();
    }
}

#if GR_TEST_UTILS
bool PaintParamsKey::isErrorKey() const {
    if (this->sizeInBytes() != sizeof(Header)) {
        return false;
    }
    Header header = read_header(this->asSpan(), /*headerOffset=*/0);
    return header.codeSnippetID == (int32_t) BuiltInCodeSnippetID::kError &&
           header.blockSize == sizeof(Header);
}
#endif

////////////////////////////////////////////////////////////////////////////////////////////////////

PaintParamsKey::BlockReader::BlockReader(const ShaderCodeDictionary* dict,
                                         SkSpan<const uint8_t> parentSpan,
                                         int offsetInParent) {
    Header header = read_header(parentSpan, offsetInParent);

    fBlock = parentSpan.subspan(offsetInParent, header.blockSize);
    fEntry = dict->getEntry(header.codeSnippetID);
    SkASSERT(fEntry);
}

int PaintParamsKey::BlockReader::numChildren() const { return fEntry->fNumChildren; }

PaintParamsKey::BlockReader PaintParamsKey::BlockReader::child(
        const ShaderCodeDictionary* dict,
        int childIndex) const {
    SkASSERT(childIndex < fEntry->fNumChildren);

    int childOffset = sizeof(Header);
    for (int i = 0; i < childIndex; ++i) {
        Header header = read_header(fBlock, childOffset);
        childOffset += header.blockSize;
    }

    return BlockReader(dict, fBlock, childOffset);
}

int32_t PaintParamsKey::BlockReader::codeSnippetId() const {
    Header header = read_header(fBlock, 0);
    return header.codeSnippetID;
}

SkSpan<const uint8_t> PaintParamsKey::BlockReader::dataPayload() const {
    int payloadOffset = sizeof(Header);
    for (int i = 0; i < fEntry->fNumChildren; ++i) {
        Header header = read_header(fBlock, payloadOffset);
        payloadOffset += header.blockSize;
    }

    int payloadSize = this->blockSize() - payloadOffset;
    return fBlock.subspan(payloadOffset, payloadSize);
}

static int field_offset(SkSpan<const DataPayloadField> fields, int fieldIndex) {
    int byteOffset = 0;
    for (int i = 0; i < fieldIndex; ++i) {
        byteOffset += field_size(fields[i].fType) * fields[i].fCount;
    }
    return byteOffset;
}

template <typename T>
static SkSpan<const T> payload_subspan_for_field(SkSpan<const uint8_t> dataPayload,
                                                 SkSpan<const DataPayloadField> fields,
                                                 int fieldIndex) {
    int offset = field_offset(fields, fieldIndex);
    return {reinterpret_cast<const T*>(&dataPayload[offset]), fields[fieldIndex].fCount};
}

SkSpan<const uint8_t> PaintParamsKey::BlockReader::bytes(int fieldIndex) const {
    SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kByte);
    return payload_subspan_for_field<uint8_t>(this->dataPayload(),
                                              fEntry->fDataPayloadExpectations,
                                              fieldIndex);
}

SkSpan<const int32_t> PaintParamsKey::BlockReader::ints(int fieldIndex) const {
    SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kInt);
    return payload_subspan_for_field<int32_t>(this->dataPayload(),
                                              fEntry->fDataPayloadExpectations,
                                              fieldIndex);
}

SkSpan<const SkColor4f> PaintParamsKey::BlockReader::colors(int fieldIndex) const {
    SkASSERT(fEntry->fDataPayloadExpectations[fieldIndex].fType == DataPayloadType::kFloat4);
    return payload_subspan_for_field<SkColor4f>(this->dataPayload(),
                                                fEntry->fDataPayloadExpectations,
                                                fieldIndex);
}

#ifdef SK_DEBUG

int PaintParamsKey::BlockReader::numDataPayloadFields() const {
    return fEntry->fDataPayloadExpectations.size();
}

static void output_indent(int indent) {
    SkDebugf("%*c", 4 * indent, ' ');
}

void PaintParamsKey::BlockReader::dump(const ShaderCodeDictionary* dict, int indent) const {
    uint8_t id = static_cast<uint8_t>(this->codeSnippetId());
    uint8_t blockSize = this->blockSize();

    auto entry = dict->getEntry(id);
    if (!entry) {
        output_indent(indent);
        SkDebugf("unknown block! (%dB)\n", blockSize);
    }

    output_indent(indent);
    SkDebugf("%s block (%dB)\n", entry->fStaticFunctionName, blockSize);

    for (int i = 0; i < this->numChildren(); ++i) {
        output_indent(indent);
        // TODO: it would be nice if the names of the children were also stored (i.e., "src"/"dst")
        SkDebugf("child %d:\n", i);

        PaintParamsKey::BlockReader childReader = this->child(dict, i);
        childReader.dump(dict, indent+1);
    }

    for (int i = 0; i < (int) fEntry->fDataPayloadExpectations.size(); ++i) {
        output_indent(indent);
        SkDebugf("%s[%d]: ",
                 fEntry->fDataPayloadExpectations[i].fName,
                 fEntry->fDataPayloadExpectations[i].fCount);
        SkSpan<const uint8_t> bytes = this->bytes(i);
        for (uint8_t b : bytes) {
            SkDebugf("%d,", b);
        }

        SkDebugf("\n");
    }
}

#endif // SK_DEBUG

} // namespace skgpu::graphite
