/*
 * Copyright 2011, 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 <pthread.h>
#include <cutils/log.h>

extern "C" {
#include "liblzf/lzf.h"
}

#include "gltrace_context.h"

namespace android {
namespace gltrace {

using ::android::gl_hooks_t;

static pthread_key_t sTLSKey = -1;
static pthread_once_t sPthreadOnceKey = PTHREAD_ONCE_INIT;

void createTLSKey() {
    pthread_key_create(&sTLSKey, (void (*)(void*))&releaseContext);
}

GLTraceContext *getGLTraceContext() {
    return (GLTraceContext*) pthread_getspecific(sTLSKey);
}

void setGLTraceContext(GLTraceContext *c) {
    pthread_setspecific(sTLSKey, c);
}

void setupTraceContextThreadSpecific(GLTraceContext *context) {
    pthread_once(&sPthreadOnceKey, createTLSKey);
    setGLTraceContext(context);
}

void releaseContext() {
    GLTraceContext *c = getGLTraceContext();
    if (c != NULL) {
        delete c;
        setGLTraceContext(NULL);
    }
}

GLTraceState::GLTraceState(TCPStream *stream) {
    mTraceContextIds = 0;
    mStream = stream;

    mCollectFbOnEglSwap = false;
    mCollectFbOnGlDraw = false;
    mCollectTextureDataOnGlTexImage = false;
    pthread_rwlock_init(&mTraceOptionsRwLock, NULL);
}

GLTraceState::~GLTraceState() {
    if (mStream) {
        mStream->closeStream();
        mStream = NULL;
    }
}

TCPStream *GLTraceState::getStream() {
    return mStream;
}

void GLTraceState::safeSetValue(bool *ptr, bool value, pthread_rwlock_t *lock) {
    pthread_rwlock_wrlock(lock);
    *ptr = value;
    pthread_rwlock_unlock(lock);
}

bool GLTraceState::safeGetValue(bool *ptr, pthread_rwlock_t *lock) {
    pthread_rwlock_rdlock(lock);
    bool value = *ptr;
    pthread_rwlock_unlock(lock);
    return value;
}

void GLTraceState::setCollectFbOnEglSwap(bool en) {
    safeSetValue(&mCollectFbOnEglSwap, en, &mTraceOptionsRwLock);
}

void GLTraceState::setCollectFbOnGlDraw(bool en) {
    safeSetValue(&mCollectFbOnGlDraw, en, &mTraceOptionsRwLock);
}

void GLTraceState::setCollectTextureDataOnGlTexImage(bool en) {
    safeSetValue(&mCollectTextureDataOnGlTexImage, en, &mTraceOptionsRwLock);
}

bool GLTraceState::shouldCollectFbOnEglSwap() {
    return safeGetValue(&mCollectFbOnEglSwap, &mTraceOptionsRwLock);
}

bool GLTraceState::shouldCollectFbOnGlDraw() {
    return safeGetValue(&mCollectFbOnGlDraw, &mTraceOptionsRwLock);
}

bool GLTraceState::shouldCollectTextureDataOnGlTexImage() {
    return safeGetValue(&mCollectTextureDataOnGlTexImage, &mTraceOptionsRwLock);
}

GLTraceContext *GLTraceState::createTraceContext(int version, EGLContext eglContext) {
    int id = __sync_fetch_and_add(&mTraceContextIds, 1);

    const size_t DEFAULT_BUFFER_SIZE = 8192;
    BufferedOutputStream *stream = new BufferedOutputStream(mStream, DEFAULT_BUFFER_SIZE);
    GLTraceContext *traceContext = new GLTraceContext(id, version, this, stream);
    mPerContextState[eglContext] = traceContext;

    return traceContext;
}

GLTraceContext *GLTraceState::getTraceContext(EGLContext c) {
    return mPerContextState[c];
}

GLTraceContext::GLTraceContext(int id, int version, GLTraceState *state,
        BufferedOutputStream *stream) :
    mId(id),
    mVersion(version),
    mState(state),
    mBufferedOutputStream(stream),
    mElementArrayBuffers(DefaultKeyedVector<GLuint, ElementArrayBuffer*>(NULL))
{
    fbcontents = fbcompressed = NULL;
    fbcontentsSize = 0;
}

int GLTraceContext::getId() {
    return mId;
}

int GLTraceContext::getVersion() {
    return mVersion;
}

GLTraceState *GLTraceContext::getGlobalTraceState() {
    return mState;
}

void GLTraceContext::resizeFBMemory(unsigned minSize) {
    if (fbcontentsSize >= minSize) {
        return;
    }

    if (fbcontents != NULL) {
        free(fbcontents);
        free(fbcompressed);
    }

    fbcontents = malloc(minSize);
    fbcompressed = malloc(minSize);

    fbcontentsSize = minSize;
}

/** obtain a pointer to the compressed framebuffer image */
void GLTraceContext::getCompressedFB(void **fb, unsigned *fbsize, unsigned *fbwidth, 
                            unsigned *fbheight, FBBinding fbToRead) {
    int viewport[4] = {};
    hooks->gl.glGetIntegerv(GL_VIEWPORT, viewport);
    unsigned fbContentsSize = viewport[2] * viewport[3] * 4;

    resizeFBMemory(fbContentsSize);

    // switch current framebuffer binding if necessary
    GLint currentFb = -1;
    bool fbSwitched = false;
    if (fbToRead != CURRENTLY_BOUND_FB) {
        hooks->gl.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &currentFb);

        if (currentFb != 0) {
            hooks->gl.glBindFramebuffer(GL_FRAMEBUFFER, 0);
            fbSwitched = true;
        }
    }

    hooks->gl.glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
                                        GL_RGBA, GL_UNSIGNED_BYTE, fbcontents);

    // switch back to previously bound buffer if necessary
    if (fbSwitched) {
        hooks->gl.glBindFramebuffer(GL_FRAMEBUFFER, currentFb);
    }

    *fbsize = lzf_compress(fbcontents, fbContentsSize, fbcompressed, fbContentsSize);
    *fb = fbcompressed;
    *fbwidth = viewport[2];
    *fbheight = viewport[3];
}

void GLTraceContext::traceGLMessage(GLMessage *msg) {
    mBufferedOutputStream->send(msg);

    GLMessage_Function func = msg->function();
    if (func == GLMessage::eglSwapBuffers
        || func == GLMessage::eglCreateContext
        || func == GLMessage::eglMakeCurrent
        || func == GLMessage::glDrawArrays
        || func == GLMessage::glDrawElements) {
        mBufferedOutputStream->flush();
    }
}

void GLTraceContext::bindBuffer(GLuint bufferId, GLvoid *data, GLsizeiptr size) {
    // free previously bound buffer if any
    ElementArrayBuffer *oldBuffer = mElementArrayBuffers.valueFor(bufferId);
    if (oldBuffer != NULL) {
        delete oldBuffer;
    }

    mElementArrayBuffers.add(bufferId, new ElementArrayBuffer(data, size));
}

void GLTraceContext::getBuffer(GLuint bufferId, GLvoid **data, GLsizeiptr *size) {
    ElementArrayBuffer *buffer = mElementArrayBuffers.valueFor(bufferId);
    if (buffer == NULL) {
        *data = NULL;
        *size = 0;
    } else {
        *data = buffer->getBuffer();
        *size = buffer->getSize();
    }
}

void GLTraceContext::updateBufferSubData(GLuint bufferId, GLintptr offset, GLvoid *data,
                                                            GLsizeiptr size) {
    ElementArrayBuffer *buffer = mElementArrayBuffers.valueFor(bufferId);
    if (buffer != NULL) {
        buffer->updateSubBuffer(offset, data, size);
    }
}

void GLTraceContext::deleteBuffer(GLuint bufferId) {
    ElementArrayBuffer *buffer = mElementArrayBuffers.valueFor(bufferId);
    if (buffer != NULL) {
        delete buffer;
        mElementArrayBuffers.removeItem(bufferId);
    }
}

ElementArrayBuffer::ElementArrayBuffer(GLvoid *buf, GLsizeiptr size) {
    mBuf = malloc(size);
    mSize = size;

    if (buf != NULL) {
        memcpy(mBuf, buf, size);
    }
}

ElementArrayBuffer::~ElementArrayBuffer() {
    if (mBuf != NULL) {
        free(mBuf);
        mSize = 0;
    }

    mBuf = NULL;
}

void ElementArrayBuffer::updateSubBuffer(GLintptr offset, const GLvoid* data, GLsizeiptr size) {
    if (offset + size <= mSize) {
        memcpy((char*)mBuf + offset, data, size);
    }
}

GLvoid *ElementArrayBuffer::getBuffer() {
    return mBuf;
}

GLsizeiptr ElementArrayBuffer::getSize() {
    return mSize;
}

}; // namespace gltrace
}; // namespace android
