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

#include "src/gpu/graphite/geom/Shape.h"

#include "include/core/SkPathBuilder.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/private/base/SkAlign.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkMalloc.h"
#include "src/core/SkPathPriv.h"
#include "src/core/SkRRectPriv.h"

#include <cstring>

namespace {
// Keys for paths may be extracted from the path data for small paths, to maximize matches
// even when the genIDs may differ. The value is based on emperical experience, to trade off
// matches vs. key size.
constexpr int kMaxKeyFromDataVerbCnt = 10;
}

namespace skgpu::graphite {

Shape& Shape::operator=(const Shape& shape) {
    switch (shape.type()) {
        case Type::kEmpty: this->reset();                         break;
        case Type::kLine:  this->setLine(shape.p0(), shape.p1()); break;
        case Type::kRect:  this->setRect(shape.rect());           break;
        case Type::kRRect: this->setRRect(shape.rrect());         break;
        case Type::kArc:   this->setArc(shape.arc());             break;
        case Type::kPath:  this->setPath(shape.path());           break;
    }

    fInverted = shape.fInverted;
    return *this;
}

bool Shape::conservativeContains(const Rect& rect) const {
    switch (fType) {
        case Type::kEmpty: return false;
        case Type::kLine:  return false;
        case Type::kRect:  return fRect.contains(rect);
        case Type::kRRect: return fRRect.contains(rect.asSkRect());
        case Type::kPath:  // We need to ensure the path is non-inverted.
                           if (this->inverted()) {
                               SkPath nonInverted(fPath);
                               nonInverted.toggleInverseFillType();
                               return nonInverted.conservativelyContainsRect(rect.asSkRect());
                           } else {
                               return fPath.conservativelyContainsRect(rect.asSkRect());
                           }
        case Type::kArc:   if (fArc.fType == SkArc::Type::kWedge) {
                               SkPath arc = this->asPath();
                               if (this->inverted()) {
                                   arc.toggleInverseFillType();
                               }
                               return arc.conservativelyContainsRect(rect.asSkRect());
                           } else {
                               return false;
                           }
    }
    SkUNREACHABLE;
}

bool Shape::conservativeContains(skvx::float2 point) const {
    switch (fType) {
        case Type::kEmpty: return false;
        case Type::kLine:  return false;
        case Type::kRect:  return fRect.contains(Rect::Point(point));
        case Type::kRRect: return SkRRectPriv::ContainsPoint(fRRect, {point.x(), point.y()});
        case Type::kPath:  // We need to ensure the path is non-inverted.
                           if (this->inverted()) {
                               SkPath nonInverted(fPath);
                               nonInverted.toggleInverseFillType();
                               return nonInverted.contains(point.x(), point.y());
                           } else {
                               return fPath.contains(point.x(), point.y());
                           }
        case Type::kArc:   return false;
    }
    SkUNREACHABLE;
}

bool Shape::convex(bool simpleFill) const {
    if (this->isPath()) {
        // SkPath.isConvex() really means "is this path convex were it to be closed".
        return (simpleFill || fPath.isLastContourClosed()) && fPath.isConvex();
    } else if (this->isArc()) {
        return SkPathPriv::DrawArcIsConvex(fArc.sweepAngle(), fArc.fType, simpleFill);
    } else {
        // Every other shape type is convex by construction.
        return true;
    }
}

Rect Shape::bounds() const {
    switch (fType) {
        case Type::kEmpty: return Rect(0, 0, 0, 0);
        case Type::kLine:  return fRect.makeSorted(); // sorting corners computes bbox of segment
        case Type::kRect:  return fRect; // assuming it's sorted
        case Type::kRRect: return fRRect.getBounds();
        case Type::kArc:   return fArc.oval();
        case Type::kPath:  return fPath.getBounds();
    }
    SkUNREACHABLE;
}

SkPath Shape::asPath() const {
    if (fType == Type::kPath) {
        return fPath;
    }

    if (fType == Type::kArc) {
        // Filled ovals are already culled out so we assume no simple fills
        SkPath out = SkPathPriv::CreateDrawArcPath(fArc, /*isFillNoPathEffect=*/false);
        // CreateDrawArcPath resets the output path and configures its fill
        // type, so we just have to ensure invertedness is correct.
        if (fInverted) {
            out.toggleInverseFillType();
        }
        return out;
    }

    SkPathBuilder builder(this->fillType());
    switch (fType) {
        case Type::kEmpty: /* do nothing */                            break;
        case Type::kLine:  builder.moveTo(fRect.left(), fRect.top())
                                  .lineTo(fRect.right(), fRect.bot()); break;
        case Type::kRect:  builder.addRect(fRect.asSkRect());          break;
        case Type::kRRect: builder.addRRect(fRRect);                   break;
        case Type::kPath:
        case Type::kArc:   SkUNREACHABLE;
    }
    return builder.detach();
}

namespace {
int path_key_from_data_size(const SkPath& path) {
    const int verbCnt = path.countVerbs();
    if (verbCnt > kMaxKeyFromDataVerbCnt) {
        return -1;
    }
    const int pointCnt = path.countPoints();
    const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);

    static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t));
    static_assert(sizeof(SkScalar) == sizeof(uint32_t));
    // 1 is for the verb count. Each verb is a byte but we'll pad the verb data out to
    // a uint32_t length.
    return 1 + (SkAlign4(verbCnt) >> 2) + 2 * pointCnt + conicWeightCnt;
}

// Writes the path data key into the passed pointer.
void write_path_key_from_data(const SkPath& path, uint32_t* origKey) {
    uint32_t* key = origKey;
    // The check below should take care of negative values casted positive.
    const int verbCnt = path.countVerbs();
    const int pointCnt = path.countPoints();
    const int conicWeightCnt = SkPathPriv::ConicWeightCnt(path);
    SkASSERT(verbCnt <= kMaxKeyFromDataVerbCnt);
    SkASSERT(pointCnt && verbCnt);
    *key++ = verbCnt;
    memcpy(key, SkPathPriv::VerbData(path), verbCnt * sizeof(uint8_t));
    int verbKeySize = SkAlign4(verbCnt);
    // pad out to uint32_t alignment using value that will stand out when debugging.
    uint8_t* pad = reinterpret_cast<uint8_t*>(key)+ verbCnt;
    memset(pad, 0xDE, verbKeySize - verbCnt);
    key += verbKeySize >> 2;

    memcpy(key, SkPathPriv::PointData(path), sizeof(SkPoint) * pointCnt);
    static_assert(sizeof(SkPoint) == 2 * sizeof(uint32_t));
    key += 2 * pointCnt;
    sk_careful_memcpy(key, SkPathPriv::ConicWeightData(path), sizeof(SkScalar) * conicWeightCnt);
    static_assert(sizeof(SkScalar) == sizeof(uint32_t));
    SkDEBUGCODE(key += conicWeightCnt);
    SkASSERT(key - origKey == path_key_from_data_size(path));
}
} // anonymous namespace

int Shape::keySize() const {
    int count = 1; // Every key has the state flags from the Shape
    switch(this->type()) {
        case Type::kLine:
            static_assert(0 == sizeof(skvx::float4) % sizeof(uint32_t));
            count += sizeof(skvx::float4) / sizeof(uint32_t);
            break;
        case Type::kRect:
            static_assert(0 == sizeof(Rect) % sizeof(uint32_t));
            count += sizeof(Rect) / sizeof(uint32_t);
            break;
        case Type::kRRect:
            static_assert(0 == SkRRect::kSizeInMemory % sizeof(uint32_t));
            count += SkRRect::kSizeInMemory / sizeof(uint32_t);
            break;
        case Type::kArc:
            static_assert(0 == sizeof(SkArc) % sizeof(uint32_t));
            count += sizeof(SkArc) / sizeof(uint32_t);
            break;
        case Type::kPath: {
            // An empty path is the same as an empty shape -- only needs the state flags
            if (!this->path().isEmpty()) {
                int dataKeySize = path_key_from_data_size(this->path());
                if (dataKeySize >= 0) {
                    count += dataKeySize;
                } else {
                    count++; // Just adds the gen ID.
                }
            }
            break;
        }
        default:
            // else it's empty, which just needs the state flags for its key
            SkASSERT(this->isEmpty());
    }
    return count;
}

void Shape::writeKey(uint32_t* key, bool includeInverted) const {
    SkASSERT(this->keySize());
    SkDEBUGCODE(uint32_t* origKey = key;)

    // Every key starts with the state from the Shape (this includes path fill type,
    // and any tracked inversion, as well as the class of geometry).
    *key++ = this->stateKey(includeInverted);

    switch(this->type()) {
        case Type::kPath: {
            // An empty path is the same as an empty shape -- only needs the state flags
            if (!this->path().isEmpty()) {
                // The path's inversion must match our state in order for the path's key to suffice.
                SkASSERT(this->inverted() == this->path().isInverseFillType());

                int dataKeySize = path_key_from_data_size(this->path());
                if (dataKeySize >= 0) {
                    // We check the rest of the size in write_path_key_from_data
                    SkASSERT(key - origKey == 1);
                    write_path_key_from_data(this->path(), key);
                    return;
                } else {
                    *key++ = this->path().getGenerationID();
                }
            }
            break;
        }
        case Type::kRect:
            memcpy(key, &this->rect(), sizeof(Rect));
            key += sizeof(Rect) / sizeof(uint32_t);
            break;
        case Type::kRRect:
            this->rrect().writeToMemory(key);
            key += SkRRect::kSizeInMemory / sizeof(uint32_t);
            break;
        case Type::kArc: {
            // Write dense floats first
            memcpy(key, &fArc, sizeof(SkRect) + 2 * sizeof(float));
            key += (sizeof(SkArc) / sizeof(uint32_t) - 1);
            // Then write the final bool as an int, to make sure upper bits are set
            *key++ = fArc.isWedge() ? 1 : 0;
            break;
        }
        case Type::kLine: {
            skvx::float4 line = this->line();
            memcpy(key, &line, sizeof(skvx::float4));
            key += sizeof(skvx::float4) / sizeof(uint32_t);
            break;
        }
        default:
            // Nothing other than the flag state is needed in the key for an empty shape
            SkASSERT(this->isEmpty());
    }
    SkASSERT(key - origKey == this->keySize());
}

namespace {
SkPathFillType noninverted_fill_type(SkPathFillType fillType) {
    switch (fillType) {
        case SkPathFillType::kWinding:
        case SkPathFillType::kInverseWinding:
            return SkPathFillType::kWinding;
        case SkPathFillType::kEvenOdd:
        case SkPathFillType::kInverseEvenOdd:
            return SkPathFillType::kEvenOdd;
    }
    SkUNREACHABLE;
}
} // anonymous namespace

uint32_t Shape::stateKey(bool includeInverted) const {
    uint32_t key;
    if (includeInverted) {
        // Use the path's full fill type instead of just whether or not it's inverted.
        key = this->isPath() ? static_cast<uint32_t>(fPath.getFillType())
                             : (fInverted ? 1 : 0);
    } else {
        // Use the path's noninverted fill type.
        key = this->isPath() ? static_cast<uint32_t>(noninverted_fill_type(fPath.getFillType()))
                             : 0;
    }
    key |= ((uint32_t) fType) << 2; // fill type was 2 bits
    return key;
}

} // namespace skgpu::graphite
