/*
 * Copyright (C) 2010 The Android Open Source Project
 * Copyright (c) 2011 Code Aurora Forum. All rights reserved.
 *
 * 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.
 */

#define LOG_TAG "TileRenderer"

#include <GLES2/gl2.h>
#include <EGL/egl.h>
#include <gl2ext.h>
#include "tilerenderer.h"

#define DEBUG_TILE_RENDERER 0

namespace android {
ANDROID_SINGLETON_STATIC_INSTANCE(uirenderer::TileRenderer) ;
namespace uirenderer {

#if DEBUG_TILE_RENDERER
    #define TILE_RENDERER_LOGD(...) ALOGD(__VA_ARGS__)
#else
    #define TILE_RENDERER_LOGD(...)
#endif

TileCacheMgr::TileCache::TileCache(void) {
}

TileCacheMgr::TileCache::TileCache(int fbo) {
    mFbo = fbo;
    mLeft = 0;
    mTop = 0;
    mRight = 0;
    mBottom = 0;
    mWidth = 0;
    mHeight = 0;
}

TileCacheMgr::TileCache::TileCache(int fbo, int left, int top, int right,
                                   int bottom, int width, int height) {
    mFbo = fbo;
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
    mWidth = width;
    mHeight = height;
}

TileCacheMgr::TileCache::~TileCache(void) {}

TileCacheMgr::TileCache::TileCache(const TileCacheMgr::TileCache& src) {
    copy(src);
}

const TileCacheMgr::TileCache& TileCacheMgr::TileCache::operator=(const TileCacheMgr::TileCache& src) {
    if (this != &src)
        copy(src);
    return *this;
}

void TileCacheMgr::TileCache::copy(const TileCacheMgr::TileCache& src) {
    mFbo = src.mFbo;
    mLeft = src.mLeft;
    mTop = src.mTop;
    mRight = src.mRight;
    mBottom = src.mBottom;
    mWidth = src.mWidth;
    mHeight = src.mHeight;
}

void TileCacheMgr::TileCache::setValue(int left, int top, int right,
                                       int bottom, int width, int height) {
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
    mWidth = width;
    mHeight = height;
}

void TileCacheMgr::TileCache::getValue(int &left, int &top, int &right,
                                       int &bottom, int &width, int &height) const {
    left = mLeft;
    top= mTop;
    right = mRight;
    bottom = mBottom;
    width = mWidth;
    height = mHeight;
}

TileCacheMgr::TileCacheMgr() {
    mIsExtTilingStarted = false;
    pthread_mutex_init(&mLock, NULL);
}

TileCacheMgr::~TileCacheMgr() {
    pthread_mutex_destroy(&mLock);
}

void TileCacheMgr::set(int left, int top, int right,
                       int bottom, int width, int height) {
    pthread_mutex_lock(&mLock);
    mIsExtTilingStarted = true;
    pthread_mutex_unlock(&mLock);
    TILE_RENDERER_LOGD("TileCacheMgr::set(ltrbwh) "
                       "fbo=-1 l=%d r=%d\n", left, right);
    set(-1, left, top, right, bottom, width, height);
}

void TileCacheMgr::set(int fbo) {
    int index = -1;
    int left = 0, top = 0, right = 0, bottom = 0;
    int width = 0, height = 0;
    TILE_RENDERER_LOGD("TileCacheMgr::set(fbo) "
                       "mIsExtTilingStarted=%d size=%d fbo=%d\n",
                       mIsExtTilingStarted, mTileCache.size(), fbo);
    if (mIsExtTilingStarted) {
        get(-1, left, top, right, bottom, width, height);
        pthread_mutex_lock(&mLock);
        mIsExtTilingStarted = false;
        TileCache entry(fbo, left, top, right, bottom, width, height);
        mTileCache.add(entry);
        pthread_mutex_unlock(&mLock);
    }
}

void TileCacheMgr::set(int fbo, int left, int top, int right,
                       int bottom, int width, int height) {
    int index = -1;
    TileCache entry(fbo, left, top, right, bottom, width, height);
    TILE_RENDERER_LOGD("TileCacheMgr::set(fbo,ltrbwh) size=%d "
                       "fbo=%d l=%d r=%d\n",
                       mTileCache.size(), fbo, left, right);
    pthread_mutex_lock(&mLock);
    index = mTileCache.indexOf(entry);
    if (index < 0) {
        TILE_RENDERER_LOGD("set::index not found, add fbo=%d\n", fbo);
        mTileCache.add(entry);
    }
    else if (left || top || right || bottom){
        TILE_RENDERER_LOGD("set::index found, edit fbo=%d\n", fbo);
        mTileCache.editItemAt(index).setValue(left, top, right,
                                              bottom, width, height);
    }
    pthread_mutex_unlock(&mLock);
}

void TileCacheMgr::get(int fbo, int &left, int &top, int &right,
                       int &bottom, int &width, int &height) {
    TileCache entry(fbo);
    pthread_mutex_lock(&mLock);
    int index = mTileCache.indexOf(entry);
    if (index >= 0) {
        mTileCache.itemAt(index).getValue(left, top, right,
                                          bottom, width, height);
        mTileCache.removeAt(index);
        TILE_RENDERER_LOGD("TileCacheMgr::get fbo=%d l=%d t=%d r=%d b=%d\n",
                           fbo, left, top, right, bottom);
    }
    pthread_mutex_unlock(&mLock);
}

void TileCacheMgr::peek(int fbo, int &left, int &top, int &right,
                        int &bottom, int &width, int &height) {
    TileCache entry(fbo);
    pthread_mutex_lock(&mLock);
    int index = mTileCache.indexOf(entry);
    if (index >= 0) {
        mTileCache.itemAt(index).getValue(left, top, right,
                                          bottom, width, height);
        TILE_RENDERER_LOGD("TileCacheMgr::peek fbo=%d l=%d t=%d r=%d b=%d\n",
                           fbo, left, top, right, bottom);
    }
    pthread_mutex_unlock(&mLock);
}

void TileCacheMgr::clear(int fbo) {
    TILE_RENDERER_LOGD("TileCacheMgr::clear %d\n", fbo);
    pthread_mutex_lock(&mLock);
    if (fbo == -1) {
        mIsExtTilingStarted = false;
    }
    else if (fbo >= 0) {
        TileCache entry(fbo);
        int index = mTileCache.indexOf(entry);
        mTileCache.removeAt(index);
    }
    else if (fbo < 0) {
        mTileCache.clear();
    }
    pthread_mutex_unlock(&mLock);
}

TileRenderer::TileRenderer() {
    pthread_mutex_init(&mLock, NULL);
    mIsTiled = false;
    mIsReady = false;
}

TileRenderer::~TileRenderer() {
    pthread_mutex_destroy(&mLock);
}

void TileRenderer::startTileRendering(int left, int top,
                               int right, int bottom,
                               int width, int height) {
#ifdef QCOM_APP_TILE_RENDER
    bool preserve = false;

    if (isTiled() ||
        (verifyAndAdjustRect(left, top, right, bottom, width, height, preserve) < 0))
        return;

    setReady(true);

    TILE_RENDERER_LOGD("startTileRendering l=%d, t=%d, r=%d, b=%d w=%d h=%d",
                       left, top, right, bottom, width, height);
    mTileCacheMgr.set(left, top, right, bottom, width, height);
    if (startTilingInternal(left, top, right, bottom, width, height, preserve) >= 0) {
        setTiled(true);
    }
#endif
}

void TileRenderer::endTileRendering() {
#ifdef QCOM_APP_TILE_RENDER
    if (!isTiled()) {
        return;
    }
    endTilingInternal();
    mTileCacheMgr.clear(-1);
    TILE_RENDERER_LOGD("endTileRendering");
    setTiled(false);
    setReady(false);
#endif
}

void TileRenderer::startTiling(int fbo, int left, int top,
                               int right, int bottom,
                               int width, int height, bool preserve) {
#ifdef QCOM_APP_TILE_RENDER
    if (!isReady() || isTiled())
        return;

    mTileCacheMgr.set(fbo, left, top, right, bottom, width, height);
    mTileCacheMgr.peek(fbo, left, top, right, bottom, width, height);
    if (verifyAndAdjustRect(left, top, right, bottom, width, height, preserve) < 0)
        return;
    TILE_RENDERER_LOGD("TileRenderer::startTiling l=%d,"
                       "t=%d, r=%d, b=%d fbo=%d",
                       left, top, right, bottom, fbo);
    startTilingInternal(left, top, right, bottom,
                        width, height, preserve);
#endif
    return;
}

void TileRenderer::startTiling(int fbo, bool preserve) {
#ifdef QCOM_APP_TILE_RENDER
    int left, top;
    int right, bottom;
    int width, height;

    if (!isReady() || isTiled())
        return;

    mTileCacheMgr.peek(fbo, left, top, right, bottom, width, height);
    if (verifyAndAdjustRect(left, top, right, bottom, width, height, preserve) < 0)
        return;
    TILE_RENDERER_LOGD("TileRenderer::startTiling fbo=%d preserve=%d",
                       fbo, preserve);
    startTilingInternal(left, top, right, bottom,
                        width, height, preserve);
#endif
    return;
}

void TileRenderer::endTiling(int fbo, bool bClear) {
#ifdef QCOM_APP_TILE_RENDER
    if (!isTiled()) {
        return;
    }
    TILE_RENDERER_LOGD("TileRenderer::end fbo=%d", fbo);
    if (fbo >= 0) {
        mTileCacheMgr.set(fbo);
    }

    if (bClear) {
        mTileCacheMgr.clear(fbo);
    }

    endTilingInternal();
#endif
}

void TileRenderer::clearCache(int fbo) {
#ifdef QCOM_APP_TILE_RENDER
    mTileCacheMgr.clear(fbo);
#endif
}

int TileRenderer::startTilingInternal(int left, int top,
                                      int right, int bottom,
                                      int width, int height,
                                      bool preserve) {
    int ret = -1;
    GLenum status = GL_NO_ERROR;
    int l = left, t = (height - bottom);
    int w = (right - left), h = (bottom - top);
    unsigned int preservemask = GL_NONE;
    int rendertarget = FRAMEBUFFER_FBO;

    if (l < 0 || t < 0) {
        l = (l < 0) ? 0 : l;
        t = (t < 0) ? 0 : t;
        preserve = true;
    }

    if (w > width || h > height) {
        w = (w > width) ? width : w;
        h = (h > height) ? height : h;
        preserve = true;
    }

    //clear off all errors before tiling, if any
    while ((status = glGetError()) != GL_NO_ERROR) {
        ALOGE("glStartTilingQCOM: 0x%x", status);
    }

    if (preserve)
        preservemask = GL_COLOR_BUFFER_BIT0_QCOM;

    preservemask = preservemask << rendertarget;

    TILE_RENDERER_LOGD("startTilingInternal rendertarget=%d, "
                       "preservemask = 0x%x",
                       rendertarget, preservemask);

    glStartTilingQCOM(l, t, w, h, preservemask);

    status = glGetError();
    if (status == GL_NO_ERROR) {
        TILE_RENDERER_LOGD("startTilingInternal l=%d, t=%d, r=%d,"
                           "b=%d, w=%d, h=%d",
                           left, top, right, bottom, width, height);
        setTiled(true);
        ret = 0;
    } else
        ALOGE("glStartTilingQCOM: 0x%x", status);
    return ret;
}

int TileRenderer::endTilingInternal() {
    unsigned int preservemask = GL_COLOR_BUFFER_BIT0_QCOM;
    int rendertarget = FRAMEBUFFER_FBO;
    TILE_RENDERER_LOGD("endTilingInternal");
    preservemask = preservemask << rendertarget;
    TILE_RENDERER_LOGD("endTilingInternal rendertarget=%d,"
                       "preservemask = 0x%x",
                       rendertarget, preservemask);
    glEndTilingQCOM(preservemask);
    GLenum status = GL_NO_ERROR;
    while ((status = glGetError()) != GL_NO_ERROR) {
        ALOGE("glEndTilingQCOM: 0x%x", status);
    }
    setTiled(false);
    return 0;
}

int TileRenderer::verifyAndAdjustRect(int &left, int &top, int &right,
                                      int &bottom, int width, int height,
                                      bool &preserve) {
    int ret = 0;

    if (!left && !right && !top && !bottom) {
        left = 0;
        top = 0;
        right = width;
        bottom = height;
        preserve = true;
    }

    if (!left && !right && !top && !bottom) {
        //can't do tile rendering
        ALOGE("can't tile render; drity region, width, height not available");
        ret = -1;
    }
    return ret;
}

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