/*
 * Copyright (C) 2012 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.
 */

#include "Snapshot.h"

#include <SkCanvas.h>

namespace android {
namespace uirenderer {

///////////////////////////////////////////////////////////////////////////////
// Constructors
///////////////////////////////////////////////////////////////////////////////

Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0),
        invisible(false), empty(false), alpha(1.0f) {

    transform = &mTransformRoot;
    clipRect = &mClipRectRoot;
    region = NULL;
    clipRegion = NULL;
}

/**
 * Copies the specified snapshot/ The specified snapshot is stored as
 * the previous snapshot.
 */
Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags):
        flags(0), previous(s), layer(NULL), fbo(s->fbo),
        invisible(s->invisible), empty(false),
        viewport(s->viewport), height(s->height), alpha(s->alpha) {

    clipRegion = NULL;

    if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
        mTransformRoot.load(*s->transform);
        transform = &mTransformRoot;
    } else {
        transform = s->transform;
    }

    if (saveFlags & SkCanvas::kClip_SaveFlag) {
        mClipRectRoot.set(*s->clipRect);
        clipRect = &mClipRectRoot;
#if STENCIL_BUFFER_SIZE
        if (s->clipRegion) {
            mClipRegionRoot.merge(*s->clipRegion);
            clipRegion = &mClipRegionRoot;
        }
#endif
    } else {
        clipRect = s->clipRect;
#if STENCIL_BUFFER_SIZE
        clipRegion = s->clipRegion;
#endif
    }

    if (s->flags & Snapshot::kFlagFboTarget) {
        flags |= Snapshot::kFlagFboTarget;
        region = s->region;
    } else {
        region = NULL;
    }
}

///////////////////////////////////////////////////////////////////////////////
// Clipping
///////////////////////////////////////////////////////////////////////////////

void Snapshot::ensureClipRegion() {
#if STENCIL_BUFFER_SIZE
    if (!clipRegion) {
        clipRegion = &mClipRegionRoot;
        android::Rect tmp(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
        clipRegion->set(tmp);
    }
#endif
}

void Snapshot::copyClipRectFromRegion() {
#if STENCIL_BUFFER_SIZE
    if (!clipRegion->isEmpty()) {
        android::Rect bounds(clipRegion->bounds());
        clipRect->set(bounds.left, bounds.top, bounds.right, bounds.bottom);

        if (clipRegion->isRect()) {
            clipRegion->clear();
            clipRegion = NULL;
        }
    } else {
        clipRect->setEmpty();
        clipRegion = NULL;
    }
#endif
}

bool Snapshot::clipRegionOr(float left, float top, float right, float bottom) {
#if STENCIL_BUFFER_SIZE
    android::Rect tmp(left, top, right, bottom);
    clipRegion->orSelf(tmp);
    copyClipRectFromRegion();
    return true;
#else
    return false;
#endif
}

bool Snapshot::clipRegionXor(float left, float top, float right, float bottom) {
#if STENCIL_BUFFER_SIZE
    android::Rect tmp(left, top, right, bottom);
    clipRegion->xorSelf(tmp);
    copyClipRectFromRegion();
    return true;
#else
    return false;
#endif
}

bool Snapshot::clipRegionAnd(float left, float top, float right, float bottom) {
#if STENCIL_BUFFER_SIZE
    android::Rect tmp(left, top, right, bottom);
    clipRegion->andSelf(tmp);
    copyClipRectFromRegion();
    return true;
#else
    return false;
#endif
}

bool Snapshot::clipRegionNand(float left, float top, float right, float bottom) {
#if STENCIL_BUFFER_SIZE
    android::Rect tmp(left, top, right, bottom);
    clipRegion->subtractSelf(tmp);
    copyClipRectFromRegion();
    return true;
#else
    return false;
#endif
}

bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) {
    Rect r(left, top, right, bottom);
    transform->mapRect(r);
    return clipTransformed(r, op);
}

bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) {
    bool clipped = false;

    switch (op) {
        case SkRegion::kDifference_Op: {
            ensureClipRegion();
            clipped = clipRegionNand(r.left, r.top, r.right, r.bottom);
            break;
        }
        case SkRegion::kIntersect_Op: {
            if (CC_UNLIKELY(clipRegion)) {
                clipped = clipRegionOr(r.left, r.top, r.right, r.bottom);
            } else {
                clipped = clipRect->intersect(r);
                if (!clipped) {
                    clipRect->setEmpty();
                    clipped = true;
                }
            }
            break;
        }
        case SkRegion::kUnion_Op: {
            if (CC_UNLIKELY(clipRegion)) {
                clipped = clipRegionAnd(r.left, r.top, r.right, r.bottom);
            } else {
                clipped = clipRect->unionWith(r);
            }
            break;
        }
        case SkRegion::kXOR_Op: {
            ensureClipRegion();
            clipped = clipRegionXor(r.left, r.top, r.right, r.bottom);
            break;
        }
        case SkRegion::kReverseDifference_Op: {
            // TODO!!!!!!!
            break;
        }
        case SkRegion::kReplace_Op: {
            setClip(r.left, r.top, r.right, r.bottom);
            clipped = true;
            break;
        }
    }

    if (clipped) {
        flags |= Snapshot::kFlagClipSet;
    }

    return clipped;
}

void Snapshot::setClip(float left, float top, float right, float bottom) {
    clipRect->set(left, top, right, bottom);
#if STENCIL_BUFFER_SIZE
    if (clipRegion) {
        clipRegion->clear();
        clipRegion = NULL;
    }
#endif
    flags |= Snapshot::kFlagClipSet;
}

const Rect& Snapshot::getLocalClip() {
    mat4 inverse;
    inverse.loadInverse(*transform);

    mLocalClip.set(*clipRect);
    inverse.mapRect(mLocalClip);

    return mLocalClip;
}

void Snapshot::resetClip(float left, float top, float right, float bottom) {
    clipRect = &mClipRectRoot;
    setClip(left, top, right, bottom);
}

///////////////////////////////////////////////////////////////////////////////
// Transforms
///////////////////////////////////////////////////////////////////////////////

void Snapshot::resetTransform(float x, float y, float z) {
    transform = &mTransformRoot;
    transform->loadTranslate(x, y, z);
}

///////////////////////////////////////////////////////////////////////////////
// Queries
///////////////////////////////////////////////////////////////////////////////

bool Snapshot::isIgnored() const {
    return invisible || empty;
}

}; // namespace uirenderer
}; // namespace android