/*
 * 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/SkDocument.h"
#include "include/core/SkFont.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/core/SkRecord.h"
#include "src/core/SkRecorder.h"
#include "src/utils/SkMultiPictureDocument.h"
#include "tests/Test.h"
#include "tools/SkSharingProc.h"

namespace {

class RecordVisitor {
// An SkRecord visitor that remembers the name of the last visited command.
public:
    SkString name;

    explicit RecordVisitor() {}

    template <typename T>
    void operator()(const T& command) {
        name = SkString(NameOf(command));
    }

    SkString lastCommandName() {
        return name;
    }
private:
    template <typename T>
    static const char* NameOf(const T&) {
        #define CASE(U) case SkRecords::U##_Type: return #U;
        switch (T::kType) { SK_RECORD_TYPES(CASE) }
        #undef CASE
        return "Unknown T";
    }
};
} // namespace

// Compare record tested with record expected. Assert op sequence is the same (comparing types)
// frame_num is only used for error message.
static void compareRecords(const SkRecord& tested, const SkRecord& expected,
    int frame_num, skiatest::Reporter* reporter) {
    REPORTER_ASSERT(reporter, tested.count() == expected.count(),
        "Found %d commands in frame %d, expected %d", tested.count(), frame_num, expected.count());

    RecordVisitor rv;
    for (int i = 0; i < tested.count(); i++) {
        tested.visit(i, rv);
        const SkString testCommandName = rv.lastCommandName();
        expected.visit(i, rv);
        const SkString expectedCommandName = rv.lastCommandName();
        REPORTER_ASSERT(reporter, testCommandName == expectedCommandName,
            "Unexpected command type '%s' in frame %d, op %d. Expected '%s'",
            testCommandName.c_str(), frame_num, i, expectedCommandName.c_str());
    }
}

// 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(Serialize_and_deserialize_multi_skp, 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();

    // Create frames, recording them to multipic.
    SkRecord expectedRecords[NUM_FRAMES];
    for (int i=0; i<NUM_FRAMES; i++) {
        SkCanvas* pictureCanvas = multipic->beginPage(WIDTH, HEIGHT);
        draw_advanced(pictureCanvas, i, image, sub);
        multipic->endPage();
        // Also record the same commands to separate SkRecords for later comparison
        SkRecorder canvas(&expectedRecords[i], WIDTH, HEIGHT);
        draw_advanced(&canvas, i, image, sub);
    }
    // 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.
    SkRecorder resultRecorder(nullptr, 1, 1);
    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());
        // confirm contents of picture match what we drew.
        // There are several ways of doing this, an ideal comparison would not break in the same
        // way at the same time as the code under test (no serialization), and would involve only
        // minimal transformation of frame.fPicture, minimizing the chance that a detected fault lies
        // in the test itself. The comparions also would not be an overly sensitive change detector,
        // so that it doesn't break every time someone submits code (no golden file)

        // Extract the SkRecord from the deserialized picture using playback (instead of a mess of
        // friend classes to grab the private record inside frame.fPicture
        SkRecord record;
        // This picture mode is necessary so that we record the command contents of frame.fPicture
        // not just a 'DrawPicture' command, but don't record pictures within it. We want to assert
        // that the code under test reffed them like it should have.
        resultRecorder.reset(&record, bounds, SkRecorder::PlaybackTop_DrawPictureMode, nullptr);
        frame.fPicture->playback(&resultRecorder);
        // Compare the record to the expected one
        compareRecords(record, expectedRecords[i], i, reporter);
        i++;
    }
}
