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

#include "GrDrawVerticesBatch.h"

#include "GrBatchFlushState.h"
#include "GrInvariantOutput.h"
#include "GrDefaultGeoProcFactory.h"

static const GrGeometryProcessor* set_vertex_attributes(bool hasLocalCoords,
                                                        int* colorOffset,
                                                        int* texOffset,
                                                        const SkMatrix& viewMatrix,
                                                        bool coverageIgnored) {
    using namespace GrDefaultGeoProcFactory;
    *texOffset = -1;
    *colorOffset = -1;

    Coverage coverage(coverageIgnored ? Coverage::kNone_Type : Coverage::kSolid_Type);
    LocalCoords localCoords(hasLocalCoords ? LocalCoords::kHasExplicit_Type :
                                             LocalCoords::kUsePosition_Type);
    *colorOffset = sizeof(SkPoint);
    if (hasLocalCoords) {
        *texOffset = sizeof(SkPoint) + sizeof(GrColor);
    }
    return GrDefaultGeoProcFactory::Create(Color(Color::kAttribute_Type),
                                           coverage, localCoords, viewMatrix);
}

GrDrawVerticesBatch::GrDrawVerticesBatch(const Geometry& geometry, GrPrimitiveType primitiveType,
                                         const SkMatrix& viewMatrix,
                                         const SkPoint* positions, int vertexCount,
                                         const uint16_t* indices, int indexCount,
                                         const GrColor* colors, const SkPoint* localCoords,
                                         const SkRect& bounds)
    : INHERITED(ClassID()) {
    SkASSERT(positions);

    fViewMatrix = viewMatrix;
    Geometry& installedGeo = fGeoData.push_back(geometry);

    installedGeo.fPositions.append(vertexCount, positions);
    if (indices) {
        installedGeo.fIndices.append(indexCount, indices);
    }

    if (colors) {
        fVariableColor = true;
        installedGeo.fColors.append(vertexCount, colors);
    } else {
        fVariableColor = false;
    }

    if (localCoords) {
        installedGeo.fLocalCoords.append(vertexCount, localCoords);
    }
    fVertexCount = vertexCount;
    fIndexCount = indexCount;
    fPrimitiveType = primitiveType;

    this->setBounds(bounds);
}

void GrDrawVerticesBatch::getInvariantOutputColor(GrInitInvariantOutput* out) const {
    // When this is called on a batch, there is only one geometry bundle
    if (fVariableColor) {
        out->setUnknownFourComponents();
    } else {
        out->setKnownFourComponents(fGeoData[0].fColor);
    }
}

void GrDrawVerticesBatch::getInvariantOutputCoverage(GrInitInvariantOutput* out) const {
    out->setKnownSingleComponent(0xff);
}

void GrDrawVerticesBatch::initBatchTracker(const GrPipelineOptimizations& opt) {
    SkASSERT(fGeoData.count() == 1);
    GrColor overrideColor;
    if (opt.getOverrideColorIfSet(&overrideColor)) {
        fGeoData[0].fColor = overrideColor;
        fGeoData[0].fColors.reset();
        fVariableColor = false;
    }
    fCoverageIgnored = !opt.readsCoverage();
    if (!opt.readsLocalCoords()) {
        fGeoData[0].fLocalCoords.reset();
    }
}

void GrDrawVerticesBatch::onPrepareDraws(Target* target) {
    bool hasLocalCoords = !fGeoData[0].fLocalCoords.isEmpty();
    int colorOffset = -1, texOffset = -1;
    SkAutoTUnref<const GrGeometryProcessor> gp(
        set_vertex_attributes(hasLocalCoords, &colorOffset, &texOffset, fViewMatrix,
                              fCoverageIgnored));
    target->initDraw(gp, this->pipeline());

    size_t vertexStride = gp->getVertexStride();

    SkASSERT(vertexStride == sizeof(SkPoint) + (hasLocalCoords ? sizeof(SkPoint) : 0)
                                             + sizeof(GrColor));

    int instanceCount = fGeoData.count();

    const GrVertexBuffer* vertexBuffer;
    int firstVertex;

    void* verts = target->makeVertexSpace(vertexStride, fVertexCount, &vertexBuffer, &firstVertex);

    if (!verts) {
        SkDebugf("Could not allocate vertices\n");
        return;
    }

    const GrIndexBuffer* indexBuffer = nullptr;
    int firstIndex = 0;

    uint16_t* indices = nullptr;
    if (!fGeoData[0].fIndices.isEmpty()) {
        indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);

        if (!indices) {
            SkDebugf("Could not allocate indices\n");
            return;
        }
    }

    int indexOffset = 0;
    int vertexOffset = 0;
    for (int i = 0; i < instanceCount; i++) {
        const Geometry& args = fGeoData[i];

        // TODO we can actually cache this interleaved and then just memcopy
        if (indices) {
            for (int j = 0; j < args.fIndices.count(); ++j, ++indexOffset) {
                *(indices + indexOffset) = args.fIndices[j] + vertexOffset;
            }
        }

        for (int j = 0; j < args.fPositions.count(); ++j) {
            *((SkPoint*)verts) = args.fPositions[j];
            if (args.fColors.isEmpty()) {
                *(GrColor*)((intptr_t)verts + colorOffset) = args.fColor;
            } else {
                *(GrColor*)((intptr_t)verts + colorOffset) = args.fColors[j];
            }
            if (hasLocalCoords) {
                *(SkPoint*)((intptr_t)verts + texOffset) = args.fLocalCoords[j];
            }
            verts = (void*)((intptr_t)verts + vertexStride);
            vertexOffset++;
        }
    }

    GrVertices vertices;
    if (indices) {
        vertices.initIndexed(this->primitiveType(), vertexBuffer, indexBuffer, firstVertex,
                             firstIndex, fVertexCount, fIndexCount);

    } else {
        vertices.init(this->primitiveType(), vertexBuffer, firstVertex, fVertexCount);
    }
    target->draw(vertices);
}

bool GrDrawVerticesBatch::onCombineIfPossible(GrBatch* t, const GrCaps& caps) {
    GrDrawVerticesBatch* that = t->cast<GrDrawVerticesBatch>();

    if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
                                that->bounds(), caps)) {
        return false;
    }

    if (!this->batchablePrimitiveType() || this->primitiveType() != that->primitiveType()) {
        return false;
    }

    // We currently use a uniform viewmatrix for this batch
    if (!fViewMatrix.cheapEqualTo(that->fViewMatrix)) {
        return false;
    }

    if (fGeoData[0].fIndices.isEmpty() != that->fGeoData[0].fIndices.isEmpty()) {
        return false;
    }

    if (fGeoData[0].fLocalCoords.isEmpty() != that->fGeoData[0].fLocalCoords.isEmpty()) {
        return false;
    }

    if (!fVariableColor) {
        if (that->fVariableColor || that->fGeoData[0].fColor != fGeoData[0].fColor) {
            fVariableColor = true;
        }
    }

    fGeoData.push_back_n(that->geoData()->count(), that->geoData()->begin());
    fVertexCount += that->fVertexCount;
    fIndexCount += that->fIndexCount;

    this->joinBounds(that->bounds());
    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////

#ifdef GR_TEST_UTILS

#include "GrBatchTest.h"

static uint32_t seed_vertices(GrPrimitiveType type) {
    switch (type) {
        case kTriangles_GrPrimitiveType:
        case kTriangleStrip_GrPrimitiveType:
        case kTriangleFan_GrPrimitiveType:
            return 3;
        case kPoints_GrPrimitiveType:
            return 1;
        case kLines_GrPrimitiveType:
        case kLineStrip_GrPrimitiveType:
            return 2;
    }
    SkFAIL("Incomplete switch\n");
    return 0;
}

static uint32_t primitive_vertices(GrPrimitiveType type) {
    switch (type) {
        case kTriangles_GrPrimitiveType:
            return 3;
        case kLines_GrPrimitiveType:
            return 2;
        case kTriangleStrip_GrPrimitiveType:
        case kTriangleFan_GrPrimitiveType:
        case kPoints_GrPrimitiveType:
        case kLineStrip_GrPrimitiveType:
            return 1;
    }
    SkFAIL("Incomplete switch\n");
    return 0;
}

static SkPoint random_point(SkRandom* random, SkScalar min, SkScalar max) {
    SkPoint p;
    p.fX = random->nextRangeScalar(min, max);
    p.fY = random->nextRangeScalar(min, max);
    return p;
}

static void randomize_params(size_t count, size_t maxVertex, SkScalar min, SkScalar max,
                             SkRandom* random,
                             SkTArray<SkPoint>* positions,
                             SkTArray<SkPoint>* texCoords, bool hasTexCoords,
                             SkTArray<GrColor>* colors, bool hasColors,
                             SkTArray<uint16_t>* indices, bool hasIndices) {
    for (uint32_t v = 0; v < count; v++) {
        positions->push_back(random_point(random, min, max));
        if (hasTexCoords) {
            texCoords->push_back(random_point(random, min, max));
        }
        if (hasColors) {
            colors->push_back(GrRandomColor(random));
        }
        if (hasIndices) {
            SkASSERT(maxVertex <= SK_MaxU16);
            indices->push_back(random->nextULessThan((uint16_t)maxVertex));
        }
    }
}

DRAW_BATCH_TEST_DEFINE(VerticesBatch) {
    GrPrimitiveType type = GrPrimitiveType(random->nextULessThan(kLast_GrPrimitiveType + 1));
    uint32_t primitiveCount = random->nextRangeU(1, 100);

    // TODO make 'sensible' indexbuffers
    SkTArray<SkPoint> positions;
    SkTArray<SkPoint> texCoords;
    SkTArray<GrColor> colors;
    SkTArray<uint16_t> indices;

    bool hasTexCoords = random->nextBool();
    bool hasIndices = random->nextBool();
    bool hasColors = random->nextBool();

    uint32_t vertexCount = seed_vertices(type) + (primitiveCount - 1) * primitive_vertices(type);

    static const SkScalar kMinVertExtent = -100.f;
    static const SkScalar kMaxVertExtent = 100.f;
    randomize_params(seed_vertices(type), vertexCount, kMinVertExtent, kMaxVertExtent,
                     random,
                     &positions,
                     &texCoords, hasTexCoords,
                     &colors, hasColors,
                     &indices, hasIndices);

    for (uint32_t i = 1; i < primitiveCount; i++) {
        randomize_params(primitive_vertices(type), vertexCount, kMinVertExtent, kMaxVertExtent,
                         random,
                         &positions,
                         &texCoords, hasTexCoords,
                         &colors, hasColors,
                         &indices, hasIndices);
    }

    SkMatrix viewMatrix = GrTest::TestMatrix(random);
    SkRect bounds;
    SkDEBUGCODE(bool result = ) bounds.setBoundsCheck(positions.begin(), vertexCount);
    SkASSERT(result);

    viewMatrix.mapRect(&bounds);

    GrDrawVerticesBatch::Geometry geometry;
    geometry.fColor = GrRandomColor(random);
    return GrDrawVerticesBatch::Create(geometry, type, viewMatrix,
                                       positions.begin(), vertexCount,
                                       indices.begin(), hasIndices ? vertexCount : 0,
                                       colors.begin(),
                                       texCoords.begin(),
                                       bounds);
}

#endif
