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

#ifndef GrStrokeInfo_DEFINED
#define GrStrokeInfo_DEFINED

#include "SkPathEffect.h"
#include "SkStrokeRec.h"
#include "SkTemplates.h"

class GrUniqueKey;

/*
 * GrStrokeInfo encapsulates all the pertinent infomation regarding the stroke. The SkStrokeRec
 * which holds information on fill style, width, miter, cap, and join. It also holds information
 * about the dash like intervals, count, and phase.
 */
class GrStrokeInfo : public SkStrokeRec {
public:
    static const GrStrokeInfo& FillInfo() {
        static const GrStrokeInfo gFill(kFill_InitStyle);
        return gFill;
    }

    GrStrokeInfo(SkStrokeRec::InitStyle style)
        : INHERITED(style)
        , fDashType(SkPathEffect::kNone_DashType) {
    }

    GrStrokeInfo(const GrStrokeInfo& src, bool includeDash = true)
        : INHERITED(src) {
        if (includeDash && src.isDashed()) {
            fDashType = src.fDashType;
            fDashPhase = src.fDashPhase;
            fIntervals.reset(src.getDashCount());
            memcpy(fIntervals.get(), src.fIntervals.get(), fIntervals.count() * sizeof(SkScalar));
        } else {
            fDashType = SkPathEffect::kNone_DashType;
        }
    }

    GrStrokeInfo(const SkPaint& paint, SkPaint::Style styleOverride)
        : INHERITED(paint, styleOverride)
        , fDashType(SkPathEffect::kNone_DashType) {
        this->init(paint);
    }

    explicit GrStrokeInfo(const SkPaint& paint)
        : INHERITED(paint)
        , fDashType(SkPathEffect::kNone_DashType) {
        this->init(paint);
    }

    GrStrokeInfo& operator=(const GrStrokeInfo& other) {
        if (other.isDashed()) {
            fDashType = other.fDashType;
            fDashPhase = other.fDashPhase;
            fIntervals.reset(other.getDashCount());
            memcpy(fIntervals.get(), other.fIntervals.get(), fIntervals.count() * sizeof(SkScalar));
        } else {
            this->removeDash();
        }
        this->INHERITED::operator=(other);
        return *this;
    }

    bool hasEqualEffect(const GrStrokeInfo& other) const {
        if (this->isDashed() != other.isDashed()) {
            return false;
        }
        if (this->isDashed()) {
            if (fDashPhase != other.fDashPhase ||
                fIntervals.count() != other.fIntervals.count() ||
                memcmp(fIntervals.get(), other.fIntervals.get(),
                       fIntervals.count() * sizeof(SkScalar)) != 0) {
                return false;
            }
        }
        return this->INHERITED::hasEqualEffect(other);
    }

    /*
     * This functions takes in a patheffect and updates the dashing information if the path effect
     * is a Dash type. Returns true if the path effect is a dashed effect and we are stroking,
     * otherwise it returns false.
     */
    bool setDashInfo(const SkPathEffect* pe) {
        if (pe && !this->isFillStyle()) {
            SkPathEffect::DashInfo dashInfo;
            fDashType = pe->asADash(&dashInfo);
            if (SkPathEffect::kDash_DashType == fDashType) {
                fIntervals.reset(dashInfo.fCount);
                dashInfo.fIntervals = fIntervals.get();
                pe->asADash(&dashInfo);
                fDashPhase = dashInfo.fPhase;
                return true;
            }
        }
        return false;
    }

    /*
     * Like the above, but sets with an explicit SkPathEffect::DashInfo
     */
    bool setDashInfo(const SkPathEffect::DashInfo& info) {
        if (!this->isFillStyle()) {
            fDashType = SkPathEffect::kDash_DashType;
            fDashPhase = info.fPhase;
            fIntervals.reset(info.fCount);
            for (int i = 0; i < fIntervals.count(); i++) {
                fIntervals[i] = info.fIntervals[i];
            }
            return true;
        }
        return false;
    }

    bool isDashed() const {
        return (!this->isFillStyle() && SkPathEffect::kDash_DashType == fDashType);
    }

    int32_t getDashCount() const {
        SkASSERT(this->isDashed());
        return fIntervals.count();
    }

    SkScalar getDashPhase() const {
        SkASSERT(this->isDashed());
        return fDashPhase;
    }

    const SkScalar* getDashIntervals() const {
        SkASSERT(this->isDashed());
        return fIntervals.get();
    }

    void removeDash() {
        fDashType = SkPathEffect::kNone_DashType;
    }

    /** Applies the dash to a path, if the stroke info has dashing.
     * @return true if the dashing was applied (dst and dstStrokeInfo will be modified).
     *         false if the stroke info did not have dashing. The dst and dstStrokeInfo
     *               will be unmodified. The stroking in the SkStrokeRec might still
     *               be applicable.
     */
    bool applyDashToPath(SkPath* dst, GrStrokeInfo* dstStrokeInfo, const SkPath& src) const;

    /**
     * Computes the length of the data that will be written by asUniqueKeyFragment() function.
     */
    int computeUniqueKeyFragmentData32Cnt() const {
        const int kSkScalarData32Cnt = sizeof(SkScalar) / sizeof(uint32_t);
        // SkStrokeRec data: 32 bits for style+join+cap and 2 scalars for miter and width.
        int strokeKeyData32Cnt = 1 + 2 * kSkScalarData32Cnt;

        if (this->isDashed()) {
            // One scalar for dash phase and one for each dash value.
            strokeKeyData32Cnt += (1 + this->getDashCount()) * kSkScalarData32Cnt;
        }
        return strokeKeyData32Cnt;
    }

    /**
     * Writes the object contents as uint32_t data, to be used with GrUniqueKey.
     * Note: the data written does not encode the length, so care must be taken to ensure
     * that the full unique key data is encoded properly. For example, GrStrokeInfo
     * fragment can be placed last in the sequence, at fixed index.
     */
    void asUniqueKeyFragment(uint32_t*) const;

private:
    // Prevent accidental usage, should use GrStrokeInfo::hasEqualEffect.
    bool hasEqualEffect(const SkStrokeRec& other) const;

    void init(const SkPaint& paint) {
        const SkPathEffect* pe = paint.getPathEffect();
        this->setDashInfo(pe);
    }

    SkPathEffect::DashType fDashType;
    SkScalar               fDashPhase;
    SkAutoSTArray<2, SkScalar> fIntervals;
    typedef SkStrokeRec INHERITED;
};

#endif
