/*
    Copyright 2010 Google Inc.

    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 "GrTextureCache.h"
#include "GrTexture.h"

GrTextureEntry::GrTextureEntry(const GrTextureKey& key, GrTexture* texture)
        : fKey(key), fTexture(texture) {
    fLockCount = 0;
    fPrev = fNext = NULL;

    // we assume ownership of the texture, and will unref it when we die
    GrAssert(texture);
}

GrTextureEntry::~GrTextureEntry() {
    fTexture->unref();
}

#if GR_DEBUG
void GrTextureEntry::validate() const {
    GrAssert(fLockCount >= 0);
    GrAssert(fTexture);
    fTexture->validate();
}
#endif

///////////////////////////////////////////////////////////////////////////////

GrTextureCache::GrTextureCache(int maxCount, size_t maxBytes) :
        fMaxCount(maxCount),
        fMaxBytes(maxBytes) {
    fEntryCount          = 0;
    fEntryBytes          = 0;
    fClientDetachedCount = 0;
    fClientDetachedBytes = 0;

    fHead = fTail = NULL;
}

GrTextureCache::~GrTextureCache() {
    GrAutoTextureCacheValidate atcv(this);

    this->removeAll();
}

void GrTextureCache::getLimits(int* maxTextures, size_t* maxTextureBytes) const{
    if (maxTextures) {
        *maxTextures = fMaxCount;
    }
    if (maxTextureBytes) {
        *maxTextureBytes = fMaxBytes;
    }
}

void GrTextureCache::setLimits(int maxTextures, size_t maxTextureBytes) {
    bool smaller = (maxTextures < fMaxCount) || (maxTextureBytes < fMaxBytes);

    fMaxCount = maxTextures;
    fMaxBytes = maxTextureBytes;

    if (smaller) {
        this->purgeAsNeeded();
    }
}

void GrTextureCache::internalDetach(GrTextureEntry* entry,
                                    bool clientDetach) {
    GrTextureEntry* prev = entry->fPrev;
    GrTextureEntry* next = entry->fNext;

    if (prev) {
        prev->fNext = next;
    } else {
        fHead = next;
    }
    if (next) {
        next->fPrev = prev;
    } else {
        fTail = prev;
    }

    // update our stats
    if (clientDetach) {
        fClientDetachedCount += 1;
        fClientDetachedBytes += entry->texture()->sizeInBytes();
    } else {
        fEntryCount -= 1;
        fEntryBytes -= entry->texture()->sizeInBytes();
    }
}

void GrTextureCache::attachToHead(GrTextureEntry* entry,
                                  bool clientReattach) {
    entry->fPrev = NULL;
    entry->fNext = fHead;
    if (fHead) {
        fHead->fPrev = entry;
    }
    fHead = entry;
    if (NULL == fTail) {
        fTail = entry;
    }

    // update our stats
    if (clientReattach) {
        fClientDetachedCount -= 1;
        fClientDetachedBytes -= entry->texture()->sizeInBytes();
    } else {
        fEntryCount += 1;
        fEntryBytes += entry->texture()->sizeInBytes();
    }
}

class GrTextureCache::Key {
    typedef GrTextureEntry T;

    const GrTextureKey& fKey;
public:
    Key(const GrTextureKey& key) : fKey(key) {}

    uint32_t getHash() const { return fKey.hashIndex(); }

    static bool LT(const T& entry, const Key& key) {
        return entry.key() < key.fKey;
    }
    static bool EQ(const T& entry, const Key& key) {
        return entry.key() == key.fKey;
    }
#if GR_DEBUG
    static uint32_t GetHash(const T& entry) {
        return entry.key().hashIndex();
    }
    static bool LT(const T& a, const T& b) {
        return a.key() < b.key();
    }
    static bool EQ(const T& a, const T& b) {
        return a.key() == b.key();
    }
#endif
};

GrTextureEntry* GrTextureCache::findAndLock(const GrTextureKey& key) {
    GrAutoTextureCacheValidate atcv(this);

    GrTextureEntry* entry = fCache.find(key);
    if (entry) {
        this->internalDetach(entry, false);
        this->attachToHead(entry, false);
        // mark the entry as "busy" so it doesn't get purged
        entry->lock();
    }
    return entry;
}

GrTextureEntry* GrTextureCache::createAndLock(const GrTextureKey& key,
                                              GrTexture* texture) {
    GrAutoTextureCacheValidate atcv(this);

    GrTextureEntry* entry = new GrTextureEntry(key, texture);

    this->attachToHead(entry, false);
    fCache.insert(key, entry);

#if GR_DUMP_TEXTURE_UPLOAD
    GrPrintf("--- add texture to cache %p, count=%d bytes= %d %d\n",
             entry, fEntryCount, texture->sizeInBytes(), fEntryBytes);
#endif

    // mark the entry as "busy" so it doesn't get purged
    entry->lock();
    this->purgeAsNeeded();
    return entry;
}

void GrTextureCache::detach(GrTextureEntry* entry) {
    internalDetach(entry, true);
    fCache.remove(entry->fKey, entry);
}

void GrTextureCache::reattachAndUnlock(GrTextureEntry* entry) {
    attachToHead(entry, true);
    fCache.insert(entry->key(), entry);
    unlock(entry);
}

void GrTextureCache::unlock(GrTextureEntry* entry) {
    GrAutoTextureCacheValidate atcv(this);

    GrAssert(entry);
    GrAssert(entry->isLocked());
    GrAssert(fCache.find(entry->key()));

    entry->unlock();
    this->purgeAsNeeded();
}

void GrTextureCache::purgeAsNeeded() {
    GrAutoTextureCacheValidate atcv(this);

    GrTextureEntry* entry = fTail;
    while (entry) {
        if (fEntryCount <= fMaxCount && fEntryBytes <= fMaxBytes) {
            break;
        }

        GrTextureEntry* prev = entry->fPrev;
        if (!entry->isLocked()) {
            // remove from our cache
            fCache.remove(entry->fKey, entry);

            // remove from our llist
            this->internalDetach(entry, false);

#if GR_DUMP_TEXTURE_UPLOAD
            GrPrintf("--- ~texture from cache %p [%d %d]\n", entry->texture(),
                     entry->texture()->width(),
                     entry->texture()->height());
#endif
            delete entry;
        }
        entry = prev;
    }
}

void GrTextureCache::removeAll() {
    GrAssert(!fClientDetachedCount);
    GrAssert(!fClientDetachedBytes);

    GrTextureEntry* entry = fHead;
    while (entry) {
        GrAssert(!entry->isLocked());

        GrTextureEntry* next = entry->fNext;
        delete entry;
        entry = next;
    }

    fCache.removeAll();
    fHead = fTail = NULL;
    fEntryCount = 0;
    fEntryBytes = 0;
}

///////////////////////////////////////////////////////////////////////////////

#if GR_DEBUG
static int countMatches(const GrTextureEntry* head, const GrTextureEntry* target) {
    const GrTextureEntry* entry = head;
    int count = 0;
    while (entry) {
        if (target == entry) {
            count += 1;
        }
        entry = entry->next();
    }
    return count;
}

#if GR_DEBUG
static bool both_zero_or_nonzero(int count, size_t bytes) {
    return (count == 0 && bytes == 0) || (count > 0 && bytes > 0);
}
#endif

void GrTextureCache::validate() const {
    GrAssert(!fHead == !fTail);
    GrAssert(both_zero_or_nonzero(fEntryCount, fEntryBytes));
    GrAssert(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes));
    GrAssert(fClientDetachedBytes <= fEntryBytes);
    GrAssert(fClientDetachedCount <= fEntryCount);
    GrAssert((fEntryCount - fClientDetachedCount) == fCache.count());

    fCache.validate();

    GrTextureEntry* entry = fHead;
    int count = 0;
    size_t bytes = 0;
    while (entry) {
        entry->validate();
        GrAssert(fCache.find(entry->key()));
        count += 1;
        bytes += entry->texture()->sizeInBytes();
        entry = entry->fNext;
    }
    GrAssert(count == fEntryCount - fClientDetachedCount);
    GrAssert(bytes == fEntryBytes  - fClientDetachedBytes);

    count = 0;
    for (entry = fTail; entry; entry = entry->fPrev) {
        count += 1;
    }
    GrAssert(count == fEntryCount - fClientDetachedCount);

    for (int i = 0; i < count; i++) {
        int matches = countMatches(fHead, fCache.getArray()[i]);
        GrAssert(1 == matches);
    }
}
#endif