/*
 ** Copyright 2006, 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 <stdio.h>
#include <stdlib.h>
#include "context.h"
#include "TextureObjectManager.h"

#include <private/ui/android_natives_priv.h>

namespace android {
// ----------------------------------------------------------------------------

EGLTextureObject::EGLTextureObject()
    : mSize(0)
{
    init();
}

EGLTextureObject::~EGLTextureObject()
{
    if (!direct) {
        if (mSize && surface.data)
            free(surface.data);
        if (mMipmaps)
            freeMipmaps();
    }
}

void EGLTextureObject::init()
{
    memset(&surface, 0, sizeof(surface));
    surface.version = sizeof(surface);
    mMipmaps = 0;
    mNumExtraLod = 0;
    mIsComplete = false;
    wraps = GL_REPEAT;
    wrapt = GL_REPEAT;
    min_filter = GL_LINEAR;
    mag_filter = GL_LINEAR;
    internalformat = 0;
    memset(crop_rect, 0, sizeof(crop_rect));
    generate_mipmap = GL_FALSE;
    direct = GL_FALSE;
    buffer = 0;
}

void EGLTextureObject::copyParameters(const sp<EGLTextureObject>& old)
{
    wraps = old->wraps;
    wrapt = old->wrapt;
    min_filter = old->min_filter;
    mag_filter = old->mag_filter;
    memcpy(crop_rect, old->crop_rect, sizeof(crop_rect));
    generate_mipmap = old->generate_mipmap;
    direct = old->direct;
}

status_t EGLTextureObject::allocateMipmaps()
{
    // here, by construction, mMipmaps=0 && mNumExtraLod=0

    if (!surface.data)
        return NO_INIT;

    int w = surface.width;
    int h = surface.height;
    const int numLods = 31 - gglClz(max(w,h));
    if (numLods <= 0)
        return NO_ERROR;

    mMipmaps = (GGLSurface*)malloc(numLods * sizeof(GGLSurface));
    if (!mMipmaps)
        return NO_MEMORY;

    memset(mMipmaps, 0, numLods * sizeof(GGLSurface));
    mNumExtraLod = numLods;
    return NO_ERROR;
}

void EGLTextureObject::freeMipmaps()
{
    if (mMipmaps) {
        for (int i=0 ; i<mNumExtraLod ; i++) {
            if (mMipmaps[i].data) {
                free(mMipmaps[i].data);
            }
        }
        free(mMipmaps);
        mMipmaps = 0;
        mNumExtraLod = 0;
    }
}

const GGLSurface& EGLTextureObject::mip(int lod) const
{
    if (lod<=0 || !mMipmaps)
        return surface;
    lod = min(lod-1, mNumExtraLod-1);
    return mMipmaps[lod];
}

GGLSurface& EGLTextureObject::editMip(int lod)
{
    return const_cast<GGLSurface&>(mip(lod));
}

status_t EGLTextureObject::setSurface(GGLSurface const* s)
{
    // XXX: glFlush() on 's'
    if (mSize && surface.data) {
        free(surface.data);
    }
    surface = *s;
    internalformat = 0;
    buffer = 0;

    // we should keep the crop_rect, but it's delicate because
    // the new size of the surface could make it invalid.
    // so for now, we just loose it.
    memset(crop_rect, 0, sizeof(crop_rect));

    // it would be nice if we could keep the generate_mipmap flag,
    // we would have to generate them right now though.
    generate_mipmap = GL_FALSE;

    direct = GL_TRUE;
    mSize = 0;  // we don't own this surface
    if (mMipmaps)
        freeMipmaps();
    mIsComplete = true;
    return NO_ERROR;
}

status_t EGLTextureObject::setImage(ANativeWindowBuffer* native_buffer)
{
    GGLSurface sur;
    sur.version = sizeof(GGLSurface);
    sur.width = native_buffer->width;
    sur.height= native_buffer->height;
    sur.stride= native_buffer->stride;
    sur.format= native_buffer->format;
    sur.data  = 0;
    setSurface(&sur);
    buffer = native_buffer;
    return NO_ERROR;
}

status_t EGLTextureObject::reallocate(
        GLint level, int w, int h, int s,
        int format, int compressedFormat, int bpr)
{
    const size_t size = h * bpr;
    if (level == 0)
    {
        if (size!=mSize || !surface.data) {
            if (mSize && surface.data) {
                free(surface.data);
            }
            surface.data = (GGLubyte*)malloc(size);
            if (!surface.data) {
                mSize = 0;
                mIsComplete = false;
                return NO_MEMORY;
            }
            mSize = size;
        }
        surface.version = sizeof(GGLSurface);
        surface.width  = w;
        surface.height = h;
        surface.stride = s;
        surface.format = format;
        surface.compressedFormat = compressedFormat;
        if (mMipmaps)
            freeMipmaps();
        mIsComplete = true;
    }
    else
    {
        if (!mMipmaps) {
            if (allocateMipmaps() != NO_ERROR)
                return NO_MEMORY;
        }

        ALOGW_IF(level-1 >= mNumExtraLod,
                "specifying mipmap level %d, but # of level is %d",
                level, mNumExtraLod+1);

        GGLSurface& mipmap = editMip(level);
        if (mipmap.data)
            free(mipmap.data);

        mipmap.data = (GGLubyte*)malloc(size);
        if (!mipmap.data) {
            memset(&mipmap, 0, sizeof(GGLSurface));
            mIsComplete = false;
            return NO_MEMORY;
        }

        mipmap.version = sizeof(GGLSurface);
        mipmap.width  = w;
        mipmap.height = h;
        mipmap.stride = s;
        mipmap.format = format;
        mipmap.compressedFormat = compressedFormat;

        // check if the texture is complete
        mIsComplete = true;
        const GGLSurface* prev = &surface;
        for (int i=0 ; i<mNumExtraLod ; i++) {
            const GGLSurface* curr = mMipmaps + i;
            if (curr->format != surface.format) {
                mIsComplete = false;
                break;
            }

            uint32_t w = (prev->width  >> 1) ? : 1;
            uint32_t h = (prev->height >> 1) ? : 1;
            if (w != curr->width || h != curr->height) {
                mIsComplete = false;
                break;
            }
            prev = curr;
        }
    }
    return NO_ERROR;
}

// ----------------------------------------------------------------------------

EGLSurfaceManager::EGLSurfaceManager()
    : TokenManager()
{
}

EGLSurfaceManager::~EGLSurfaceManager()
{
    // everything gets freed automatically here...
}

sp<EGLTextureObject> EGLSurfaceManager::createTexture(GLuint name)
{
    sp<EGLTextureObject> result;

    Mutex::Autolock _l(mLock);
    if (mTextures.indexOfKey(name) >= 0)
        return result; // already exists!

    result = new EGLTextureObject();

    status_t err = mTextures.add(name, result);
    if (err < 0)
        result.clear();

    return result;
}

sp<EGLTextureObject> EGLSurfaceManager::removeTexture(GLuint name)
{
    Mutex::Autolock _l(mLock);
    const ssize_t index = mTextures.indexOfKey(name);
    if (index >= 0) {
        sp<EGLTextureObject> result(mTextures.valueAt(index));
        mTextures.removeItemsAt(index);
        return result;
    }
    return 0;
}

sp<EGLTextureObject> EGLSurfaceManager::replaceTexture(GLuint name)
{
    sp<EGLTextureObject> tex;
    Mutex::Autolock _l(mLock);
    const ssize_t index = mTextures.indexOfKey(name);
    if (index >= 0) {
        const sp<EGLTextureObject>& old = mTextures.valueAt(index);
        const uint32_t refs = old->getStrongCount();
        if (ggl_likely(refs == 1)) {
            // we're the only owner
            tex = old;
        } else {
            // keep the texture's parameters
            tex = new EGLTextureObject();
            tex->copyParameters(old);
            mTextures.removeItemsAt(index);
            mTextures.add(name, tex);
        }
    }
    return tex;
}

void EGLSurfaceManager::deleteTextures(GLsizei n, const GLuint *tokens)
{
    // free all textures
    Mutex::Autolock _l(mLock);
    for (GLsizei i=0 ; i<n ; i++) {
        const GLuint t(*tokens++);
        if (t) {
            mTextures.removeItem(t);
        }
    }
}

sp<EGLTextureObject> EGLSurfaceManager::texture(GLuint name)
{
    Mutex::Autolock _l(mLock);
    const ssize_t index = mTextures.indexOfKey(name);
    if (index >= 0)
        return mTextures.valueAt(index);
    return 0;
}

// ----------------------------------------------------------------------------
}; // namespace android