/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANDROID_HWUI_VPATH_H
#define ANDROID_HWUI_VPATH_H

#include "Canvas.h"

#include <SkBitmap.h>
#include <SkColor.h>
#include <SkCanvas.h>
#include <SkMatrix.h>
#include <SkPaint.h>
#include <SkPath.h>
#include <SkPathMeasure.h>
#include <SkRect.h>
#include <SkShader.h>

#include <cutils/compiler.h>
#include <stddef.h>
#include <vector>
#include <string>

namespace android {
namespace uirenderer {

namespace VectorDrawable {
#define VD_SET_PROP_WITH_FLAG(field, value, flag) (VD_SET_PROP(field, value) ? (flag = true, true): false);
#define VD_SET_PROP(field, value) (value != field ? (field = value, true) : false)

/* A VectorDrawable is composed of a tree of nodes.
 * Each node can be a group node, or a path.
 * A group node can have groups or paths as children, but a path node has
 * no children.
 * One example can be:
 *                 Root Group
 *                /    |     \
 *           Group    Path    Group
 *          /     \             |
 *         Path   Path         Path
 *
 */
class ANDROID_API Node {
public:
    Node(const Node& node) {
        mName = node.mName;
    }
    Node() {}
    virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
            float scaleX, float scaleY) = 0;
    virtual void dump() = 0;
    void setName(const char* name) {
        mName = name;
    }
    virtual ~Node(){}
protected:
    std::string mName;
};

class ANDROID_API Path : public Node {
public:
    struct ANDROID_API Data {
        std::vector<char> verbs;
        std::vector<size_t> verbSizes;
        std::vector<float> points;
        bool operator==(const Data& data) const {
            return verbs == data.verbs && verbSizes == data.verbSizes
                    && points == data.points;
        }
    };
    Path(const Data& nodes);
    Path(const Path& path);
    Path(const char* path, size_t strLength);
    Path() {}
    void dump() override;
    bool canMorph(const Data& path);
    bool canMorph(const Path& path);
    void draw(SkCanvas* outCanvas, const SkMatrix& groupStackedMatrix,
            float scaleX, float scaleY) override;
    void setPath(const char* path, size_t strLength);
    void setPathData(const Data& data);
    static float getMatrixScale(const SkMatrix& groupStackedMatrix);

protected:
    virtual const SkPath& getUpdatedPath();
    virtual void drawPath(SkCanvas *outCanvas, SkPath& renderPath,
            float strokeScale, const SkMatrix& matrix) = 0;
    Data mData;
    SkPath mSkPath;
    bool mSkPathDirty = true;
};

class ANDROID_API FullPath: public Path {
public:

struct Properties {
    float strokeWidth = 0;
    SkColor strokeColor = SK_ColorTRANSPARENT;
    float strokeAlpha = 1;
    SkColor fillColor = SK_ColorTRANSPARENT;
    float fillAlpha = 1;
    float trimPathStart = 0;
    float trimPathEnd = 1;
    float trimPathOffset = 0;
    int32_t strokeLineCap = SkPaint::Cap::kButt_Cap;
    int32_t strokeLineJoin = SkPaint::Join::kMiter_Join;
    float strokeMiterLimit = 4;
    int fillType = 0; /* non-zero or kWinding_FillType in Skia */
};

    FullPath(const FullPath& path); // for cloning
    FullPath(const char* path, size_t strLength) : Path(path, strLength) {}
    FullPath() : Path() {}
    FullPath(const Data& nodes) : Path(nodes) {}

    ~FullPath() {
        SkSafeUnref(mFillGradient);
        SkSafeUnref(mStrokeGradient);
    }

    void updateProperties(float strokeWidth, SkColor strokeColor,
            float strokeAlpha, SkColor fillColor, float fillAlpha,
            float trimPathStart, float trimPathEnd, float trimPathOffset,
            float strokeMiterLimit, int strokeLineCap, int strokeLineJoin, int fillType);
    // TODO: Cleanup: Remove the setter and getters below, and their counterparts in java and JNI
    float getStrokeWidth() {
        return mProperties.strokeWidth;
    }
    void setStrokeWidth(float strokeWidth) {
        mProperties.strokeWidth = strokeWidth;
    }
    SkColor getStrokeColor() {
        return mProperties.strokeColor;
    }
    void setStrokeColor(SkColor strokeColor) {
        mProperties.strokeColor = strokeColor;
    }
    float getStrokeAlpha() {
        return mProperties.strokeAlpha;
    }
    void setStrokeAlpha(float strokeAlpha) {
        mProperties.strokeAlpha = strokeAlpha;
    }
    SkColor getFillColor() {
        return mProperties.fillColor;
    }
    void setFillColor(SkColor fillColor) {
        mProperties.fillColor = fillColor;
    }
    float getFillAlpha() {
        return mProperties.fillAlpha;
    }
    void setFillAlpha(float fillAlpha) {
        mProperties.fillAlpha = fillAlpha;
    }
    float getTrimPathStart() {
        return mProperties.trimPathStart;
    }
    void setTrimPathStart(float trimPathStart) {
        VD_SET_PROP_WITH_FLAG(mProperties.trimPathStart, trimPathStart, mTrimDirty);
    }
    float getTrimPathEnd() {
        return mProperties.trimPathEnd;
    }
    void setTrimPathEnd(float trimPathEnd) {
        VD_SET_PROP_WITH_FLAG(mProperties.trimPathEnd, trimPathEnd, mTrimDirty);
    }
    float getTrimPathOffset() {
        return mProperties.trimPathOffset;
    }
    void setTrimPathOffset(float trimPathOffset) {
        VD_SET_PROP_WITH_FLAG(mProperties.trimPathOffset, trimPathOffset, mTrimDirty);
    }
    bool getProperties(int8_t* outProperties, int length);
    void setColorPropertyValue(int propertyId, int32_t value);
    void setPropertyValue(int propertyId, float value);

    void setFillGradient(SkShader* fillGradient) {
        SkRefCnt_SafeAssign(mFillGradient, fillGradient);
    };
    void setStrokeGradient(SkShader* strokeGradient) {
        SkRefCnt_SafeAssign(mStrokeGradient, strokeGradient);
    };


protected:
    const SkPath& getUpdatedPath() override;
    void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
            float strokeScale, const SkMatrix& matrix) override;

private:
    enum class Property {
        StrokeWidth = 0,
        StrokeColor,
        StrokeAlpha,
        FillColor,
        FillAlpha,
        TrimPathStart,
        TrimPathEnd,
        TrimPathOffset,
        StrokeLineCap,
        StrokeLineJoin,
        StrokeMiterLimit,
        FillType,
        Count,
    };
    // Applies trimming to the specified path.
    void applyTrim();
    Properties mProperties;
    bool mTrimDirty = true;
    SkPath mTrimmedSkPath;
    SkPaint mPaint;
    SkShader* mStrokeGradient = nullptr;
    SkShader* mFillGradient = nullptr;
};

class ANDROID_API ClipPath: public Path {
public:
    ClipPath(const ClipPath& path) : Path(path) {}
    ClipPath(const char* path, size_t strLength) : Path(path, strLength) {}
    ClipPath() : Path() {}
    ClipPath(const Data& nodes) : Path(nodes) {}

protected:
    void drawPath(SkCanvas* outCanvas, SkPath& renderPath,
            float strokeScale, const SkMatrix& matrix) override;
};

class ANDROID_API Group: public Node {
public:
    struct Properties {
        float rotate = 0;
        float pivotX = 0;
        float pivotY = 0;
        float scaleX = 1;
        float scaleY = 1;
        float translateX = 0;
        float translateY = 0;
    };
    Group(const Group& group);
    Group() {}
    float getRotation() {
        return mProperties.rotate;
    }
    void setRotation(float rotation) {
        mProperties.rotate = rotation;
    }
    float getPivotX() {
        return mProperties.pivotX;
    }
    void setPivotX(float pivotX) {
        mProperties.pivotX = pivotX;
    }
    float getPivotY() {
        return mProperties.pivotY;
    }
    void setPivotY(float pivotY) {
        mProperties.pivotY = pivotY;
    }
    float getScaleX() {
        return mProperties.scaleX;
    }
    void setScaleX(float scaleX) {
        mProperties.scaleX = scaleX;
    }
    float getScaleY() {
        return mProperties.scaleY;
    }
    void setScaleY(float scaleY) {
        mProperties.scaleY = scaleY;
    }
    float getTranslateX() {
        return mProperties.translateX;
    }
    void setTranslateX(float translateX) {
        mProperties.translateX = translateX;
    }
    float getTranslateY() {
        return mProperties.translateY;
    }
    void setTranslateY(float translateY) {
        mProperties.translateY = translateY;
    }
    virtual void draw(SkCanvas* outCanvas, const SkMatrix& currentMatrix,
            float scaleX, float scaleY) override;
    void updateLocalMatrix(float rotate, float pivotX, float pivotY,
            float scaleX, float scaleY, float translateX, float translateY);
    void getLocalMatrix(SkMatrix* outMatrix);
    void addChild(Node* child);
    void dump() override;
    bool getProperties(float* outProperties, int length);
    float getPropertyValue(int propertyId) const;
    void setPropertyValue(int propertyId, float value);
    static bool isValidProperty(int propertyId);

private:
    enum class Property {
        Rotate = 0,
        PivotX,
        PivotY,
        ScaleX,
        ScaleY,
        TranslateX,
        TranslateY,
        // Count of the properties, must be at the end.
        Count,
    };
    std::vector< std::unique_ptr<Node> > mChildren;
    Properties mProperties;
};

class ANDROID_API Tree : public VirtualLightRefBase {
public:
    Tree(Group* rootNode) : mRootNode(rootNode) {}
    void draw(Canvas* outCanvas, SkColorFilter* colorFilter,
            const SkRect& bounds, bool needsMirroring, bool canReuseCache);

    const SkBitmap& getBitmapUpdateIfDirty();
    void createCachedBitmapIfNeeded(int width, int height);
    bool canReuseBitmap(int width, int height);
    void setAllowCaching(bool allowCaching) {
        mAllowCaching = allowCaching;
    }
    bool setRootAlpha(float rootAlpha) {
        return VD_SET_PROP(mRootAlpha, rootAlpha);
    }

    float getRootAlpha() {
        return mRootAlpha;
    }
    void setViewportSize(float viewportWidth, float viewportHeight) {
        mViewportWidth = viewportWidth;
        mViewportHeight = viewportHeight;
    }
    SkPaint* getPaint();
    const SkRect& getBounds() const {
        return mBounds;
    }

private:
    // Cap the bitmap size, such that it won't hurt the performance too much
    // and it won't crash due to a very large scale.
    // The drawable will look blurry above this size.
    const static int MAX_CACHED_BITMAP_SIZE;

    bool mCacheDirty = true;
    bool mAllowCaching = true;
    float mViewportWidth = 0;
    float mViewportHeight = 0;
    float mRootAlpha = 1.0f;

    std::unique_ptr<Group> mRootNode;
    SkRect mBounds;
    SkMatrix mCanvasMatrix;
    SkPaint mPaint;
    SkPathMeasure mPathMeasure;
    SkBitmap mCachedBitmap;

};

} // namespace VectorDrawable

typedef VectorDrawable::Path::Data PathData;
} // namespace uirenderer
} // namespace android

#endif // ANDROID_HWUI_VPATH_H
