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

#define LOG_TAG "OpenGLRenderer"

#include <utils/Log.h>

#include "Debug.h"
#include "Properties.h"
#include "RenderBufferCache.h"

namespace android {
namespace uirenderer {

///////////////////////////////////////////////////////////////////////////////
// Defines
///////////////////////////////////////////////////////////////////////////////

// Debug
#if DEBUG_RENDER_BUFFERS
    #define RENDER_BUFFER_LOGD(...) ALOGD(__VA_ARGS__)
#else
    #define RENDER_BUFFER_LOGD(...)
#endif

///////////////////////////////////////////////////////////////////////////////
// Constructors/destructor
///////////////////////////////////////////////////////////////////////////////

RenderBufferCache::RenderBufferCache(): mSize(0), mMaxSize(MB(DEFAULT_RENDER_BUFFER_CACHE_SIZE)) {
    char property[PROPERTY_VALUE_MAX];
    if (property_get(PROPERTY_RENDER_BUFFER_CACHE_SIZE, property, nullptr) > 0) {
        INIT_LOGD("  Setting render buffer cache size to %sMB", property);
        setMaxSize(MB(atof(property)));
    } else {
        INIT_LOGD("  Using default render buffer cache size of %.2fMB",
                DEFAULT_RENDER_BUFFER_CACHE_SIZE);
    }
}

RenderBufferCache::~RenderBufferCache() {
    clear();
}

///////////////////////////////////////////////////////////////////////////////
// Size management
///////////////////////////////////////////////////////////////////////////////

uint32_t RenderBufferCache::getSize() {
    return mSize;
}

uint32_t RenderBufferCache::getMaxSize() {
    return mMaxSize;
}

void RenderBufferCache::setMaxSize(uint32_t maxSize) {
    clear();
    mMaxSize = maxSize;
}

///////////////////////////////////////////////////////////////////////////////
// Caching
///////////////////////////////////////////////////////////////////////////////

int RenderBufferCache::RenderBufferEntry::compare(
        const RenderBufferCache::RenderBufferEntry& lhs,
        const RenderBufferCache::RenderBufferEntry& rhs) {
    int deltaInt = int(lhs.mWidth) - int(rhs.mWidth);
    if (deltaInt != 0) return deltaInt;

    deltaInt = int(lhs.mHeight) - int(rhs.mHeight);
    if (deltaInt != 0) return deltaInt;

    return int(lhs.mFormat) - int(rhs.mFormat);
}

void RenderBufferCache::deleteBuffer(RenderBuffer* buffer) {
    if (buffer) {
        RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d)",
                RenderBuffer::formatName(buffer->getFormat()),
                buffer->getWidth(), buffer->getHeight());

        mSize -= buffer->getSize();
        delete buffer;
    }
}

void RenderBufferCache::clear() {
    size_t count = mCache.size();
    for (size_t i = 0; i < count; i++) {
        deleteBuffer(mCache.itemAt(i).mBuffer);
    }
    mCache.clear();
}

RenderBuffer* RenderBufferCache::get(GLenum format, const uint32_t width, const uint32_t height) {
    RenderBuffer* buffer = nullptr;

    RenderBufferEntry entry(format, width, height);
    ssize_t index = mCache.indexOf(entry);

    if (index >= 0) {
        entry = mCache.itemAt(index);
        mCache.removeAt(index);

        buffer = entry.mBuffer;
        mSize -= buffer->getSize();

        RENDER_BUFFER_LOGD("Found %s render buffer (%dx%d)",
                RenderBuffer::formatName(format), width, height);
    } else {
        buffer = new RenderBuffer(format, width, height);

        RENDER_BUFFER_LOGD("Created new %s render buffer (%dx%d)",
                RenderBuffer::formatName(format), width, height);
    }

    buffer->bind();
    buffer->allocate();

    return buffer;
}

bool RenderBufferCache::put(RenderBuffer* buffer) {
    if (!buffer) return false;

    const uint32_t size = buffer->getSize();
    if (size < mMaxSize) {
        while (mSize + size > mMaxSize) {
            size_t position = 0;

            RenderBuffer* victim = mCache.itemAt(position).mBuffer;
            deleteBuffer(victim);
            mCache.removeAt(position);
        }

        RenderBufferEntry entry(buffer);

        mCache.add(entry);
        mSize += size;

        RENDER_BUFFER_LOGD("Added %s render buffer (%dx%d)",
                RenderBuffer::formatName(buffer->getFormat()),
                buffer->getWidth(), buffer->getHeight());

        return true;
    } else {
        RENDER_BUFFER_LOGD("Deleted %s render buffer (%dx%d) Size=%d, MaxSize=%d",
        RenderBuffer::formatName(buffer->getFormat()),
                 buffer->getWidth(), buffer->getHeight(), size, mMaxSize);
        delete buffer;
    }
    return false;
}

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