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

#include "fuzz/Fuzz.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkSurface.h"
#include "include/effects/SkGradientShader.h"
#include "src/core/SkTLazy.h"
#include "tools/flags/CommandLineFlags.h"

#include <algorithm>
#include <vector>

static DEFINE_bool2(verbose, v, false, "log verbose linear gradient description");

const int MAX_COUNT = 400;

void makeMatrix(Fuzz* fuzz, SkMatrix* m) {
    SkScalar mat[9];
    fuzz->nextN(mat, 9);
    m->set9(mat);
}

void initGradientParams(Fuzz* fuzz, std::vector<SkColor>* colors,
                        std::vector<SkScalar>* pos, SkTileMode* mode) {
    int count;
    fuzz->nextRange(&count, 0, MAX_COUNT);

    // Use a uint8_t to conserve bytes.  This makes our "fuzzed bytes footprint"
    // smaller, which leads to more efficient fuzzing.
    uint8_t m;
    fuzz->nextRange(&m, 0, 2);
    *mode = static_cast<SkTileMode>(m);

    colors->clear();
    pos   ->clear();
    for (int i = 0; i < count; i++) {
        SkColor c;
        SkScalar s;
        fuzz->next(&c, &s);
        colors->push_back(c);
        pos   ->push_back(s);
    }
    if (count) {
        std::sort(pos->begin(), pos->end());
        // The order matters.  If count == 1, we want pos == 0.
        (*pos)[count - 1] = 1;
        (*pos)[0]         = 0;
    }
}

static void logOptionalMatrix(const char* label, const SkMatrix* m) {
    if (!m) {
        return;
    }

    SkDEBUGF("  %s: [ ", label);
    for (int i = 0; i < 9; ++i) {
        SkDEBUGF("%.9g ", m->get(i));
    }
    SkDEBUGF("]\n");
}

static void logLinearGradient(const SkPoint pts[2],
                              const std::vector<SkColor>& colors,
                              const std::vector<SkScalar> pos,
                              SkTileMode mode,
                              uint32_t flags,
                              const SkMatrix* localMatrix,
                              const SkMatrix* globalMatrix) {
    if (!FLAGS_verbose) {
        return;
    }

    SkDebugf("--- fuzzLinearGradient ---\n");
    SkDebugf("  pts:\t\t[ (%.9g %.9g) (%.9g %.9g) ]\n",
             pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y());
    SkDebugf("  colors:\t[ ");
    for (auto color : colors) {
        SkDebugf("0x%x ", color);
    }

    SkDebugf("]\n  pos:\t\t");
    if (pos.empty()) {
        SkDebugf("nullptr");
    } else {
        SkDebugf("[ ");
        for (auto p : pos) {
            SkDebugf("%f ", p);
        }
    }
    SkDebugf("]\n");

    static const char* gModeName[] = {
        "kClamp_TileMode", "kRepeat_TileMode", "kMirror_TileMode", "kDecal_TileMode"
    };
    SkASSERT((unsigned)mode < SK_ARRAY_COUNT(gModeName));
    SkDebugf("  mode:\t\t%s\n", gModeName[(unsigned)mode]);
    SkDebugf("  flags:\t0x%x\n", flags);
    logOptionalMatrix("local matrix", localMatrix);
    logOptionalMatrix("global matrix", globalMatrix);
}

void fuzzLinearGradient(Fuzz* fuzz) {
    SkPoint pts[2];
    fuzz->next(&pts[0].fX, &pts[0].fY, &pts[1].fX, &pts[1].fY);
    bool useLocalMatrix, useGlobalMatrix;
    fuzz->next(&useLocalMatrix, &useGlobalMatrix);

    std::vector<SkColor> colors;
    std::vector<SkScalar> pos;
    SkTileMode mode;
    initGradientParams(fuzz, &colors, &pos, &mode);

    SkPaint p;
    uint32_t flags;
    fuzz->next(&flags);

    SkTLazy<SkMatrix> localMatrix;
    if (useLocalMatrix) {
        makeMatrix(fuzz, localMatrix.init());
    }
    p.setShader(SkGradientShader::MakeLinear(pts, colors.data(), pos.data(),
        colors.size(), mode, flags, localMatrix.getMaybeNull()));

    sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(50, 50));
    if (useGlobalMatrix) {
        SkMatrix gm;
        makeMatrix(fuzz, &gm);
        logLinearGradient(pts, colors, pos, mode, flags, localMatrix.getMaybeNull(), &gm);
        SkCanvas* c = surface->getCanvas();
        c->setMatrix(gm);
        c->drawPaint(p);
    } else {
        logLinearGradient(pts, colors, pos, mode, flags, localMatrix.getMaybeNull(), nullptr);
        surface->getCanvas()->drawPaint(p);
    }
}

void fuzzRadialGradient(Fuzz* fuzz) {
    SkPoint center;
    fuzz->next(&center.fX, &center.fY);
    SkScalar radius;
    bool useLocalMatrix, useGlobalMatrix;
    fuzz->next(&radius, &useLocalMatrix, &useGlobalMatrix);


    std::vector<SkColor> colors;
    std::vector<SkScalar> pos;
    SkTileMode mode;
    initGradientParams(fuzz, &colors, &pos, &mode);

    SkPaint p;
    uint32_t flags;
    fuzz->next(&flags);

    SkTLazy<SkMatrix> localMatrix;
    if (useLocalMatrix) {
        makeMatrix(fuzz, localMatrix.init());
    }
    p.setShader(SkGradientShader::MakeRadial(center, radius, colors.data(),
        pos.data(), colors.size(), mode, flags, localMatrix.getMaybeNull()));


    sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(50, 50));
    if (useGlobalMatrix) {
        SkMatrix gm;
        makeMatrix(fuzz, &gm);
        SkCanvas* c = surface->getCanvas();
        c->setMatrix(gm);
        c->drawPaint(p);
    } else {
        surface->getCanvas()->drawPaint(p);
    }
}

void fuzzTwoPointConicalGradient(Fuzz* fuzz) {
    SkPoint start;
    fuzz->next(&start.fX, &start.fY);
    SkPoint end;
    fuzz->next(&end.fX, &end.fY);
    SkScalar startRadius, endRadius;
    bool useLocalMatrix, useGlobalMatrix;
    fuzz->next(&startRadius, &endRadius, &useLocalMatrix, &useGlobalMatrix);

    std::vector<SkColor> colors;
    std::vector<SkScalar> pos;
    SkTileMode mode;
    initGradientParams(fuzz, &colors, &pos, &mode);

    SkPaint p;
    uint32_t flags;
    fuzz->next(&flags);

    SkTLazy<SkMatrix> localMatrix;
    if (useLocalMatrix) {
        makeMatrix(fuzz, localMatrix.init());
    }
    p.setShader(SkGradientShader::MakeTwoPointConical(start, startRadius,
        end, endRadius, colors.data(), pos.data(), colors.size(), mode,
        flags, localMatrix.getMaybeNull()));

    sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(50, 50));
    if (useGlobalMatrix) {
        SkMatrix gm;
        makeMatrix(fuzz, &gm);
        SkCanvas* c = surface->getCanvas();
        c->setMatrix(gm);
        c->drawPaint(p);
    } else {
        surface->getCanvas()->drawPaint(p);
    }
}

void fuzzSweepGradient(Fuzz* fuzz) {
    SkScalar cx, cy;
    bool useLocalMatrix, useGlobalMatrix;
    fuzz->next(&cx, &cy, &useLocalMatrix, &useGlobalMatrix);

    std::vector<SkColor> colors;
    std::vector<SkScalar> pos;
    SkTileMode mode;
    initGradientParams(fuzz, &colors, &pos, &mode);

    SkPaint p;
    if (useLocalMatrix) {
        SkMatrix m;
        makeMatrix(fuzz, &m);
        uint32_t flags;
        fuzz->next(&flags);

        p.setShader(SkGradientShader::MakeSweep(cx, cy, colors.data(),
            pos.data(), colors.size(), flags, &m));
    } else {
        p.setShader(SkGradientShader::MakeSweep(cx, cy, colors.data(),
            pos.data(), colors.size()));
    }

    sk_sp<SkSurface> surface(SkSurface::MakeRasterN32Premul(50, 50));
    if (useGlobalMatrix) {
        SkMatrix gm;
        makeMatrix(fuzz, &gm);
        SkCanvas* c = surface->getCanvas();
        c->setMatrix(gm);
        c->drawPaint(p);
    } else {
        surface->getCanvas()->drawPaint(p);
    }
}

DEF_FUZZ(Gradients, fuzz) {
    uint8_t i;
    fuzz->next(&i);

    switch(i) {
        case 0:
            SkDEBUGF("LinearGradient\n");
            fuzzLinearGradient(fuzz);
            return;
        case 1:
            SkDEBUGF("RadialGradient\n");
            fuzzRadialGradient(fuzz);
            return;
        case 2:
            SkDEBUGF("TwoPointConicalGradient\n");
            fuzzTwoPointConicalGradient(fuzz);
            return;
    }
    SkDEBUGF("SweepGradient\n");
    fuzzSweepGradient(fuzz);
    return;
}
