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

#include "SkPictureStateTree.h"
#include "SkCanvas.h"

SkPictureStateTree::SkPictureStateTree()
    : fAlloc(2048)
    , fLastRestoredNode(NULL)
    , fStateStack(sizeof(Draw), 16) {
    fRootMatrix.reset();
    fRoot.fParent = NULL;
    fRoot.fMatrix = &fRootMatrix;
    fRoot.fFlags = Node::kSave_Flag;
    fRoot.fOffset = 0;
    fRoot.fLevel = 0;
    fCurrentState.fNode = &fRoot;
    fCurrentState.fMatrix = &fRootMatrix;
    *static_cast<Draw*>(fStateStack.push_back()) = fCurrentState;
}

SkPictureStateTree::~SkPictureStateTree() {
}

SkPictureStateTree::Draw* SkPictureStateTree::appendDraw(size_t offset) {
    Draw* draw = static_cast<Draw*>(fAlloc.allocThrow(sizeof(Draw)));
    *draw = fCurrentState;
    draw->fOffset = SkToU32(offset);
    return draw;
}

void SkPictureStateTree::appendSave() {
    *static_cast<Draw*>(fStateStack.push_back()) = fCurrentState;
    fCurrentState.fNode->fFlags |= Node::kSave_Flag;
}

void SkPictureStateTree::appendSaveLayer(size_t offset) {
    *static_cast<Draw*>(fStateStack.push_back()) = fCurrentState;
    this->appendNode(offset);
    fCurrentState.fNode->fFlags |= Node::kSaveLayer_Flag;
}

void SkPictureStateTree::saveCollapsed() {
    SkASSERT(NULL != fLastRestoredNode);
    SkASSERT(SkToBool(fLastRestoredNode->fFlags & \
        (Node::kSaveLayer_Flag | Node::kSave_Flag)));
    SkASSERT(fLastRestoredNode->fParent == fCurrentState.fNode);
    // The structure of the tree is not modified here. We just turn off
    // the save or saveLayer flag to prevent the iterator from making state
    // changing calls on the playback canvas when traversing a save or
    // saveLayerNode node.
    fLastRestoredNode->fFlags = 0;
}

void SkPictureStateTree::appendRestore() {
    fLastRestoredNode = fCurrentState.fNode;
    fCurrentState = *static_cast<Draw*>(fStateStack.back());
    fStateStack.pop_back();
}

void SkPictureStateTree::appendTransform(const SkMatrix& trans) {
    SkMatrix* m = static_cast<SkMatrix*>(fAlloc.allocThrow(sizeof(SkMatrix)));
    *m = trans;
    fCurrentState.fMatrix = m;
}

void SkPictureStateTree::appendClip(size_t offset) {
    this->appendNode(offset);
}

SkPictureStateTree::Iterator SkPictureStateTree::getIterator(const SkTDArray<void*>& draws,
                                                             SkCanvas* canvas) {
    return Iterator(draws, canvas, &fRoot);
}

void SkPictureStateTree::appendNode(size_t offset) {
    Node* n = static_cast<Node*>(fAlloc.allocThrow(sizeof(Node)));
    n->fOffset = SkToU32(offset);
    n->fFlags = 0;
    n->fParent = fCurrentState.fNode;
    n->fLevel = fCurrentState.fNode->fLevel + 1;
    n->fMatrix = fCurrentState.fMatrix;
    fCurrentState.fNode = n;
}

SkPictureStateTree::Iterator::Iterator(const SkTDArray<void*>& draws, SkCanvas* canvas, Node* root)
    : fDraws(&draws)
    , fCanvas(canvas)
    , fCurrentNode(root)
    , fPlaybackMatrix(canvas->getTotalMatrix())
    , fCurrentMatrix(NULL)
    , fPlaybackIndex(0)
    , fSave(false)
    , fValid(true) {
}

uint32_t SkPictureStateTree::Iterator::draw() {
    SkASSERT(this->isValid());
    if (fPlaybackIndex >= fDraws->count()) {
        // restore back to where we started
        fCanvas->setMatrix(fPlaybackMatrix);
        if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); }
        fCurrentNode = fCurrentNode->fParent;
        while (NULL != fCurrentNode) {
            if (fCurrentNode->fFlags & Node::kSave_Flag) { fCanvas->restore(); }
            if (fCurrentNode->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); }
            fCurrentNode = fCurrentNode->fParent;
        }
        return kDrawComplete;
    }

    Draw* draw = static_cast<Draw*>((*fDraws)[fPlaybackIndex]);
    Node* targetNode = draw->fNode;

    if (fSave) {
        fCanvas->save(SkCanvas::kClip_SaveFlag);
        fSave = false;
    }

    if (fCurrentNode != targetNode) {
        // If we're not at the target and we don't have a list of nodes to get there, we need to
        // figure out the path from our current node, to the target
        if (fNodes.count() == 0) {
            // Trace back up to a common ancestor, restoring to get our current state to match that
            // of the ancestor, and saving a list of nodes whose state we need to apply to get to
            // the target (we can restore up to the ancestor immediately, but we'll need to return
            // an offset for each node on the way down to the target, to apply the desired clips and
            // saveLayers, so it may take several draw() calls before the next draw actually occurs)
            Node* tmp = fCurrentNode;
            Node* ancestor = targetNode;
            while (tmp != ancestor) {
                uint16_t currentLevel = tmp->fLevel;
                uint16_t targetLevel = ancestor->fLevel;
                if (currentLevel >= targetLevel) {
                    if (tmp != fCurrentNode && tmp->fFlags & Node::kSave_Flag) { fCanvas->restore(); }
                    if (tmp->fFlags & Node::kSaveLayer_Flag) { fCanvas->restore(); }
                    tmp = tmp->fParent;
                }
                if (currentLevel <= targetLevel) {
                    fNodes.push(ancestor);
                    ancestor = ancestor->fParent;
                }
            }

            if (ancestor->fFlags & Node::kSave_Flag) {
                if (fCurrentNode != ancestor) { fCanvas->restore(); }
                if (targetNode != ancestor) { fCanvas->save(SkCanvas::kClip_SaveFlag); }
            }
            fCurrentNode = ancestor;
        }

        // If we're not at the target node yet, we'll need to return an offset to make the caller
        // apply the next clip or saveLayer.
        if (fCurrentNode != targetNode) {
            if (fCurrentMatrix != fNodes.top()->fMatrix) {
                fCurrentMatrix = fNodes.top()->fMatrix;
                SkMatrix tmp = *fNodes.top()->fMatrix;
                tmp.postConcat(fPlaybackMatrix);
                fCanvas->setMatrix(tmp);
            }
            uint32_t offset = fNodes.top()->fOffset;
            fCurrentNode = fNodes.top();
            fSave = fCurrentNode != targetNode && fCurrentNode->fFlags & Node::kSave_Flag;
            fNodes.pop();
            return offset;
        }
    }

    // If we got this far, the clip/saveLayer state is all set, so we can proceed to set the matrix
    // for the draw, and return its offset.

    if (fCurrentMatrix != draw->fMatrix) {
        SkMatrix tmp = *draw->fMatrix;
        tmp.postConcat(fPlaybackMatrix);
        fCanvas->setMatrix(tmp);
        fCurrentMatrix = draw->fMatrix;
    }

    ++fPlaybackIndex;
    return draw->fOffset;
}
