/*
 * 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 SkMatrixClipStateMgr_DEFINED
#define SkMatrixClipStateMgr_DEFINED

#include "SkCanvas.h"
#include "SkMatrix.h"
#include "SkRegion.h"
#include "SkRRect.h"
#include "SkTypes.h"
#include "SkTDArray.h"

class SkPictureRecord;
class SkWriter32;

// The SkMatrixClipStateMgr collapses the matrix/clip state of an SkPicture into
// a series of save/restore blocks of consistent matrix clip state, e.g.:
//
//   save
//     clip(s)
//     concat
//     ... draw ops ...
//   restore
//
// SaveLayers simply add another level, e.g.:
//
//   save
//     clip(s)
//     concat
//     ... draw ops ...
//     saveLayer
//       save
//         clip(s)
//         concat
//         ... draw ops ...
//       restore
//     restore
//   restore
//
// As a side effect of this process all saves and saveLayers will become
// kMatrixClip_SaveFlag style saves/saveLayers.

// The SkMatrixClipStateMgr works by intercepting all the save*, restore, clip*,
// and matrix calls sent to SkCanvas in order to track the current matrix/clip
// state. All the other canvas calls get funnelled into a generic "call" entry
// point that signals that a state block is required.
class SkMatrixClipStateMgr {
public:
    static const int32_t kIdentityWideOpenStateID = 0;
    static const int kIdentityMatID = 0;

    class MatrixClipState : public SkNoncopyable {
    public:
        class MatrixInfo {
        public:
            void reset() {
                fMatrixID = kIdentityMatID;
                fMatrix.reset();
            }

            bool preTranslate(SkScalar dx, SkScalar dy) {
                fMatrixID = -1;
                return fMatrix.preTranslate(dx, dy);
            }

            bool preScale(SkScalar sx, SkScalar sy) {
                fMatrixID = -1;
                return fMatrix.preScale(sx, sy);
            }

            bool preRotate(SkScalar degrees) {
                fMatrixID = -1;
                return fMatrix.preRotate(degrees);
            }

            bool preSkew(SkScalar sx, SkScalar sy) {
                fMatrixID = -1;
                return fMatrix.preSkew(sx, sy);
            }

            bool preConcat(const SkMatrix& matrix) {
                fMatrixID = -1;
                return fMatrix.preConcat(matrix);
            }

            void setMatrix(const SkMatrix& matrix) {
                fMatrixID = -1;
                fMatrix = matrix;
            }

            int getID(SkMatrixClipStateMgr* mgr) {
                if (fMatrixID >= 0) {
                    return fMatrixID;
                }

                fMatrixID = mgr->addMatToDict(fMatrix);
                return fMatrixID;
            }

        private:
            SkMatrix fMatrix;
            int      fMatrixID;

            typedef SkNoncopyable INHERITED;
        };

        class ClipInfo : public SkNoncopyable {
        public:
            ClipInfo() {}

            bool clipRect(const SkRect& rect,
                          SkRegion::Op op,
                          bool doAA,
                          int matrixID) {
                ClipOp* newClip = fClips.append();
                newClip->fClipType = kRect_ClipType;
                newClip->fGeom.fRRect.setRect(rect);   // storing the clipRect in the RRect
                newClip->fOp = op;
                newClip->fDoAA = doAA;
                newClip->fMatrixID = matrixID;
                return false;
            }

            bool clipRRect(const SkRRect& rrect,
                           SkRegion::Op op,
                           bool doAA,
                           int matrixID) {
                ClipOp* newClip = fClips.append();
                newClip->fClipType = kRRect_ClipType;
                newClip->fGeom.fRRect = rrect;
                newClip->fOp = op;
                newClip->fDoAA = doAA;
                newClip->fMatrixID = matrixID;
                return false;
            }

            bool clipPath(SkPictureRecord* picRecord,
                          const SkPath& path,
                          SkRegion::Op op,
                          bool doAA,
                          int matrixID);
            bool clipRegion(SkPictureRecord* picRecord,
                            int regionID,
                            SkRegion::Op op,
                            int matrixID);
            void writeClip(int* curMatID, SkMatrixClipStateMgr* mgr);

            SkDEBUGCODE(int numClips() const { return fClips.count(); })

        private:
            enum ClipType {
                kRect_ClipType,
                kRRect_ClipType,
                kPath_ClipType,
                kRegion_ClipType
            };

            class ClipOp {
            public:
                ClipType     fClipType;

                union {
                    SkRRect fRRect;        // also stores clip rect
                    int     fPathID;
                    int     fRegionID;
                } fGeom;

                bool         fDoAA;
                SkRegion::Op fOp;

                // The CTM in effect when this clip call was issued
                int          fMatrixID;
            };

            SkTDArray<ClipOp> fClips;

            typedef SkNoncopyable INHERITED;
        };

        MatrixClipState(MatrixClipState* prev, int flags)
            : fPrev(prev)
        {
            fHasOpen = false;

            if (NULL == prev) {
                fLayerID = 0;

                fMatrixInfoStorage.reset();
                fMatrixInfo = &fMatrixInfoStorage;
                fClipInfo = &fClipInfoStorage;  // ctor handles init of fClipInfoStorage

                // The identity/wide-open-clip state is current by default
                fMCStateID = kIdentityWideOpenStateID;
#ifdef SK_DEBUG
                fExpectedDepth = 1;
#endif
            }
            else {
                fLayerID = prev->fLayerID;

                if (flags & SkCanvas::kMatrix_SaveFlag) {
                    fMatrixInfoStorage = *prev->fMatrixInfo;
                    fMatrixInfo = &fMatrixInfoStorage;
                } else {
                    fMatrixInfo = prev->fMatrixInfo;
                }

                if (flags & SkCanvas::kClip_SaveFlag) {
                    // We don't copy the ClipOps of the previous clip states
                    fClipInfo = &fClipInfoStorage;
                } else {
                    fClipInfo = prev->fClipInfo;
                }

                // Initially a new save/saveLayer represents the same MC state
                // as its predecessor.
                fMCStateID = prev->fMCStateID;
#ifdef SK_DEBUG
                fExpectedDepth = prev->fExpectedDepth;
#endif
            }

            fIsSaveLayer = false;
        }

        MatrixInfo*  fMatrixInfo;
        MatrixInfo   fMatrixInfoStorage;

        ClipInfo*    fClipInfo;
        ClipInfo     fClipInfoStorage;

        // Tracks the current depth of saveLayers to support the isDrawingToLayer call
        int          fLayerID;
        // Does this MC state represent a saveLayer call?
        bool         fIsSaveLayer;

        // The next field is only valid when fIsSaveLayer is set.
        SkTDArray<int>* fSavedSkipOffsets;

        // Does the MC state have an open block in the skp?
        bool         fHasOpen;

        MatrixClipState* fPrev;

#ifdef SK_DEBUG
        int              fExpectedDepth;    // debugging aid
#endif

        int32_t     fMCStateID;
    };

    enum CallType {
        kMatrix_CallType,
        kClip_CallType,
        kOther_CallType
    };

    SkMatrixClipStateMgr();
    ~SkMatrixClipStateMgr();

    void init(SkPictureRecord* picRecord) {
        // Note: we're not taking a ref here. It is expected that the SkMatrixClipStateMgr
        // is owned by the SkPictureRecord object
        fPicRecord = picRecord;
    }

    SkPictureRecord* getPicRecord() { return fPicRecord; }

    // TODO: need to override canvas' getSaveCount. Right now we pass the
    // save* and restore calls on to the base SkCanvas in SkPictureRecord but
    // this duplicates effort.
    int getSaveCount() const { return fMatrixClipStack.count(); }

    int save(SkCanvas::SaveFlags flags);

    int saveLayer(const SkRect* bounds, const SkPaint* paint, SkCanvas::SaveFlags flags);

    bool isDrawingToLayer() const {
        return fCurMCState->fLayerID > 0;
    }

    void restore();

    bool translate(SkScalar dx, SkScalar dy) {
        this->call(kMatrix_CallType);
        return fCurMCState->fMatrixInfo->preTranslate(dx, dy);
    }

    bool scale(SkScalar sx, SkScalar sy) {
        this->call(kMatrix_CallType);
        return fCurMCState->fMatrixInfo->preScale(sx, sy);
    }

    bool rotate(SkScalar degrees) {
        this->call(kMatrix_CallType);
        return fCurMCState->fMatrixInfo->preRotate(degrees);
    }

    bool skew(SkScalar sx, SkScalar sy) {
        this->call(kMatrix_CallType);
        return fCurMCState->fMatrixInfo->preSkew(sx, sy);
    }

    bool concat(const SkMatrix& matrix) {
        this->call(kMatrix_CallType);
        return fCurMCState->fMatrixInfo->preConcat(matrix);
    }

    void setMatrix(const SkMatrix& matrix) {
        this->call(kMatrix_CallType);
        fCurMCState->fMatrixInfo->setMatrix(matrix);
    }

    bool clipRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
        this->call(SkMatrixClipStateMgr::kClip_CallType);
        return fCurMCState->fClipInfo->clipRect(rect, op, doAA,
                                                fCurMCState->fMatrixInfo->getID(this));
    }

    bool clipRRect(const SkRRect& rrect, SkRegion::Op op, bool doAA) {
        this->call(SkMatrixClipStateMgr::kClip_CallType);
        return fCurMCState->fClipInfo->clipRRect(rrect, op, doAA,
                                                 fCurMCState->fMatrixInfo->getID(this));
    }

    bool clipPath(const SkPath& path, SkRegion::Op op, bool doAA) {
        this->call(SkMatrixClipStateMgr::kClip_CallType);
        return fCurMCState->fClipInfo->clipPath(fPicRecord, path, op, doAA,
                                                fCurMCState->fMatrixInfo->getID(this));
    }

    bool clipRegion(const SkRegion& region, SkRegion::Op op) {
        this->call(SkMatrixClipStateMgr::kClip_CallType);
        int regionID = this->addRegionToDict(region);
        return fCurMCState->fClipInfo->clipRegion(fPicRecord, regionID, op,
                                                  fCurMCState->fMatrixInfo->getID(this));
    }

    bool call(CallType callType);

    void fillInSkips(SkWriter32* writer, int32_t restoreOffset);

    void finish();

protected:
    SkPictureRecord* fPicRecord;

    uint32_t         fMatrixClipStackStorage[43]; // sized to fit 2 clip states
    SkDeque          fMatrixClipStack;
    MatrixClipState* fCurMCState;

    // This dictionary doesn't actually de-duplicate the matrices (except for the
    // identity matrix). It merely stores the matrices and allows them to be looked
    // up by ID later. The de-duplication mainly falls upon the matrix/clip stack
    // which stores the ID so a revisited clip/matrix (via popping the stack) will
    // use the same ID.
    SkTDArray<SkMatrix> fMatrixDict;

    SkTDArray<SkRegion*> fRegionDict;

    // The MCStateID of the state currently in effect in the byte stream. 0 if none.
    int32_t          fCurOpenStateID;
    // The skip offsets for the current open state. These are the locations in the
    // skp that must be filled in when the current open state is closed. These are
    // here rather then distributed across the MatrixClipState's because saveLayers
    // can cause MC states to be nested.
    SkTDArray<int32_t>  *fSkipOffsets;

    SkDEBUGCODE(void validate();)

    int MCStackPush(SkCanvas::SaveFlags flags);

    void addClipOffset(int offset) {
        SkASSERT(NULL != fSkipOffsets);
        SkASSERT(kIdentityWideOpenStateID != fCurOpenStateID);
        SkASSERT(fCurMCState->fHasOpen);
        SkASSERT(!fCurMCState->fIsSaveLayer);

        *fSkipOffsets->append() = offset;
    }

    void writeDeltaMat(int currentMatID, int desiredMatID);
    static int32_t   NewMCStateID();

    int addRegionToDict(const SkRegion& region);
    const SkRegion* lookupRegion(int index) {
        SkASSERT(index >= 0 && index < fRegionDict.count());
        return fRegionDict[index];
    }

    // TODO: add stats to check if the dictionary really does
    // reduce the size of the SkPicture.
    int addMatToDict(const SkMatrix& mat);
    const SkMatrix& lookupMat(int index) {
        SkASSERT(index >= 0 && index < fMatrixDict.count());
        return fMatrixDict[index];
    }

    bool isNestingMCState(int stateID);

#ifdef SK_DEBUG
    int fActualDepth;
#endif

    // save layers are nested within a specific MC state. This stack tracks
    // the nesting MC state's ID as save layers are pushed and popped.
    SkTDArray<int> fStateIDStack;
};

#endif
