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

#ifndef SkPixmap_DEFINED
#define SkPixmap_DEFINED

#include "SkColor.h"
#include "SkImageInfo.h"

class SkColorTable;
struct SkMask;

/**
 *  Pairs SkImageInfo with actual pixels and rowbytes. This class does not try to manage the
 *  lifetime of the pixel memory (nor the colortable if provided).
 */
class SK_API SkPixmap {
public:
    SkPixmap()
        : fPixels(NULL), fCTable(NULL), fRowBytes(0), fInfo(SkImageInfo::MakeUnknown(0, 0))
    {}

    SkPixmap(const SkImageInfo& info, const void* addr, size_t rowBytes,
             SkColorTable* ctable = NULL)
        : fPixels(addr), fCTable(ctable), fRowBytes(rowBytes), fInfo(info)
    {
        if (kIndex_8_SkColorType == info.colorType()) {
            SkASSERT(ctable);
        } else {
            SkASSERT(NULL == ctable);
        }
    }

    void reset();
    void reset(const SkImageInfo& info, const void* addr, size_t rowBytes,
               SkColorTable* ctable = NULL);
    void reset(const SkImageInfo& info) {
        this->reset(info, NULL, 0, NULL);
    }

    /**
     *  If supported, set this pixmap to point to the pixels in the specified mask and return true.
     *  On failure, return false and set this pixmap to empty.
     */
    bool SK_WARN_UNUSED_RESULT reset(const SkMask&);

    /**
     *  Computes the intersection of area and this pixmap. If that intersection is non-empty,
     *  set subset to that intersection and return true.
     *
     *  On failure, return false and ignore the subset parameter.
     */
    bool SK_WARN_UNUSED_RESULT extractSubset(SkPixmap* subset, const SkIRect& area) const;

    const SkImageInfo& info() const { return fInfo; }
    size_t rowBytes() const { return fRowBytes; }
    const void* addr() const { return fPixels; }
    SkColorTable* ctable() const { return fCTable; }

    int width() const { return fInfo.width(); }
    int height() const { return fInfo.height(); }
    SkColorType colorType() const { return fInfo.colorType(); }
    SkAlphaType alphaType() const { return fInfo.alphaType(); }
    bool isOpaque() const { return fInfo.isOpaque(); }

    SkIRect bounds() const { return SkIRect::MakeWH(this->width(), this->height()); }

    uint64_t getSize64() const { return sk_64_mul(fInfo.height(), fRowBytes); }
    uint64_t getSafeSize64() const { return fInfo.getSafeSize64(fRowBytes); }
    size_t getSafeSize() const { return fInfo.getSafeSize(fRowBytes); }

    const uint32_t* addr32() const {
        SkASSERT(4 == SkColorTypeBytesPerPixel(fInfo.colorType()));
        return reinterpret_cast<const uint32_t*>(fPixels);
    }

    const uint16_t* addr16() const {
        SkASSERT(2 == SkColorTypeBytesPerPixel(fInfo.colorType()));
        return reinterpret_cast<const uint16_t*>(fPixels);
    }

    const uint8_t* addr8() const {
        SkASSERT(1 == SkColorTypeBytesPerPixel(fInfo.colorType()));
        return reinterpret_cast<const uint8_t*>(fPixels);
    }

    const uint32_t* addr32(int x, int y) const {
        SkASSERT((unsigned)x < (unsigned)fInfo.width());
        SkASSERT((unsigned)y < (unsigned)fInfo.height());
        return (const uint32_t*)((const char*)this->addr32() + y * fRowBytes + (x << 2));
    }
    const uint16_t* addr16(int x, int y) const {
        SkASSERT((unsigned)x < (unsigned)fInfo.width());
        SkASSERT((unsigned)y < (unsigned)fInfo.height());
        return (const uint16_t*)((const char*)this->addr16() + y * fRowBytes + (x << 1));
    }
    const uint8_t* addr8(int x, int y) const {
        SkASSERT((unsigned)x < (unsigned)fInfo.width());
        SkASSERT((unsigned)y < (unsigned)fInfo.height());
        return (const uint8_t*)((const char*)this->addr8() + y * fRowBytes + (x << 0));
    }
    const void* addr(int x, int y) const {
        return (const char*)fPixels + fInfo.computeOffset(x, y, fRowBytes);
    }

    // Writable versions

    void* writable_addr() const { return const_cast<void*>(fPixels); }
    uint32_t* writable_addr32(int x, int y) const {
        return const_cast<uint32_t*>(this->addr32(x, y));
    }
    uint16_t* writable_addr16(int x, int y) const {
        return const_cast<uint16_t*>(this->addr16(x, y));
    }
    uint8_t* writable_addr8(int x, int y) const {
        return const_cast<uint8_t*>(this->addr8(x, y));
    }

    // copy methods

    bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes,
                    int srcX, int srcY) const;
    bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes) const {
        return this->readPixels(dstInfo, dstPixels, dstRowBytes, 0, 0);
    }
    bool readPixels(const SkPixmap& dst, int srcX, int srcY) const {
        return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), srcX, srcY);
    }
    bool readPixels(const SkPixmap& dst) const {
        return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), 0, 0);
    }

    /**
     *  Returns true if pixels were written to (e.g. if colorType is kUnknown_SkColorType, this
     *  will return false). If subset does not intersect the bounds of this pixmap, returns false.
     */
    bool erase(SkColor, const SkIRect& subset) const;

    bool erase(SkColor color) const { return this->erase(color, this->bounds()); }

private:
    const void*     fPixels;
    SkColorTable*   fCTable;
    size_t          fRowBytes;
    SkImageInfo     fInfo;
};

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

class SK_API SkAutoPixmapStorage : public SkPixmap {
public:
    SkAutoPixmapStorage();
    ~SkAutoPixmapStorage();

    /**
     *  Try to allocate memory for the pixels needed to match the specified Info. On success
     *  return true and fill out the pixmap to point to that memory. The storage will be freed
     *  when this object is destroyed, or if another call to tryAlloc() or alloc() is made.
     *
     *  On failure, return false and reset() the pixmap to empty.
     */
    bool tryAlloc(const SkImageInfo&);

    /**
     *  Allocate memory for the pixels needed to match the specified Info and fill out the pixmap
     *  to point to that memory. The storage will be freed when this object is destroyed,
     *  or if another call to tryAlloc() or alloc() is made.
     *
     *  If the memory cannot be allocated, calls sk_throw().
     */
    void alloc(const SkImageInfo&);

    // We wrap these so we can clear our internal storage

    void reset() {
        this->freeStorage();
        this->INHERITED::reset();
    }
    void reset(const SkImageInfo& info, const void* addr, size_t rb, SkColorTable* ctable = NULL) {
        this->freeStorage();
        this->INHERITED::reset(info, addr, rb, ctable);
    }
    void reset(const SkImageInfo& info) {
        this->freeStorage();
        this->INHERITED::reset(info);
    }
    bool SK_WARN_UNUSED_RESULT reset(const SkMask& mask) {
        this->freeStorage();
        return this->INHERITED::reset(mask);
    }

private:
    void*   fStorage;

    void freeStorage() {
        sk_free(fStorage);
        fStorage = NULL;
    }

    typedef SkPixmap INHERITED;
};

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

class SK_API SkAutoPixmapUnlock : ::SkNoncopyable {
public:
    SkAutoPixmapUnlock() : fUnlockProc(NULL), fIsLocked(false) {}
    SkAutoPixmapUnlock(const SkPixmap& pm, void (*unlock)(void*), void* ctx)
        : fUnlockProc(unlock), fUnlockContext(ctx), fPixmap(pm), fIsLocked(true)
    {}
    ~SkAutoPixmapUnlock() { this->unlock(); }

    /**
     *  Return the currently locked pixmap. Undefined if it has been unlocked.
     */
    const SkPixmap& pixmap() const {
        SkASSERT(this->isLocked());
        return fPixmap;
    }

    bool isLocked() const { return fIsLocked; }

    /**
     *  Unlocks the pixmap. Can safely be called more than once as it will only call the underlying
     *  unlock-proc once.
     */
    void unlock() {
        if (fUnlockProc) {
            SkASSERT(fIsLocked);
            fUnlockProc(fUnlockContext);
            fUnlockProc = NULL;
            fIsLocked = false;
        }
    }

    /**
     *  If there is a currently locked pixmap, unlock it, then copy the specified pixmap
     *  and (optional) unlock proc/context.
     */
    void reset(const SkPixmap& pm, void (*unlock)(void*), void* ctx);

private:
    void        (*fUnlockProc)(void*);
    void*       fUnlockContext;
    SkPixmap    fPixmap;
    bool        fIsLocked;

    friend class SkBitmap;
};

#endif
