/*
 * Copyright 2013 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * This test confirms that a MultiPictureDocument can be serialized and deserailzied without error.
 * And that the pictures within it are re-created accurately
 */

#include "include/core/SkCanvas.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkDocument.h"
#include "include/core/SkFont.h"
#include "include/core/SkImage.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTextBlob.h"
#include "src/utils/SkMultiPictureDocument.h"
#include "tests/Test.h"
#include "tools/SkSharingProc.h"
#include "tools/ToolUtils.h"

// Covers rects, ovals, paths, images, text
static void draw_basic(SkCanvas* canvas, int seed, sk_sp<SkImage> image) {
    canvas->drawColor(SK_ColorWHITE);

    SkPaint paint;
    paint.setStyle(SkPaint::kStroke_Style);
    paint.setStrokeWidth(seed);
    paint.setColor(SK_ColorRED);

    SkRect rect = SkRect::MakeXYWH(50+seed, 50+seed, 4*seed, 60);
    canvas->drawRect(rect, paint);

    SkRRect oval;
    oval.setOval(rect);
    oval.offset(40, 60+seed);
    paint.setColor(SK_ColorBLUE);
    canvas->drawRRect(oval, paint);

    paint.setColor(SK_ColorCYAN);
    canvas->drawCircle(180, 50, 5*seed, paint);

    rect.offset(80, 0);
    paint.setColor(SK_ColorYELLOW);
    canvas->drawRoundRect(rect, 10, 10, paint);

    SkPath path;
    path.cubicTo(768, 0, -512, 256, 256, 256);
    paint.setColor(SK_ColorGREEN);
    canvas->drawPath(path, paint);

    canvas->drawImage(image, 128-seed, 128, &paint);

    if (seed % 2 == 0) {
        SkRect rect2 = SkRect::MakeXYWH(0, 0, 40, 60);
        canvas->drawImageRect(image, rect2, &paint);
    }

    SkPaint paint2;
    auto text = SkTextBlob::MakeFromString(
        SkStringPrintf("Frame %d", seed).c_str(), SkFont(nullptr, 2+seed));
    canvas->drawTextBlob(text.get(), 50, 25, paint2);
}

// Covers all of the above and drawing nested sub-pictures.
static void draw_advanced(SkCanvas* canvas, int seed, sk_sp<SkImage> image, sk_sp<SkPicture> sub) {
    draw_basic(canvas, seed, image);

    // Use subpicture twice in different places
    canvas->drawPicture(sub);
    canvas->save();
    canvas->translate(seed, seed);
    canvas->drawPicture(sub);
    canvas->restore();
}

// Test serialization and deserialization of multi picture document
DEF_TEST(SkMultiPictureDocument_Serialize_and_deserialize, reporter) {
    // Create the stream we will serialize into.
    SkDynamicMemoryWStream stream;

    // Create the image sharing proc.
    SkSharingSerialContext ctx;
    SkSerialProcs procs;
    procs.fImageProc = SkSharingSerialContext::serializeImage;
    procs.fImageCtx = &ctx;

    // Create the multi picture document used for recording frames.
    sk_sp<SkDocument> multipic = SkMakeMultiPictureDocument(&stream, &procs);

    static const int NUM_FRAMES = 12;
    static const int WIDTH = 256;
    static const int HEIGHT = 256;

    // Make an image to be used in a later step.
    auto surface(SkSurface::MakeRasterN32Premul(100, 100));
    surface->getCanvas()->clear(SK_ColorGREEN);
    sk_sp<SkImage> image(surface->makeImageSnapshot());
    REPORTER_ASSERT(reporter, image);

    // Make a subpicture to be used in a later step
    SkPictureRecorder pr;
    SkCanvas* subCanvas = pr.beginRecording(100, 100);
    draw_basic(subCanvas, 42, image);
    sk_sp<SkPicture> sub = pr.finishRecordingAsPicture();

    const SkImageInfo info = SkImageInfo::MakeN32Premul(WIDTH, HEIGHT);
    std::vector<sk_sp<SkImage>> expectedImages;

    for (int i=0; i<NUM_FRAMES; i++) {
        SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT);
        draw_advanced(pictureCanvas, i, image, sub);
        multipic->endPage();
        // Also draw the picture to an image for later comparison
        auto surf = SkSurface::MakeRaster(info);
        draw_advanced(surf->getCanvas(), i, image, sub);
        expectedImages.push_back(surf->makeImageSnapshot());
    }
    // Finalize
    multipic->close();

    // Confirm written data is at least as large as the magic word
    std::unique_ptr<SkStreamAsset> writtenStream = stream.detachAsStream();
    REPORTER_ASSERT(reporter, writtenStream->getLength() > 24,
        "Written data length too short (%zu)", writtenStream->getLength());
    // SkDebugf("Multi Frame file size = %zu\n", writtenStream->getLength());

    // Set up deserialization
    SkSharingDeserialContext deserialContext;
    SkDeserialProcs dprocs;
    dprocs.fImageProc = SkSharingDeserialContext::deserializeImage;
    dprocs.fImageCtx = &deserialContext;

    // Confirm data is a MultiPictureDocument
    int frame_count = SkMultiPictureDocumentReadPageCount(writtenStream.get());
    REPORTER_ASSERT(reporter, frame_count == NUM_FRAMES,
        "Expected %d frames, got %d. \n 0 frames may indicate the written file was not a "
        "MultiPictureDocument.", NUM_FRAMES, frame_count);

    // Deserailize
    std::vector<SkDocumentPage> frames(frame_count);
    REPORTER_ASSERT(reporter,
        SkMultiPictureDocumentRead(writtenStream.get(), frames.data(), frame_count, &dprocs),
        "Failed while reading MultiPictureDocument");

    // Examine each frame.
    int i=0;
    for (const auto& frame : frames) {
        SkRect bounds = frame.fPicture->cullRect();
        REPORTER_ASSERT(reporter, bounds.width() == WIDTH,
            "Page width: expected (%d) got (%d)", WIDTH, (int)bounds.width());
        REPORTER_ASSERT(reporter, bounds.height() == HEIGHT,
            "Page height: expected (%d) got (%d)", HEIGHT, (int)bounds.height());

        auto surf = SkSurface::MakeRaster(info);
        surf->getCanvas()->drawPicture(frame.fPicture);
        auto img = surf->makeImageSnapshot();
        REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img.get(), expectedImages[i].get()));

        i++;
    }
}


#if SK_SUPPORT_GPU && defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26

#include "include/gpu/GrDirectContext.h"
#include "src/gpu/GrAHardwareBufferUtils.h"
#include "src/gpu/GrDirectContextPriv.h"

#include <android/hardware_buffer.h>

static const int DEV_W = 16, DEV_H = 16;

static SkPMColor get_src_color(int x, int y) {
    SkASSERT(x >= 0 && x < DEV_W);
    SkASSERT(y >= 0 && y < DEV_H);

    U8CPU r = x;
    U8CPU g = y;
    U8CPU b = 0xc;

    U8CPU a = 0xff;
    switch ((x+y) % 5) {
        case 0:
            a = 0xff;
            break;
        case 1:
            a = 0x80;
            break;
        case 2:
            a = 0xCC;
            break;
        case 4:
            a = 0x01;
            break;
        case 3:
            a = 0x00;
            break;
    }
    a = 0xff;
    return SkPremultiplyARGBInline(a, r, g, b);
}

static SkBitmap make_src_bitmap() {
    static SkBitmap bmp;
    if (bmp.isNull()) {
        bmp.allocN32Pixels(DEV_W, DEV_H);
        intptr_t pixels = reinterpret_cast<intptr_t>(bmp.getPixels());
        for (int y = 0; y < DEV_H; ++y) {
            for (int x = 0; x < DEV_W; ++x) {
                SkPMColor* pixel = reinterpret_cast<SkPMColor*>(
                        pixels + y * bmp.rowBytes() + x * bmp.bytesPerPixel());
                *pixel = get_src_color(x, y);
            }
        }
    }
    return bmp;
}

static void cleanup_resources(AHardwareBuffer* buffer) {
    if (buffer) {
        AHardwareBuffer_release(buffer);
    }
}

static sk_sp<SkImage> makeAHardwareBufferTestImage(
    skiatest::Reporter* reporter, GrDirectContext* context, AHardwareBuffer* buffer) {

    const SkBitmap srcBitmap = make_src_bitmap();

    AHardwareBuffer_Desc hwbDesc;
    hwbDesc.width = DEV_W;
    hwbDesc.height = DEV_H;
    hwbDesc.layers = 1;
    hwbDesc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
                    AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
                    AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
    hwbDesc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
    // The following three are not used in the allocate
    hwbDesc.stride = 0;
    hwbDesc.rfu0= 0;
    hwbDesc.rfu1= 0;

    if (int error = AHardwareBuffer_allocate(&hwbDesc, &buffer)) {
        ERRORF(reporter, "Failed to allocated hardware buffer, error: %d", error);
        cleanup_resources(buffer);
        return nullptr;
    }

    // Get actual desc for allocated buffer so we know the stride for uploading cpu data.
    AHardwareBuffer_describe(buffer, &hwbDesc);

    void* bufferAddr;
    if (AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, nullptr,
                             &bufferAddr)) {
        ERRORF(reporter, "Failed to lock hardware buffer");
        cleanup_resources(buffer);
        return nullptr;
    }

    // fill buffer
    int bbp = srcBitmap.bytesPerPixel();
    uint32_t* src = (uint32_t*)srcBitmap.getPixels();
    int nextLineStep = DEV_W;
    uint32_t* dst = static_cast<uint32_t*>(bufferAddr);
    for (int y = 0; y < DEV_H; ++y) {
        memcpy(dst, src, DEV_W * bbp);
        src += nextLineStep;
        dst += hwbDesc.stride;
    }
    AHardwareBuffer_unlock(buffer, nullptr);

    // Make SkImage from buffer in a way that mimics libs/hwui/AutoBackendTextureRelease
    GrBackendFormat backendFormat =
            GrAHardwareBufferUtils::GetBackendFormat(context, buffer, hwbDesc.format, false);
    GrAHardwareBufferUtils::DeleteImageProc deleteProc;
    GrAHardwareBufferUtils::UpdateImageProc updateProc;
    GrAHardwareBufferUtils::TexImageCtx imageCtx;
    GrBackendTexture texture = GrAHardwareBufferUtils::MakeBackendTexture(
        context, buffer, hwbDesc.width, hwbDesc.height,
        &deleteProc, // set by MakeBackendTexture
        &updateProc, // set by MakeBackendTexture
        &imageCtx, // set by MakeBackendTexture
        false,   // don't make protected image
        backendFormat,
        false   // isRenderable
    );
    SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(hwbDesc.format);
    sk_sp<SkImage> image = SkImage::MakeFromTexture(
        context, texture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
        SkColorSpace::MakeSRGB(),
        nullptr, // no release proc
        nullptr // context for release proc
    );

    REPORTER_ASSERT(reporter, image);
    REPORTER_ASSERT(reporter, image->isTextureBacked());
    return image;
}

// Test the onEndPage callback's intended use by processing an mskp containing AHardwareBuffer-backed SkImages
// Expected behavior is that the callback is called while the AHardwareBuffer is still valid and the
// images are copied so .close() can still access them.
// Confirm deserialized file contains images with correct data.
DEF_GPUTEST_FOR_RENDERING_CONTEXTS(SkMultiPictureDocument_AHardwarebuffer,
                                   reporter, ctx_info) {
    auto context = ctx_info.directContext();
    if (!context->priv().caps()->supportsAHardwareBufferImages()) {
        return;
    }

    // Create the stream we will serialize into.
    SkDynamicMemoryWStream stream;

    // Create the image sharing proc.
    SkSharingSerialContext ctx;
    SkSerialProcs procs;
    procs.fImageProc = SkSharingSerialContext::serializeImage;
    procs.fImageCtx = &ctx;

    // Create the multi picture document used for recording frames.
    // Pass a lambda as the onEndPage callback that captures our sharing context
    sk_sp<SkDocument> multipic = SkMakeMultiPictureDocument(&stream, &procs,
        [sharingCtx = &ctx](const SkPicture* pic) {
            SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
        });

    static const int WIDTH = 256;
    static const int HEIGHT = 256;

    // Make an image to be used in a later step.
    AHardwareBuffer* ahbuffer = nullptr;
    sk_sp<SkImage> image = makeAHardwareBufferTestImage(reporter, context, ahbuffer);

    const SkImageInfo info = SkImageInfo::MakeN32Premul(WIDTH, HEIGHT);
    std::vector<sk_sp<SkImage>> expectedImages;

    // Record single frame
    SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT);
    draw_basic(pictureCanvas, 0, image);
    multipic->endPage();
    // Also draw the picture to an image for later comparison
    auto surf = SkSurface::MakeRaster(info);
    draw_basic(surf->getCanvas(), 0, image);
    expectedImages.push_back(surf->makeImageSnapshot());

    // Release Ahardwarebuffer. If the code under test has not copied it already,
    // close() will fail.
    // Note that this only works because we're doing one frame only. If this test were recording
    // two or more frames, it would have change the buffer contents instead.
    cleanup_resources(ahbuffer);

    // Finalize
    multipic->close();

    // Confirm written data is at least as large as the magic word
    std::unique_ptr<SkStreamAsset> writtenStream = stream.detachAsStream();
    REPORTER_ASSERT(reporter, writtenStream->getLength() > 24,
        "Written data length too short (%zu)", writtenStream->getLength());

    // Set up deserialization
    SkSharingDeserialContext deserialContext;
    SkDeserialProcs dprocs;
    dprocs.fImageProc = SkSharingDeserialContext::deserializeImage;
    dprocs.fImageCtx = &deserialContext;

    // Confirm data is a MultiPictureDocument
    int frame_count = SkMultiPictureDocumentReadPageCount(writtenStream.get());
    REPORTER_ASSERT(reporter, frame_count == 1,
        "Expected 1 frame, got %d. \n 0 frames may indicate the written file was not a "
        "MultiPictureDocument.", frame_count);

    // Deserialize
    std::vector<SkDocumentPage> frames(frame_count);
    REPORTER_ASSERT(reporter,
        SkMultiPictureDocumentRead(writtenStream.get(), frames.data(), frame_count, &dprocs),
        "Failed while reading MultiPictureDocument");

    // Examine frame.
    SkRect bounds = frames[0].fPicture->cullRect();
    REPORTER_ASSERT(reporter, bounds.width() == WIDTH,
        "Page width: expected (%d) got (%d)", WIDTH, (int)bounds.width());
    REPORTER_ASSERT(reporter, bounds.height() == HEIGHT,
        "Page height: expected (%d) got (%d)", HEIGHT, (int)bounds.height());

    auto surf2 = SkSurface::MakeRaster(info);
    surf2->getCanvas()->drawPicture(frames[0].fPicture);
    auto img = surf2->makeImageSnapshot();
    REPORTER_ASSERT(reporter, ToolUtils::equal_pixels(img.get(), expectedImages[0].get()));
}

#endif // android compilation
