/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "Sk64.h"
#include "SkLazyPixelRef.h"
#include "SkColorTable.h"
#include "SkData.h"
#include "SkImageCache.h"
#include "SkImagePriv.h"

#if LAZY_CACHE_STATS
#include "SkThread.h"

int32_t SkLazyPixelRef::gCacheHits;
int32_t SkLazyPixelRef::gCacheMisses;
#endif

SkLazyPixelRef::SkLazyPixelRef(SkData* data, SkBitmapFactory::DecodeProc proc, SkImageCache* cache)
    // Pass NULL for the Mutex so that the default (ring buffer) will be used.
    : INHERITED(NULL)
    , fDecodeProc(proc)
    , fImageCache(cache)
    , fCacheId(SkImageCache::UNINITIALIZED_ID)
    , fRowBytes(0) {
    SkASSERT(fDecodeProc != NULL);
    if (NULL == data) {
        fData = SkData::NewEmpty();
        fErrorInDecoding = true;
    } else {
        fData = data;
        fData->ref();
        fErrorInDecoding = data->size() == 0;
    }
    SkASSERT(cache != NULL);
    cache->ref();
    // Since this pixel ref bases its data on encoded data, it should never change.
    this->setImmutable();
}

SkLazyPixelRef::~SkLazyPixelRef() {
    SkASSERT(fData != NULL);
    fData->unref();
    SkASSERT(fImageCache);
    if (fCacheId != SkImageCache::UNINITIALIZED_ID) {
        fImageCache->throwAwayCache(fCacheId);
    }
    fImageCache->unref();
}

static size_t ComputeMinRowBytesAndSize(const SkImage::Info& info, size_t* rowBytes) {
    *rowBytes = SkImageMinRowBytes(info);

    Sk64 safeSize;
    safeSize.setZero();
    if (info.fHeight > 0) {
        safeSize.setMul(info.fHeight, SkToS32(*rowBytes));
    }
    SkASSERT(!safeSize.isNeg());
    return safeSize.is32() ? safeSize.get32() : 0;
}

void* SkLazyPixelRef::onLockPixels(SkColorTable**) {
    if (fErrorInDecoding) {
        return NULL;
    }
    SkBitmapFactory::Target target;
    // Check to see if the pixels still exist in the cache.
    if (SkImageCache::UNINITIALIZED_ID == fCacheId) {
        target.fAddr = NULL;
    } else {
        SkImageCache::DataStatus status;
        target.fAddr = fImageCache->pinCache(fCacheId, &status);
        if (target.fAddr == NULL) {
            fCacheId = SkImageCache::UNINITIALIZED_ID;
        } else {
            if (SkImageCache::kRetained_DataStatus == status) {
#if LAZY_CACHE_STATS
                sk_atomic_inc(&gCacheHits);
#endif
                return target.fAddr;
            }
            SkASSERT(SkImageCache::kUninitialized_DataStatus == status);
        }
        // Cache miss. Either pinCache returned NULL or it returned a memory address without the old
        // data
#if LAZY_CACHE_STATS
        sk_atomic_inc(&gCacheMisses);
#endif
    }
    SkImage::Info info;
    SkASSERT(fData != NULL && fData->size() > 0);
    if (NULL == target.fAddr) {
        // Determine the size of the image in order to determine how much memory to allocate.
        // FIXME: As an optimization, only do this part once.
        fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, NULL);
        if (fErrorInDecoding) {
            // We can only reach here if fCacheId was already set to UNINITIALIZED_ID, or if
            // pinCache returned NULL, in which case it was reset to UNINITIALIZED_ID.
            SkASSERT(SkImageCache::UNINITIALIZED_ID == fCacheId);
            return NULL;
        }

        size_t bytes = ComputeMinRowBytesAndSize(info, &target.fRowBytes);
        target.fAddr = fImageCache->allocAndPinCache(bytes, &fCacheId);
        if (NULL == target.fAddr) {
            // Space could not be allocated.
            // Just like the last assert, fCacheId must be UNINITIALIZED_ID.
            SkASSERT(SkImageCache::UNINITIALIZED_ID == fCacheId);
            return NULL;
        }
    } else {
        // pinCache returned purged memory to which target.fAddr already points. Set
        // target.fRowBytes properly.
        target.fRowBytes = fRowBytes;
        // Assume that the size is correct, since it was determined by this same function
        // previously.
    }
    SkASSERT(target.fAddr != NULL);
    SkASSERT(SkImageCache::UNINITIALIZED_ID != fCacheId);
    fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, &target);
    if (fErrorInDecoding) {
        fImageCache->throwAwayCache(fCacheId);
        fCacheId = SkImageCache::UNINITIALIZED_ID;
        return NULL;
    }
    // Upon success, store fRowBytes so it can be used in case pinCache later returns purged memory.
    fRowBytes = target.fRowBytes;
    return target.fAddr;
}

void SkLazyPixelRef::onUnlockPixels() {
    if (fErrorInDecoding) {
        return;
    }
    if (fCacheId != SkImageCache::UNINITIALIZED_ID) {
        fImageCache->releaseCache(fCacheId);
    }
}

SkData* SkLazyPixelRef::onRefEncodedData() {
    fData->ref();
    return fData;
}

#include "SkImagePriv.h"

static bool init_from_info(SkBitmap* bm, const SkImage::Info& info,
                           size_t rowBytes) {
    bool isOpaque;
    SkBitmap::Config config = SkImageInfoToBitmapConfig(info, &isOpaque);
    if (SkBitmap::kNo_Config == config) {
        return false;
    }

    bm->setConfig(config, info.fWidth, info.fHeight, rowBytes);
    bm->setIsOpaque(isOpaque);
    return bm->allocPixels();
}

bool SkLazyPixelRef::onImplementsDecodeInto() {
    return true;
}

bool SkLazyPixelRef::onDecodeInto(int pow2, SkBitmap* bitmap) {
    SkASSERT(fData != NULL && fData->size() > 0);
    if (fErrorInDecoding) {
        return false;
    }

    SkImage::Info info;
    // Determine the size of the image in order to determine how much memory to allocate.
    // FIXME: As an optimization, only do this part once.
    fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, NULL);
    if (fErrorInDecoding) {
        return false;
    }

    SkBitmapFactory::Target target;
    (void)ComputeMinRowBytesAndSize(info, &target.fRowBytes);

    SkBitmap tmp;
    if (!init_from_info(&tmp, info, target.fRowBytes)) {
        return false;
    }

    target.fAddr = tmp.getPixels();
    fErrorInDecoding = !fDecodeProc(fData->data(), fData->size(), &info, &target);
    if (fErrorInDecoding) {
        return false;
    }

    *bitmap = tmp;
    return true;
}
