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

#include "gm/verifiers/gmverifier.h"

#include "gm/gm.h"
#include "include/core/SkAlphaType.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkColorType.h"
#include "include/core/SkImage.h" // IWYU pragma: keep
#include "include/core/SkImageInfo.h"
#include "include/core/SkSize.h"
#include "include/private/base/SkAssert.h"

#include <utility>

/** Checks the given VerifierResult. If it is not ok, returns it. */
#define RETURN_NOT_OK(res)  if (!(res).ok()) return (res)

namespace skiagm {
namespace verifiers {

VerifierResult::VerifierResult() : VerifierResult(Code::kOk, SkString("Ok")) {}

VerifierResult::VerifierResult(VerifierResult::Code code, const SkString& msg) :
    fCode(code), fMessage(msg) {}

bool VerifierResult::ok() const {
    return fCode == Code::kOk;
}

const SkString& VerifierResult::message() const {
    return fMessage;
}

VerifierResult VerifierResult::Ok() {
    return VerifierResult(Code::kOk, SkString("Ok"));
}

VerifierResult VerifierResult::Fail(const SkString& msg) {
    return VerifierResult(Code::kFail, msg);
}

GMVerifier::GMVerifier(InputType inputType) : fInputType(inputType) {}

GMVerifier::~GMVerifier() {}

bool GMVerifier::needsGoldImage() const {
    return fInputType == InputType::kGoldImageRequired;
}

VerifierResult GMVerifier::verify(const SkBitmap& gold, const SkBitmap& actual) {
    // Call into specific implementation.
    return verifyWithGold(actual.bounds(), gold, actual);
}

VerifierResult GMVerifier::verify(const SkBitmap& actual) {
    // Call into specific implementation.
    return verify(actual.bounds(), actual);
}

SkBitmap GMVerifier::RenderGoldBmp(skiagm::GM* gm, const SkColorInfo& colorInfo) {
    SkASSERT(gm);

    // Call into the GM instance to get the initial image.
    const SkISize size = gm->getISize();
    SkBitmap goldBmp;
    goldBmp.allocPixels(SkImageInfo::Make(size, colorInfo));
    SkCanvas canvas(goldBmp);

    if (gm->gpuSetup(&canvas) == DrawResult::kOk) {
        gm->draw(&canvas);
    }

    // Convert into common verifier colorspace.
    SkBitmap goldVerifierBmp;
    goldVerifierBmp.allocPixels(SkImageInfo::Make(size, VerifierColorInfo()));
    SkCanvas verifierCanvas(goldVerifierBmp);
    verifierCanvas.drawImage(goldBmp.asImage(), 0, 0);

    return goldVerifierBmp;
}

SkColorInfo GMVerifier::VerifierColorInfo() {
    return SkColorInfo(
        kRGBA_F16_SkColorType, kPremul_SkAlphaType,
        SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, SkNamedGamut::kRec2020));
}

VerifierResult GMVerifier::makeError(const SkString& msg) const {
    return VerifierResult::Fail(SkStringPrintf("[%s] %s", name().c_str(), msg.c_str()));
}

VerifierList::VerifierList(GM* gm) : fGM(gm), fFailedVerifier(nullptr) {}

void VerifierList::add(std::unique_ptr<GMVerifier> verifier) {
    fVerifiers.push_back(std::move(verifier));
}

bool VerifierList::needsGoldImage() const {
    for (const auto& v : fVerifiers) {
        if (v->needsGoldImage()) {
            return true;
        }
    }

    return false;
}

VerifierResult VerifierList::verifyAll(const SkColorInfo& colorInfo, const SkBitmap& actual) {
    // Render the golden image if any verifiers need it.
    SkBitmap goldBmp;
    if (needsGoldImage()) {
        goldBmp = GMVerifier::RenderGoldBmp(fGM, colorInfo);
    }

    for (const auto& v : fVerifiers) {
        fFailedVerifier = v.get();
        if (v->needsGoldImage()) {
            RETURN_NOT_OK(v->verify(goldBmp, actual));
        } else {
            RETURN_NOT_OK(v->verify(actual));
        }
    }

    fFailedVerifier = nullptr;
    return VerifierResult::Ok();
}

}  // namespace verifiers
}  // namespace skiagm
