/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "SkDevice.h"
#include "SkDraw.h"
#include "SkImageFilter.h"
#include "SkMetaData.h"
#include "SkRect.h"

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

SkDevice::SkDevice(const SkBitmap& bitmap) : fBitmap(bitmap) {
    fOrigin.setZero();
    fMetaData = NULL;
}

SkDevice::SkDevice(SkBitmap::Config config, int width, int height, bool isOpaque) {
    fOrigin.setZero();
    fMetaData = NULL;

    fBitmap.setConfig(config, width, height);
    fBitmap.allocPixels();
    fBitmap.setIsOpaque(isOpaque);
    if (!isOpaque) {
        fBitmap.eraseColor(0);
    }
}

SkDevice::~SkDevice() {
    delete fMetaData;
}

SkDevice* SkDevice::createCompatibleDevice(SkBitmap::Config config,
                                           int width, int height,
                                           bool isOpaque) {
    return this->onCreateCompatibleDevice(config, width, height,
                                          isOpaque, kGeneral_Usage);
}

SkDevice* SkDevice::createCompatibleDeviceForSaveLayer(SkBitmap::Config config,
                                                       int width, int height,
                                                       bool isOpaque) {
    return this->onCreateCompatibleDevice(config, width, height,
                                          isOpaque, kSaveLayer_Usage);
}

SkDevice* SkDevice::onCreateCompatibleDevice(SkBitmap::Config config,
                                             int width, int height,
                                             bool isOpaque,
                                             Usage usage) {
    return SkNEW_ARGS(SkDevice,(config, width, height, isOpaque));
}

SkMetaData& SkDevice::getMetaData() {
    // metadata users are rare, so we lazily allocate it. If that changes we
    // can decide to just make it a field in the device (rather than a ptr)
    if (NULL == fMetaData) {
        fMetaData = new SkMetaData;
    }
    return *fMetaData;
}

void SkDevice::lockPixels() {
    if (fBitmap.lockPixelsAreWritable()) {
        fBitmap.lockPixels();
    }
}

void SkDevice::unlockPixels() {
    if (fBitmap.lockPixelsAreWritable()) {
        fBitmap.unlockPixels();
    }
}

const SkBitmap& SkDevice::accessBitmap(bool changePixels) {
    const SkBitmap& bitmap = this->onAccessBitmap(&fBitmap);
    if (changePixels) {
        bitmap.notifyPixelsChanged();
    }
    return bitmap;
}

void SkDevice::getGlobalBounds(SkIRect* bounds) const {
    if (bounds) {
        bounds->setXYWH(fOrigin.x(), fOrigin.y(),
                        fBitmap.width(), fBitmap.height());
    }
}

void SkDevice::clear(SkColor color) {
    fBitmap.eraseColor(color);
}

const SkBitmap& SkDevice::onAccessBitmap(SkBitmap* bitmap) {return *bitmap;}

void SkDevice::setMatrixClip(const SkMatrix& matrix, const SkRegion& region,
                             const SkClipStack& clipStack) {
}

bool SkDevice::filterImage(SkImageFilter*, const SkBitmap& src,
                           const SkMatrix& ctm,
                           SkBitmap* result, SkIPoint* offset) {
    return false;
}

bool SkDevice::allowImageFilter(SkImageFilter*) {
    return true;
}

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

bool SkDevice::readPixels(SkBitmap* bitmap, int x, int y,
                          SkCanvas::Config8888 config8888) {
    if (SkBitmap::kARGB_8888_Config != bitmap->config() ||
        NULL != bitmap->getTexture()) {
        return false;
    }

    const SkBitmap& src = this->accessBitmap(false);

    SkIRect srcRect = SkIRect::MakeXYWH(x, y, bitmap->width(),
                                              bitmap->height());
    SkIRect devbounds = SkIRect::MakeWH(src.width(), src.height());
    if (!srcRect.intersect(devbounds)) {
        return false;
    }

    SkBitmap tmp;
    SkBitmap* bmp;
    if (bitmap->isNull()) {
        tmp.setConfig(SkBitmap::kARGB_8888_Config, bitmap->width(),
                                                   bitmap->height());
        if (!tmp.allocPixels()) {
            return false;
        }
        bmp = &tmp;
    } else {
        bmp = bitmap;
    }

    SkIRect subrect = srcRect;
    subrect.offset(-x, -y);
    SkBitmap bmpSubset;
    bmp->extractSubset(&bmpSubset, subrect);

    bool result = this->onReadPixels(bmpSubset,
                                     srcRect.fLeft,
                                     srcRect.fTop,
                                     config8888);
    if (result && bmp == &tmp) {
        tmp.swap(*bitmap);
    }
    return result;
}

#ifdef SK_CPU_LENDIAN
    #if   24 == SK_A32_SHIFT && 16 == SK_R32_SHIFT && \
           8 == SK_G32_SHIFT &&  0 == SK_B32_SHIFT
        const SkCanvas::Config8888 SkDevice::kPMColorAlias =
            SkCanvas::kBGRA_Premul_Config8888;
    #elif 24 == SK_A32_SHIFT &&  0 == SK_R32_SHIFT && \
           8 == SK_G32_SHIFT && 16 == SK_B32_SHIFT
        const SkCanvas::Config8888 SkDevice::kPMColorAlias =
            SkCanvas::kRGBA_Premul_Config8888;
    #else
        const SkCanvas::Config8888 SkDevice::kPMColorAlias =
            (SkCanvas::Config8888) -1;
    #endif
#else
    #if    0 == SK_A32_SHIFT &&   8 == SK_R32_SHIFT && \
          16 == SK_G32_SHIFT &&  24 == SK_B32_SHIFT
        const SkCanvas::Config8888 SkDevice::kPMColorAlias =
            SkCanvas::kBGRA_Premul_Config8888;
    #elif  0 == SK_A32_SHIFT &&  24 == SK_R32_SHIFT && \
          16 == SK_G32_SHIFT &&   8 == SK_B32_SHIFT
        const SkCanvas::Config8888 SkDevice::kPMColorAlias =
            SkCanvas::kRGBA_Premul_Config8888;
    #else
        const SkCanvas::Config8888 SkDevice::kPMColorAlias =
            (SkCanvas::Config8888) -1;
    #endif
#endif

#include <SkConfig8888.h>

bool SkDevice::onReadPixels(const SkBitmap& bitmap,
                            int x, int y,
                            SkCanvas::Config8888 config8888) {
    SkASSERT(SkBitmap::kARGB_8888_Config == bitmap.config());
    SkASSERT(!bitmap.isNull());
    SkASSERT(SkIRect::MakeWH(this->width(), this->height()).contains(SkIRect::MakeXYWH(x, y, bitmap.width(), bitmap.height())));

    SkIRect srcRect = SkIRect::MakeXYWH(x, y, bitmap.width(),
                                              bitmap.height());
    const SkBitmap& src = this->accessBitmap(false);

    SkBitmap subset;
    if (!src.extractSubset(&subset, srcRect)) {
        return false;
    }
    if (SkBitmap::kARGB_8888_Config != subset.config()) {
        // It'd be preferable to do this directly to bitmap.
        subset.copyTo(&subset, SkBitmap::kARGB_8888_Config);
    }
    SkAutoLockPixels alp(bitmap);
    uint32_t* bmpPixels = reinterpret_cast<uint32_t*>(bitmap.getPixels());
    SkCopyBitmapToConfig8888(bmpPixels, bitmap.rowBytes(), config8888, subset);
    return true;
}

void SkDevice::writePixels(const SkBitmap& bitmap,
                           int x, int y,
                           SkCanvas::Config8888 config8888) {
    if (bitmap.isNull() || bitmap.getTexture()) {
        return;
    }
    const SkBitmap* sprite = &bitmap;
    // check whether we have to handle a config8888 that doesn't match SkPMColor
    if (SkBitmap::kARGB_8888_Config == bitmap.config() &&
        SkCanvas::kNative_Premul_Config8888 != config8888 &&
        kPMColorAlias != config8888) {

        // We're going to have to convert from a config8888 to the native config
        // First we clip to the device bounds.
        SkBitmap dstBmp = this->accessBitmap(true);
        SkIRect spriteRect = SkIRect::MakeXYWH(x, y,
                                               bitmap.width(), bitmap.height());
        SkIRect devRect = SkIRect::MakeWH(dstBmp.width(), dstBmp.height());
        if (!spriteRect.intersect(devRect)) {
            return;
        }

        // write directly to the device if it has pixels and is SkPMColor
        bool drawSprite;
        if (SkBitmap::kARGB_8888_Config == dstBmp.config() && !dstBmp.isNull()) {
            // we can write directly to the dst when doing the conversion
            dstBmp.extractSubset(&dstBmp, spriteRect);
            drawSprite = false;
        } else {
            // we convert to a temporary bitmap and draw that as a sprite
            dstBmp.setConfig(SkBitmap::kARGB_8888_Config,
                             spriteRect.width(),
                             spriteRect.height());
            if (!dstBmp.allocPixels()) {
                return;
            }
            drawSprite = true;
        }

        // copy pixels to dstBmp and convert from config8888 to native config.
        SkAutoLockPixels alp(bitmap);
        uint32_t* srcPixels = bitmap.getAddr32(spriteRect.fLeft - x,
                                               spriteRect.fTop - y);
        SkCopyConfig8888ToBitmap(dstBmp,
                                 srcPixels,
                                 bitmap.rowBytes(),
                                 config8888);

        if (drawSprite) {
            // we've clipped the sprite when we made a copy
            x = spriteRect.fLeft;
            y = spriteRect.fTop;
            sprite = &dstBmp;
        } else {
            return;
        }
    }

    SkPaint paint;
    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
    SkCanvas canvas(this);
    canvas.drawSprite(*sprite, x, y, &paint);
}

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

void SkDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
    draw.drawPaint(paint);
}

void SkDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
                              const SkPoint pts[], const SkPaint& paint) {
    draw.drawPoints(mode, count, pts, paint);
}

void SkDevice::drawRect(const SkDraw& draw, const SkRect& r,
                            const SkPaint& paint) {
    draw.drawRect(r, paint);
}

void SkDevice::drawPath(const SkDraw& draw, const SkPath& path,
                        const SkPaint& paint, const SkMatrix* prePathMatrix,
                        bool pathIsMutable) {
    draw.drawPath(path, paint, prePathMatrix, pathIsMutable);
}

void SkDevice::drawBitmap(const SkDraw& draw, const SkBitmap& bitmap,
                          const SkIRect* srcRect,
                          const SkMatrix& matrix, const SkPaint& paint) {
    SkBitmap        tmp;    // storage if we need a subset of bitmap
    const SkBitmap* bitmapPtr = &bitmap;

    if (srcRect) {
        if (!bitmap.extractSubset(&tmp, *srcRect)) {
            return;     // extraction failed
        }
        bitmapPtr = &tmp;
    }
    draw.drawBitmap(*bitmapPtr, matrix, paint);
}

void SkDevice::drawSprite(const SkDraw& draw, const SkBitmap& bitmap,
                              int x, int y, const SkPaint& paint) {
    draw.drawSprite(bitmap, x, y, paint);
}

void SkDevice::drawText(const SkDraw& draw, const void* text, size_t len,
                            SkScalar x, SkScalar y, const SkPaint& paint) {
    draw.drawText((const char*)text, len, x, y, paint);
}

void SkDevice::drawPosText(const SkDraw& draw, const void* text, size_t len,
                               const SkScalar xpos[], SkScalar y,
                               int scalarsPerPos, const SkPaint& paint) {
    draw.drawPosText((const char*)text, len, xpos, y, scalarsPerPos, paint);
}

void SkDevice::drawTextOnPath(const SkDraw& draw, const void* text,
                                  size_t len, const SkPath& path,
                                  const SkMatrix* matrix,
                                  const SkPaint& paint) {
    draw.drawTextOnPath((const char*)text, len, path, matrix, paint);
}

#ifdef SK_BUILD_FOR_ANDROID
void SkDevice::drawPosTextOnPath(const SkDraw& draw, const void* text, size_t len,
                                     const SkPoint pos[], const SkPaint& paint,
                                     const SkPath& path, const SkMatrix* matrix) {
    draw.drawPosTextOnPath((const char*)text, len, pos, paint, path, matrix);
}
#endif

void SkDevice::drawVertices(const SkDraw& draw, SkCanvas::VertexMode vmode,
                                int vertexCount,
                                const SkPoint verts[], const SkPoint textures[],
                                const SkColor colors[], SkXfermode* xmode,
                                const uint16_t indices[], int indexCount,
                                const SkPaint& paint) {
    draw.drawVertices(vmode, vertexCount, verts, textures, colors, xmode,
                      indices, indexCount, paint);
}

void SkDevice::drawDevice(const SkDraw& draw, SkDevice* device,
                              int x, int y, const SkPaint& paint) {
    const SkBitmap& src = device->accessBitmap(false);
    draw.drawSprite(src, x, y, paint);
}

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

bool SkDevice::filterTextFlags(const SkPaint& paint, TextFlags* flags) {
    if (!paint.isLCDRenderText() || !paint.isAntiAlias()) {
        // we're cool with the paint as is
        return false;
    }

    if (SkBitmap::kARGB_8888_Config != fBitmap.config() ||
        paint.getRasterizer() ||
        paint.getPathEffect() ||
        paint.isFakeBoldText() ||
        paint.getStyle() != SkPaint::kFill_Style ||
        !SkXfermode::IsMode(paint.getXfermode(), SkXfermode::kSrcOver_Mode)) {
        // turn off lcd
        flags->fFlags = paint.getFlags() & ~SkPaint::kLCDRenderText_Flag;
        flags->fHinting = paint.getHinting();
        return true;
    }
    // we're cool with the paint as is
    return false;
}