/*
 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "ImageBuffer.h"

#include "BitmapImage.h"
#include "GraphicsContext.h"
#include "ImageData.h"
#include "ImageDecoder.h"
#include "NotImplemented.h"
#include "PainterOpenVG.h"
#include "SurfaceOpenVG.h"
#include "TiledImageOpenVG.h"
#include <runtime/Operations.h>
#include <runtime/TypedArrayInlines.h>
//#include "JPEGImageEncoder.h"
//#include "PNGImageEncoder.h"


#include <openvg.h>
#include <wtf/Assertions.h>

// ImageData is RGBA on big endian and ABGR on little endian.
// VGImageFormat always works on the same bit order no matter the endianness.
// Therefore, we have to take endianness into account when converting from
// and to VGImage pixel data.
#define IMAGEDATA_VG_UNMULTIPLIED_FORMAT VGUtils::endianAwareImageFormat(VG_sRGBA_8888)
#define IMAGEDATA_VG_PREMULTIPLIED_FORMAT VGUtils::endianAwareImageFormat(VG_sRGBA_8888_PRE)
#define IMAGEBUFFER_VG_EXCHANGE_FORMAT VG_sARGB_8888

#ifdef WTF_PLATFORM_BIG_ENDIAN
#define IMAGEBUFFER_A 0
#define IMAGEBUFFER_R 1
#define IMAGEBUFFER_G 2
#define IMAGEBUFFER_B 3
#else
#define IMAGEBUFFER_A 3
#define IMAGEBUFFER_R 2
#define IMAGEBUFFER_G 1
#define IMAGEBUFFER_B 0
#endif

// TODO: We create a lot of contexts here for small vg manipulation. We should
// consider storing a context within the imagebuffer or using a shared context.

namespace WebCore {

ImageBufferData::ImageBufferData(const IntSize& size, ColorSpace colorSpace, bool accelerated, bool& success)
: m_colorSpace(colorSpace)
, m_surface(0)
{
    ASSERT(size.width() > 0);
    ASSERT(size.height() > 0);
    success = false;

    switch (colorSpace) {
    case ColorSpaceSRGB:
        m_format = VG_sARGB_8888; break;
    case ColorSpaceLinearRGB:
        m_format = VG_lARGB_8888; break;
    default:
        ASSERT_NOT_REACHED();
        break;
    }

    m_surface = new SurfaceOpenVG(size, VG_sARGB_8888_PRE, accelerated);

    if (!m_surface->isValid()) {
        delete m_surface;
        m_surface = 0;
        return;
    }

    GraphicsContext ctx(m_surface);
    if (ctx.platformContext()->activePainter())
        ctx.platformContext()->activePainter()->makeCurrent();

    // Clear the ImageBuffer from whatever color was set before to transparent.
    const VGfloat transparent[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
    vgSeti(VG_SCISSORING, VG_FALSE);
    vgSetfv(VG_CLEAR_COLOR, 4, transparent);
    vgClear(0, 0, size.width(), size.height());

    success = true;
}

ImageBufferData::~ImageBufferData()
{
    delete m_surface;
}

PassRefPtr<Uint8ClampedArray> ImageBufferData::getImageData(GraphicsContext* context, const IntRect& rect, const IntSize& size, VGImageFormat format) const
{
    ASSERT(m_surface);

    PassRefPtr<Uint8ClampedArray> result = Uint8ClampedArray::create(rect.width() * rect.height() * 4);
    unsigned char* data = result->data();

    #if PAINTER_DISABLE_READBACK
    return result;
    #endif

    // If this copy operation won't fill all of the pixels in the target image,
    // paint it black in order to avoid random uninitialized pixel garbage.
    if (rect.x() < 0 || rect.y() < 0 || (rect.maxX()) > size.width() || (rect.maxY()) > size.height())
        memset(data, 0, result->length());

    context->platformContext()->activePainter()->makeCurrent();

    // OpenVG ignores pixels that are out of bounds, so we can just
    // call vgReadPixels() without any further safety assurances.
    vgReadPixels(data, rect.width() * 4, format,
        rect.x(), rect.y(), rect.width(), rect.height());
    ASSERT_VG_NO_ERROR();

    return result;
}

void ImageBufferData::putImageData(GraphicsContext* context, Uint8ClampedArray*& source, const IntSize& sourceSize, const IntRect& sourceRect,
		const IntPoint& destPoint, const IntSize& size, VGImageFormat format)
{
    ASSERT(m_surface);

    ASSERT(sourceRect.width() > 0);
    ASSERT(sourceRect.height() > 0);

    // We expect the sourceRect to be a subset of the given source image.
    ASSERT(sourceRect.x() >= 0);
    ASSERT(sourceRect.y() >= 0);
    ASSERT(sourceRect.maxX() <= sourceSize.width());
    ASSERT(sourceRect.maxY() <= sourceSize.height());

    // The target origin point is the combined offset of sourceRect.location()
    // and destPoint.
    int destx = destPoint.x() + sourceRect.x();
    int desty = destPoint.y() + sourceRect.y();
    ASSERT(destx >= 0);
    ASSERT(desty >= 0);
    ASSERT(destx + sourceRect.width() <= size.width());
    ASSERT(desty + sourceRect.height() <= size.height());

    unsigned const char* data = source->data();
    int dataOffset = (sourceRect.y() * sourceSize.width() * 4) + (sourceRect.x() * 4);

    context->platformContext()->activePainter()->makeCurrent();

    vgWritePixels(data + dataOffset, sourceSize.width() * 4, format,
        destx, desty, sourceRect.width(), sourceRect.height());
    ASSERT_VG_NO_ERROR();
}

void ImageBufferData::transformColorSpace(const Vector<int>& lookUpTable)
{
    ASSERT(m_surface);

    #if PAINTER_DISABLE_READBACK
    return;
    #endif

    VGint width = m_surface->width();
    VGint height = m_surface->height();

    VGubyte* data = new VGubyte[width * height * 4];
    VGubyte* currentPixel = data;

    GraphicsContext ctx(m_surface);
    if (ctx.platformContext()->activePainter())
        ctx.platformContext()->activePainter()->makeCurrent();

    vgReadPixels(data, width * 4, IMAGEBUFFER_VG_EXCHANGE_FORMAT, 0, 0, width, height);
    ASSERT_VG_NO_ERROR();

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; currentPixel += 4, x++) {
            currentPixel[IMAGEBUFFER_A] = lookUpTable[currentPixel[IMAGEBUFFER_A]];
            currentPixel[IMAGEBUFFER_R] = lookUpTable[currentPixel[IMAGEBUFFER_R]];
            currentPixel[IMAGEBUFFER_G] = lookUpTable[currentPixel[IMAGEBUFFER_G]];
            currentPixel[IMAGEBUFFER_B] = lookUpTable[currentPixel[IMAGEBUFFER_B]];
        }
    }

    vgWritePixels(data, width * 4, IMAGEBUFFER_VG_EXCHANGE_FORMAT, 0, 0, width, height);
    ASSERT_VG_NO_ERROR();

    delete[] data;
}

ImageBuffer::ImageBuffer(const IntSize& size, float resolutionScale, ColorSpace colorSpace, RenderingMode renderingMode, DeferralMode, bool& success)
    : m_data(size, colorSpace, renderingMode == Accelerated, success)
    , m_size(size)
    , m_logicalSize(size)
    , m_hasAlpha(true)
{
    if (!success) // success has already been set by m_data's constructor.
        return;

    m_context = adoptPtr(new GraphicsContext(m_data.m_surface));
}

ImageBuffer::~ImageBuffer()
{
}

DLL_PUBLIC void ImageBuffer::drawPattern(GraphicsContext* context, const FloatRect& srcRect, const AffineTransform& patternTransform,
                              const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
{
    RefPtr<Image> image = copyImage(DontCopyBackingStore);
    image->drawPattern(context, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
}

DLL_PUBLIC PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior) const
{
    if (copyBehavior == DontCopyBackingStore && m_data.m_surface->isValid() && m_data.m_surface->tiledImage()) {
        m_data.m_surface->tiledImage()->ref();
        return BitmapImage::create(m_data.m_surface->tiledImage(), 0, m_hasAlpha);
    }

    NativeImagePtr image = context()->platformContext()->activePainter()->asNewNativeImage(
        IntRect(IntPoint(0, 0), m_size), m_data.m_format);
    return BitmapImage::create(image);
}

void ImageBuffer::platformTransformColorSpace(const Vector<int>& lookUpTable)
{
    ASSERT(context());
    if (context()->platformContext()->activePainter())
        context()->platformContext()->activePainter()->blitToSurface();
    m_data.transformColorSpace(lookUpTable);
}

DLL_PUBLIC PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem) const
{
    ASSERT(context());
    if (context()->platformContext()->activePainter())
        context()->platformContext()->activePainter()->blitToSurface();
    return m_data.getImageData(context(), rect, m_size, IMAGEDATA_VG_UNMULTIPLIED_FORMAT);
}

DLL_PUBLIC PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem) const
{
    ASSERT(context());
    if (context()->platformContext()->activePainter())
        context()->platformContext()->activePainter()->blitToSurface();
    return m_data.getImageData(context(), rect, m_size, IMAGEDATA_VG_PREMULTIPLIED_FORMAT);
}

void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem)
{
    m_data.putImageData(context(), source, sourceSize, sourceRect, destPoint, m_size,
            multiplied == Premultiplied ? IMAGEDATA_VG_PREMULTIPLIED_FORMAT : IMAGEDATA_VG_UNMULTIPLIED_FORMAT);
}

void ImageBuffer::didDraw()
{
	m_data.m_cachedImage.clear();
}

void ImageBuffer::flush()
{
}

DLL_PUBLIC void ImageBuffer::draw(GraphicsContext* context, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
                       CompositeOperator op, BlendMode blendMode, bool useLowQualityScale)
{
    if (m_data.m_surface->tiledImage()) {
        context->save();
    	context->setCompositeOperation(op);

    	// get vgimage
    	PainterOpenVG* painter = context->platformContext()->activePainter();
        if (painter)
            painter->drawImage(m_data.m_surface->tiledImage(), destRect, srcRect);

        context->restore();
    }
    else{
    	if(!m_data.m_cachedImage)
    		m_data.m_cachedImage = ImageBuffer::copyImage(CopyBackingStore);

    	if(m_data.m_cachedImage)
    		context->drawImage(m_data.m_cachedImage.get(), styleColorSpace, destRect, srcRect, op, blendMode, DoNotRespectImageOrientation, useLowQualityScale);
    }
}

String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const
{
	// TODO: for now, output to data uri is disabled. need to port
	// in PNGImageEncoder and JPEGImageEncoder.
	notImplemented();
	return "data:,";

#if 0
    if (m_size.isEmpty())
        return "data:,";

    enum {
        EncodeJPEG,
        EncodePNG,
    } encodeType = mimeType.lower() == "image/png" ? EncodePNG : EncodeJPEG;

    // According to http://www.w3.org/TR/html5/the-canvas-element.html,
    // "For image types that do not support an alpha channel, the image must be"
    // "composited onto a solid black background using the source-over operator,"
    // "and the resulting image must be the one used to create the data: URL."
    // JPEG doesn't have alpha channel, so we need premultiplied data.
    RefPtr<ImageData> imageData = encodeType == EncodePNG
        ? getUnmultipliedImageData(IntRect(IntPoint(0, 0), m_size))
        : getPremultipliedImageData(IntRect(IntPoint(0, 0), m_size));

    ASSERT(imageData && imageData->width() == m_size.width() && imageData->height() == m_size.height());

    Vector<char> output;
    const char* header;
    if (encodeType == EncodePNG) {
        if (!compressRGBABigEndianToPNG(imageData->data()->data()->data(), m_size, output))
            return "data:,";
        header = "data:image/png;base64,";
    } else {
        if (!compressRGBABigEndianToJPEG(imageData->data()->data()->data(), m_size, output))
            return "data:,";
        header = "data:image/jpeg;base64,";
    }

    Vector<char> base64;
    base64Encode(output, base64);

    output.clear();

    Vector<char> url;
    url.append(header, strlen(header));
    url.append(base64);

    return String(url.data(), url.size());
#endif
}

}
