/*
 * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef LOTTIEITEM_H
#define LOTTIEITEM_H

#include <memory>
#include <sstream>

#include "lottie_lottiekeypath.h"
#include "lottie_lottiefiltermodel.h"
#include "rlottie.h"
#include "rlottiecommon.h"
#include "vector_varenaalloc.h"
#include "vector_vdrawable.h"
#include "vector_vmatrix.h"
#include "vector_vpainter.h"
#include "vector_vpath.h"
#include "vector_vpathmesure.h"
#include "vector_vpoint.h"

V_USE_NAMESPACE

namespace rlottie {

namespace internal {

template <class T>
class VSpan {
public:
    using reference = T &;
    using pointer = T *;
    using const_pointer = T const *;
    using const_reference = T const &;
    using index_type = size_t;

    using iterator = pointer;
    using const_iterator = const_pointer;

    VSpan() = default;
    VSpan(pointer data, index_type size) : _data(data), _size(size) {}

    constexpr pointer        data() const noexcept { return _data; }
    constexpr index_type     size() const noexcept { return _size; }
    constexpr bool           empty() const noexcept { return size() == 0; }
    constexpr iterator       begin() const noexcept { return data(); }
    constexpr iterator       end() const noexcept { return data() + size(); }
    constexpr const_iterator cbegin() const noexcept { return data(); }
    constexpr const_iterator cend() const noexcept { return data() + size(); }
    constexpr reference      operator[](index_type idx) const
    {
        return *(data() + idx);
    }

private:
    pointer    _data{nullptr};
    index_type _size{0};
};

namespace renderer {

using DrawableList = VSpan<VDrawable *>;

enum class DirtyFlagBit : uchar {
    None = 0x00,
    Matrix = 0x01,
    Alpha = 0x02,
    All = (Matrix | Alpha)
};
typedef vFlag<DirtyFlagBit> DirtyFlag;

class SurfaceCache {
public:
    SurfaceCache() { mCache.reserve(10); }

    VBitmap make_surface(
        size_t width, size_t height,
        VBitmap::Format format = VBitmap::Format::ARGB32_Premultiplied)
    {
        if (mCache.empty()) return {width, height, format};

        auto surface = mCache.back();
        surface.reset(width, height, format);

        mCache.pop_back();
        return surface;
    }

    void release_surface(VBitmap &surface) { mCache.push_back(surface); }

private:
    std::vector<VBitmap> mCache;
};

class Drawable final : public VDrawable {
public:
    void sync();

public:
    std::unique_ptr<LOTNode> mCNode{nullptr};

    ~Drawable() noexcept
    {
        if (mCNode && mCNode->mGradient.stopPtr)
            free(mCNode->mGradient.stopPtr);
    }
};

struct CApiData {
    CApiData();
    LOTLayerNode                mLayer;
    std::vector<LOTMask>        mMasks;
    std::vector<LOTLayerNode *> mLayers;
    std::vector<LOTNode *>      mCNodeList;
};

class Clipper {
public:
    explicit Clipper(VSize size) : mSize(size) {}
    void update(const VMatrix &matrix);
    void preprocess(const VRect &clip);
    VRle rle(const VRle &mask);

public:
    VSize       mSize;
    VPath       mPath;
    VRle        mMaskedRle;
    VRasterizer mRasterizer;
    bool        mRasterRequest{false};
};

class Mask {
public:
    explicit Mask(model::Mask *data) : mData(data) {}
    void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
                const DirtyFlag &flag);
    model::Mask::Mode maskMode() const { return mData->mMode; }
    VRle              rle();
    void              preprocess(const VRect &clip);
    bool              inverted() const { return mData->mInv; }
public:
    model::Mask *mData{nullptr};
    VPath        mLocalPath;
    VPath        mFinalPath;
    VRasterizer  mRasterizer;
    float        mCombinedAlpha{0};
    bool         mRasterRequest{false};
};

/*
 * Handels mask property of a layer item
 */
class LayerMask {
public:
    explicit LayerMask(model::Layer *layerData);
    void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
                const DirtyFlag &flag);
    bool isStatic() const { return mStatic; }
    VRle maskRle(const VRect &clipRect);
    void preprocess(const VRect &clip);

public:
    std::vector<Mask> mMasks;
    VRle              mRle;
    bool              mStatic{true};
    bool              mDirty{true};
};

class Layer;

class Composition {
public:
    explicit Composition(std::shared_ptr<model::Composition> composition);
    bool  update(int frameNo, const VSize &size, bool keepAspectRatio);
    VSize size() const { return mViewSize; }
    void  buildRenderTree();
    const LOTLayerNode *renderTree() const;
    bool                render(const rlottie::Surface &surface);
    void                setValue(const std::string &keypath, LOTVariant &value);

private:
    SurfaceCache                        mSurfaceCache;
    VBitmap                             mSurface;
    VMatrix                             mScaleMatrix;
    VSize                               mViewSize;
    std::shared_ptr<model::Composition> mModel;
    Layer *                             mRootLayer{nullptr};
    VArenaAlloc                         mAllocator{2048};
    int                                 mCurFrameNo;
    bool                                mKeepAspectRatio{true};
};

class Layer {
public:
    virtual ~Layer() = default;
    Layer &operator=(Layer &&) noexcept = delete;
    Layer(model::Layer *layerData);
    int          id() const { return mLayerData->id(); }
    int          parentId() const { return mLayerData->parentId(); }
    void         setParentLayer(Layer *parent) { mParentLayer = parent; }
    void         setComplexContent(bool value) { mComplexContent = value; }
    bool         complexContent() const { return mComplexContent; }
    virtual void update(int frameNo, const VMatrix &parentMatrix,
                        float parentAlpha);
    VMatrix      matrix(int frameNo) const;
    void         preprocess(const VRect &clip);
    virtual DrawableList renderList() { return {}; }
    virtual void         render(VPainter *painter, const VRle &mask,
                                const VRle &matteRle, SurfaceCache &cache);
    bool                 hasMatte()
    {
        if (mLayerData->mMatteType == model::MatteType::None) return false;
        return true;
    }
    model::MatteType matteType() const { return mLayerData->mMatteType; }
    bool             visible() const;
    virtual void     buildLayerNode();
    LOTLayerNode &   clayer() { return mCApiData->mLayer; }
    std::vector<LOTLayerNode *> &clayers() { return mCApiData->mLayers; }
    std::vector<LOTMask> &       cmasks() { return mCApiData->mMasks; }
    std::vector<LOTNode *> &     cnodes() { return mCApiData->mCNodeList; }
    const char *                 name() const { return mLayerData->name(); }
    virtual bool                 resolveKeyPath(LOTKeyPath &keyPath, uint depth,
                                                LOTVariant &value);

protected:
    virtual void   preprocessStage(const VRect &clip) = 0;
    virtual void   updateContent() = 0;
    inline VMatrix combinedMatrix() const { return mCombinedMatrix; }
    inline int     frameNo() const { return mFrameNo; }
    inline float   combinedAlpha() const { return mCombinedAlpha; }
    inline bool    isStatic() const { return mLayerData->isStatic(); }
    float opacity(int frameNo) const { return mLayerData->opacity(frameNo); }
    inline DirtyFlag flag() const { return mDirtyFlag; }
    bool             skipRendering() const
    {
        return (!visible() || vIsZero(combinedAlpha()));
    }

protected:
    std::unique_ptr<LayerMask> mLayerMask;
    model::Layer *             mLayerData{nullptr};
    Layer *                    mParentLayer{nullptr};
    VMatrix                    mCombinedMatrix;
    float                      mCombinedAlpha{0.0};
    int                        mFrameNo{-1};
    DirtyFlag                  mDirtyFlag{DirtyFlagBit::All};
    bool                       mComplexContent{false};
    std::unique_ptr<CApiData>  mCApiData;
};

class CompLayer final : public Layer {
public:
    explicit CompLayer(model::Layer *layerData, VArenaAlloc *allocator);

    void render(VPainter *painter, const VRle &mask, const VRle &matteRle,
                SurfaceCache &cache) final;
    void buildLayerNode() final;
    bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
                        LOTVariant &value) override;

protected:
    void preprocessStage(const VRect &clip) final;
    void updateContent() final;

private:
    void renderHelper(VPainter *painter, const VRle &mask, const VRle &matteRle,
                      SurfaceCache &cache);
    void renderMatteLayer(VPainter *painter, const VRle &inheritMask,
                          const VRle &matteRle, Layer *layer, Layer *src,
                          SurfaceCache &cache);

private:
    std::vector<Layer *>     mLayers;
    std::unique_ptr<Clipper> mClipper;
};

class SolidLayer final : public Layer {
public:
    explicit SolidLayer(model::Layer *layerData);
    void         buildLayerNode() final;
    DrawableList renderList() final;

protected:
    void preprocessStage(const VRect &clip) final;
    void updateContent() final;

private:
    Drawable   mRenderNode;
    VPath      mPath;
    VDrawable *mDrawableList{nullptr};  // to work with the Span api
};

class Group;

class ShapeLayer final : public Layer {
public:
    explicit ShapeLayer(model::Layer *layerData, VArenaAlloc *allocator);
    DrawableList renderList() final;
    void         buildLayerNode() final;
    bool         resolveKeyPath(LOTKeyPath &keyPath, uint depth,
                                LOTVariant &value) override;

protected:
    void                     preprocessStage(const VRect &clip) final;
    void                     updateContent() final;
    std::vector<VDrawable *> mDrawableList;
    Group *                  mRoot{nullptr};
};

class NullLayer final : public Layer {
public:
    explicit NullLayer(model::Layer *layerData);

protected:
    void preprocessStage(const VRect &) final {}
    void updateContent() final;
};

class ImageLayer final : public Layer {
public:
    explicit ImageLayer(model::Layer *layerData);
    void         buildLayerNode() final;
    DrawableList renderList() final;

protected:
    void preprocessStage(const VRect &clip) final;
    void updateContent() final;

private:
    Drawable   mRenderNode;
    VTexture   mTexture;
    VPath      mPath;
    VDrawable *mDrawableList{nullptr};  // to work with the Span api
};

class Object {
public:
    enum class Type : uchar { Unknown, Group, Shape, Paint, Trim };
    virtual ~Object() = default;
    Object &     operator=(Object &&) noexcept = delete;
    virtual void update(int frameNo, const VMatrix &parentMatrix,
                        float parentAlpha, const DirtyFlag &flag) = 0;
    virtual void renderList(std::vector<VDrawable *> &) {}
    virtual bool resolveKeyPath(LOTKeyPath &, uint, LOTVariant &)
    {
        return false;
    }
    virtual Object::Type type() const { return Object::Type::Unknown; }
};

class Shape;
class Group : public Object {
public:
    Group() = default;
    explicit Group(model::Group *data, VArenaAlloc *allocator);
    void addChildren(model::Group *data, VArenaAlloc *allocator);
    void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
                const DirtyFlag &flag) override;
    void applyTrim();
    void processTrimItems(std::vector<Shape *> &list);
    void processPaintItems(std::vector<Shape *> &list);
    void renderList(std::vector<VDrawable *> &list) override;
    Object::Type   type() const final { return Object::Type::Group; }
    const VMatrix &matrix() const { return mMatrix; }
    const char *   name() const
    {
        static const char *TAG = "__";
        return mModel.hasModel() ? mModel.name() : TAG;
    }
    bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
                        LOTVariant &value) override;

protected:
    std::vector<Object *> mContents;
    VMatrix               mMatrix;

private:
    model::Filter<model::Group> mModel;
};

class Shape : public Object {
public:
    Shape(bool staticPath) : mStaticPath(staticPath) {}
    void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
                const DirtyFlag &flag) final;
    Object::Type type() const final { return Object::Type::Shape; }
    bool         dirty() const { return mDirtyPath; }
    const VPath &localPath() const { return mTemp; }
    void         finalPath(VPath &result);
    void         updatePath(const VPath &path)
    {
        mTemp = path;
        mDirtyPath = true;
    }
    bool   staticPath() const { return mStaticPath; }
    void   setParent(Group *parent) { mParent = parent; }
    Group *parent() const { return mParent; }

protected:
    virtual void updatePath(VPath &path, int frameNo) = 0;
    virtual bool hasChanged(int prevFrame, int curFrame) = 0;

private:
    bool hasChanged(int frameNo)
    {
        int prevFrame = mFrameNo;
        mFrameNo = frameNo;
        if (prevFrame == -1) return true;
        if (mStaticPath || (prevFrame == frameNo)) return false;
        return hasChanged(prevFrame, frameNo);
    }
    Group *mParent{nullptr};
    VPath  mLocalPath;
    VPath  mTemp;
    int    mFrameNo{-1};
    bool   mDirtyPath{true};
    bool   mStaticPath;
};

class Rect final : public Shape {
public:
    explicit Rect(model::Rect *data);

protected:
    void         updatePath(VPath &path, int frameNo) final;
    model::Rect *mData{nullptr};

    bool hasChanged(int prevFrame, int curFrame) final
    {
        return (mData->mPos.changed(prevFrame, curFrame) ||
                mData->mSize.changed(prevFrame, curFrame) ||
                mData->roundnessChanged(prevFrame, curFrame));
    }
};

class Ellipse final : public Shape {
public:
    explicit Ellipse(model::Ellipse *data);

private:
    void            updatePath(VPath &path, int frameNo) final;
    model::Ellipse *mData{nullptr};
    bool            hasChanged(int prevFrame, int curFrame) final
    {
        return (mData->mPos.changed(prevFrame, curFrame) ||
                mData->mSize.changed(prevFrame, curFrame));
    }
};

class Path final : public Shape {
public:
    explicit Path(model::Path *data);

private:
    void         updatePath(VPath &path, int frameNo) final;
    model::Path *mData{nullptr};
    bool         hasChanged(int prevFrame, int curFrame) final
    {
        return mData->mShape.changed(prevFrame, curFrame);
    }
};

class Polystar final : public Shape {
public:
    explicit Polystar(model::Polystar *data);

private:
    void             updatePath(VPath &path, int frameNo) final;
    model::Polystar *mData{nullptr};

    bool hasChanged(int prevFrame, int curFrame) final
    {
        return (mData->mPos.changed(prevFrame, curFrame) ||
                mData->mPointCount.changed(prevFrame, curFrame) ||
                mData->mInnerRadius.changed(prevFrame, curFrame) ||
                mData->mOuterRadius.changed(prevFrame, curFrame) ||
                mData->mInnerRoundness.changed(prevFrame, curFrame) ||
                mData->mOuterRoundness.changed(prevFrame, curFrame) ||
                mData->mRotation.changed(prevFrame, curFrame));
    }
};

class Paint : public Object {
public:
    Paint(bool staticContent);
    void addPathItems(std::vector<Shape *> &list, size_t startOffset);
    void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
                const DirtyFlag &flag) override;
    void renderList(std::vector<VDrawable *> &list) final;
    Object::Type type() const final { return Object::Type::Paint; }

protected:
    virtual bool updateContent(int frameNo, const VMatrix &matrix,
                               float alpha) = 0;

private:
    void updateRenderNode();

protected:
    std::vector<Shape *> mPathItems;
    Drawable             mDrawable;
    VPath                mPath;
    DirtyFlag            mFlag;
    bool                 mStaticContent;
    bool                 mRenderNodeUpdate{true};
    bool                 mContentToRender{true};
};

class Fill final : public Paint {
public:
    explicit Fill(model::Fill *data);

protected:
    bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final;
    bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
                        LOTVariant &value) final;

private:
    model::Filter<model::Fill> mModel;
};

class GradientFill final : public Paint {
public:
    explicit GradientFill(model::GradientFill *data);

protected:
    bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final;

private:
    model::GradientFill *      mData{nullptr};
    std::unique_ptr<VGradient> mGradient;
};

class Stroke : public Paint {
public:
    explicit Stroke(model::Stroke *data);

protected:
    bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final;
    bool resolveKeyPath(LOTKeyPath &keyPath, uint depth,
                        LOTVariant &value) final;

private:
    model::Filter<model::Stroke> mModel;
};

class GradientStroke final : public Paint {
public:
    explicit GradientStroke(model::GradientStroke *data);

protected:
    bool updateContent(int frameNo, const VMatrix &matrix, float alpha) final;

private:
    model::GradientStroke *    mData{nullptr};
    std::unique_ptr<VGradient> mGradient;
};

class Trim final : public Object {
public:
    explicit Trim(model::Trim *data) : mData(data) {}
    void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
                const DirtyFlag &flag) final;
    Object::Type type() const final { return Object::Type::Trim; }
    void         update();
    void         addPathItems(std::vector<Shape *> &list, size_t startOffset);

private:
    bool pathDirty() const
    {
        for (auto &i : mPathItems) {
            if (i->dirty()) return true;
        }
        return false;
    }
    struct Cache {
        int                  mFrameNo{-1};
        model::Trim::Segment mSegment{};
    };
    Cache                mCache;
    std::vector<Shape *> mPathItems;
    model::Trim *        mData{nullptr};
    VPathMesure          mPathMesure;
    bool                 mDirty{true};
};

class Repeater final : public Group {
public:
    explicit Repeater(model::Repeater *data, VArenaAlloc *allocator);
    void update(int frameNo, const VMatrix &parentMatrix, float parentAlpha,
                const DirtyFlag &flag) final;
    void renderList(std::vector<VDrawable *> &list) final;

private:
    model::Repeater *mRepeaterData{nullptr};
    bool             mHidden{false};
    int              mCopies{0};
};

}  // namespace renderer

}  // namespace internal

}  // namespace rlottie

#endif  // LOTTIEITEM_H