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

#include "src/text/gpu/SlugImpl.h"

#include "include/core/SkMatrix.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkSerialProcs.h"
#include "include/private/base/SkAssert.h"
#include "include/private/chromium/Slug.h"
#include "src/core/SkDevice.h"
#include "src/core/SkPaintPriv.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkWriteBuffer.h"
#include "src/text/GlyphRun.h"
#include "src/text/gpu/SubRunAllocator.h"
#include "src/text/gpu/SubRunContainer.h"

#include <memory>
#include <utility>

class SkStrikeClient;

namespace sktext::gpu {

SlugImpl::SlugImpl(SubRunAllocator&& alloc,
                   gpu::SubRunContainerOwner subRuns,
                   SkRect sourceBounds,
                   const SkPaint& paint,
                   SkPoint origin)
        : fAlloc {std::move(alloc)}
        , fSubRuns(std::move(subRuns))
        , fSourceBounds{sourceBounds}
        , fInitialPaint{paint}
        , fOrigin{origin} {}

void SlugImpl::doFlatten(SkWriteBuffer& buffer) const {
    buffer.writeRect(fSourceBounds);
    SkPaintPriv::Flatten(fInitialPaint, buffer);
    buffer.writePoint(fOrigin);
    fSubRuns->flattenAllocSizeHint(buffer);
    fSubRuns->flattenRuns(buffer);
}

sk_sp<Slug> SlugImpl::MakeFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
    SkRect sourceBounds = buffer.readRect();
    if (!buffer.validate(!sourceBounds.isEmpty())) { return nullptr; }
    SkPaint paint = buffer.readPaint();
    SkPoint origin = buffer.readPoint();
    int allocSizeHint = gpu::SubRunContainer::AllocSizeHintFromBuffer(buffer);

    auto [initializer, _, alloc] =
            SubRunAllocator::AllocateClassMemoryAndArena<SlugImpl>(allocSizeHint);

    gpu::SubRunContainerOwner container = gpu::SubRunContainer::MakeFromBufferInAlloc(buffer, client, &alloc);

    // Something went wrong while reading.
    SkASSERT(buffer.isValid());
    if (!buffer.isValid()) { return nullptr;}

    return sk_sp<SlugImpl>(initializer.initialize(
            std::move(alloc), std::move(container), sourceBounds, paint, origin));
}

SkMatrix position_matrix(const SkMatrix& drawMatrix, SkPoint drawOrigin) {
    SkMatrix position_matrix = drawMatrix;
    return position_matrix.preTranslate(drawOrigin.x(), drawOrigin.y());
}

sk_sp<SlugImpl> SlugImpl::Make(const SkMatrix& viewMatrix,
                               const sktext::GlyphRunList& glyphRunList,
                               const SkPaint& initialPaint,
                               const SkPaint& drawingPaint,
                               SkStrikeDeviceInfo strikeDeviceInfo,
                               sktext::StrikeForGPUCacheInterface* strikeCache) {
    size_t subRunSizeHint = gpu::SubRunContainer::EstimateAllocSize(glyphRunList);
    auto [initializer, _, alloc] =
            SubRunAllocator::AllocateClassMemoryAndArena<SlugImpl>(subRunSizeHint);

    const SkMatrix positionMatrix = position_matrix(viewMatrix, glyphRunList.origin());

    auto subRuns = gpu::SubRunContainer::MakeInAlloc(glyphRunList,
                                                     positionMatrix,
                                                     drawingPaint,
                                                     strikeDeviceInfo,
                                                     strikeCache,
                                                     &alloc,
                                                     gpu::SubRunContainer::kAddSubRuns,
                                                     "Make Slug");

    sk_sp<SlugImpl> slug = sk_sp<SlugImpl>(initializer.initialize(std::move(alloc),
                                                                  std::move(subRuns),
                                                                  glyphRunList.sourceBounds(),
                                                                  initialPaint,
                                                                  glyphRunList.origin()));

    // There is nothing to draw here. This is particularly a problem with RSX form blobs where a
    // single space becomes a run with no glyphs.
    if (slug->fSubRuns->isEmpty()) { return nullptr; }

    return slug;
}

void Slug::AddDeserialProcs(SkDeserialProcs* procs, const SkStrikeClient* client) {
    SkASSERT(procs);
    procs->fSlugCtx = const_cast<SkStrikeClient*>(client);
    procs->fSlugProc = [](SkReadBuffer& buffer, void* ctx) -> sk_sp<Slug> {
        auto client = static_cast<const SkStrikeClient*>(ctx);
        return SlugImpl::MakeFromBuffer(buffer, client);
    };
}

#if !defined(SK_SLUG_DISABLE_LEGACY_DESERIALIZE)
sk_sp<Slug> SkMakeSlugFromBuffer(SkReadBuffer& buffer, const SkStrikeClient* client) {
    return SlugImpl::MakeFromBuffer(buffer, client);
}
#endif

}  // namespace sktext::gpu
