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

#include "include/core/SkData.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkRRect.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/private/SkPathRef.h"
#include "include/private/base/SkAssert.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkPoint_impl.h"
#include "include/private/base/SkTPin.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkAutoMalloc.h"
#include "src/base/SkBuffer.h"
#include "src/base/SkSafeMath.h"
#include "src/core/SkPathEnums.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkRRectPriv.h"

#include <cstddef>
#include <cstdint>
#include <optional>

enum SerializationOffsets {
    kType_SerializationShift = 28,       // requires 4 bits
    kDirection_SerializationShift = 26,  // requires 2 bits
    kFillType_SerializationShift = 8,    // requires 8 bits
    // low-8-bits are version
    kVersion_SerializationMask = 0xFF,
};

enum SerializationVersions {
    // kPathPrivFirstDirection_Version = 1,
    // kPathPrivLastMoveToIndex_Version = 2,
    // kPathPrivTypeEnumVersion = 3,
    kJustPublicData_Version = 4,            // introduced Feb/2018
    kVerbsAreStoredForward_Version = 5,     // introduced Sept/2019

    kMin_Version     = kJustPublicData_Version,
    kCurrent_Version = kVerbsAreStoredForward_Version
};

enum SerializationType {
    kGeneral = 0,
    kRRect = 1
};

static unsigned extract_version(uint32_t packed) {
    return packed & kVersion_SerializationMask;
}

static SkPathFillType extract_filltype(uint32_t packed) {
    return static_cast<SkPathFillType>((packed >> kFillType_SerializationShift) & 0x3);
}

static SerializationType extract_serializationtype(uint32_t packed) {
    return static_cast<SerializationType>((packed >> kType_SerializationShift) & 0xF);
}

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

size_t SkPath::writeToMemoryAsRRect(void* storage) const {
    SkRRect rrect;
    SkPathDirection firstDir;
    unsigned start;

    if (auto oinfo = this->getOvalInfo()) {
        rrect.setOval(oinfo->fBounds);
        firstDir = oinfo->fDirection;
        // Convert to rrect start indices.
        start = oinfo->fStartIndex * 2;
    } else if (auto rinfo = this->getRRectInfo()) {
        rrect = rinfo->fRRect;
        firstDir = rinfo->fDirection;
        start = rinfo->fStartIndex;
    } else {
        return 0;
    }

    // packed header, rrect, start index.
    const size_t sizeNeeded = sizeof(int32_t) + SkRRect::kSizeInMemory + sizeof(int32_t);
    if (!storage) {
        return sizeNeeded;
    }

    int32_t packed = (static_cast<int>(fFillType) << kFillType_SerializationShift) |
                     ((int)firstDir << kDirection_SerializationShift) |
                     (SerializationType::kRRect << kType_SerializationShift) |
                     kCurrent_Version;

    SkWBuffer buffer(storage);
    buffer.write32(packed);
    SkRRectPriv::WriteToBuffer(rrect, &buffer);
    buffer.write32(SkToS32(start));
    buffer.padToAlign4();
    SkASSERT(sizeNeeded == buffer.pos());
    return buffer.pos();
}

size_t SkPath::writeToMemory(void* storage) const {
    SkDEBUGCODE(this->validate();)

    if (size_t bytes = this->writeToMemoryAsRRect(storage)) {
        return bytes;
    }

    int32_t packed = (static_cast<int>(fFillType) << kFillType_SerializationShift) |
                     (SerializationType::kGeneral << kType_SerializationShift) |
                     kCurrent_Version;

    SkSpan<const SkPoint> points = this->points();
    SkSpan<const SkPathVerb> verbs = this->verbs();
    SkSpan<const float> conics = this->conicWeights();

    int32_t pts = SkToS32(points.size());
    int32_t cnx = SkToS32(conics.size());
    int32_t vbs = SkToS32(verbs.size());

    SkSafeMath safe;
    size_t size = 4 * sizeof(int32_t);
    size = safe.add(size, safe.mul(pts, sizeof(SkPoint)));
    size = safe.add(size, safe.mul(cnx, sizeof(float)));
    size = safe.add(size, safe.mul(vbs, sizeof(uint8_t)));
    size = safe.alignUp(size, 4);
    if (!safe) {
        return 0;
    }
    if (!storage) {
        return size;
    }

    SkWBuffer buffer(storage);
    buffer.write32(packed);
    buffer.write32(pts);
    buffer.write32(cnx);
    buffer.write32(vbs);
    buffer.write(points.data(), points.size_bytes());
    buffer.write(conics.data(), conics.size_bytes());
    buffer.write(verbs.data(), verbs.size_bytes());
    buffer.padToAlign4();

    SkASSERT(buffer.pos() == size);
    return size;
}

sk_sp<SkData> SkPath::serialize() const {
    size_t size = this->writeToMemory(nullptr);
    sk_sp<SkData> data = SkData::MakeUninitialized(size);
    this->writeToMemory(data->writable_data());
    return data;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// reading

std::optional<SkPath> read_rrect_path(const void* storage, size_t length, size_t* bytesRead) {
    SkASSERT(bytesRead);
    SkRBuffer buffer(storage, length);
    uint32_t packed;
    if (!buffer.readU32(&packed)) {
        *bytesRead = 0;
        return {};
    }

    SkASSERT(extract_serializationtype(packed) == SerializationType::kRRect);

    uint8_t dir = (packed >> kDirection_SerializationShift) & 0x3;
    SkPathFillType fillType = extract_filltype(packed);

    SkPathDirection rrectDir;
    switch (dir) {
        case (int)SkPathFirstDirection::kCW:
            rrectDir = SkPathDirection::kCW;
            break;
        case (int)SkPathFirstDirection::kCCW:
            rrectDir = SkPathDirection::kCCW;
            break;
        default:
            *bytesRead = 0;
            return {};
    }

    SkRRect rrect;
    if (!SkRRectPriv::ReadFromBuffer(&buffer, &rrect)) {
        *bytesRead = 0;
        return {};
    }

    int32_t start;
    if (!buffer.readS32(&start) || start != SkTPin(start, 0, 7)) {
        *bytesRead = 0;
        return {};
    }

    SkPath path = SkPath::RRect(rrect, rrectDir, SkToUInt(start));
    path.setFillType(fillType);
    buffer.skipToAlign4();
    *bytesRead = buffer.pos();
    return path;
}

std::optional<SkPath> SkPath::ReadFromMemory(const void* storage, size_t length, size_t* bytesRead) {
    size_t bytesStorage = 0;
    if (!bytesRead) {
        bytesRead = &bytesStorage;
    }
    SkRBuffer buffer(storage, length);
    uint32_t packed;
    if (!buffer.readU32(&packed)) {
        *bytesRead = 0;
        return {};
    }
    unsigned version = extract_version(packed);

    const bool verbsAreForward = (version == kVerbsAreStoredForward_Version);
    if (!verbsAreForward && version != kJustPublicData_Version) SK_UNLIKELY {
        // Old/unsupported version.
        *bytesRead = 0;
        return {};
    }

    switch (extract_serializationtype(packed)) {
        case SerializationType::kRRect:
            return read_rrect_path(storage, length, bytesRead);
        case SerializationType::kGeneral:
            break;  // fall out
        default:
            *bytesRead = 0;
            return {};
    }

    // To minimize the number of reads done a structure with the counts is used.
    struct {
      uint32_t pts, cnx, vbs;
    } counts;
    if (!buffer.read(&counts, sizeof(counts))) {
        *bytesRead = 0;
        return {};
    }

    const SkPoint* points = buffer.skipCount<SkPoint>(counts.pts);
    const SkScalar* conics = buffer.skipCount<SkScalar>(counts.cnx);
    const SkPathVerb* verbs = buffer.skipCount<SkPathVerb>(counts.vbs);
    buffer.skipToAlign4();
    if (!buffer.isValid()) {
        *bytesRead = 0;
        return {};
    }
    SkASSERT(buffer.pos() <= length);

    if (counts.vbs == 0) {
        if (counts.pts == 0 && counts.cnx == 0) {
            SkPath path(extract_filltype(packed));
            *bytesRead = buffer.pos();
            return path;
        }
        // No verbs but points and/or conic weights is a not a valid path.
        *bytesRead = 0;
        return {};
    }

    SkAutoMalloc reversedStorage;
    if (!verbsAreForward) SK_UNLIKELY {
        SkPathVerb* tmpVerbs = (SkPathVerb*)reversedStorage.reset(counts.vbs);
        for (unsigned i = 0; i < counts.vbs; ++i) {
            tmpVerbs[i] = verbs[counts.vbs - i - 1];
        }
        verbs = tmpVerbs;
    }

    *bytesRead = buffer.pos();
    return SkPath::Raw({points, counts.pts},
                       {verbs, counts.vbs},
                       {conics, counts.cnx},
                       extract_filltype(packed),
                       false);
}
