/*
 * 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 "GraphicsContext.h"

#include "AffineTransform.h"
#include "GraphicsContextPrivate.h"
#include "KURL.h"
#include "NotImplemented.h"
#include "PainterOpenVG.h"
#include "SurfaceOpenVG.h"

#include <wtf/Assertions.h>
#include <wtf/MathExtras.h>
#include <wtf/UnusedParam.h>
#include <wtf/Vector.h>

#if PLATFORM(EGL)
#include "EGLDisplayOpenVG.h"
#include "EGLUtils.h"
#include <egl.h>
#endif

namespace WebCore {

// typedef'ing doesn't work, let's inherit from PainterOpenVG instead
class GraphicsContextPlatformPrivate : public PainterOpenVG {
public:
    GraphicsContextPlatformPrivate(SurfaceOpenVG* surface)
        : PainterOpenVG(surface)
    {
    }
};

GraphicsContext::GraphicsContext(SurfaceOpenVG* surface)
    : m_common(createGraphicsContextPrivate())
    , m_data(surface ? new GraphicsContextPlatformPrivate(surface) : 0)
{
    setPaintingDisabled(!surface);
}

GraphicsContext::~GraphicsContext()
{
    destroyGraphicsContextPrivate(m_common);
    delete m_data;
}

PlatformGraphicsContext* GraphicsContext::platformContext() const
{
    if (paintingDisabled())
        return 0;

    return m_data->baseSurface();
}

AffineTransform GraphicsContext::getCTM() const
{
    if (paintingDisabled())
        return AffineTransform();

    return m_data->transformation();
}

void GraphicsContext::savePlatformState()
{
    if (paintingDisabled())
        return;

    m_data->save();
}

void GraphicsContext::restorePlatformState()
{
    if (paintingDisabled())
        return;

    m_data->restore();
}

void GraphicsContext::drawRect(const IntRect& rect)
{
    if (paintingDisabled())
        return;

    m_data->drawRect(rect);
}

void GraphicsContext::drawLine(const IntPoint& from, const IntPoint& to)
{
    if (paintingDisabled())
        return;

    m_data->drawLine(from, to);
}

/**
 * Draw the largest ellipse that fits into the given rectangle.
 */
void GraphicsContext::drawEllipse(const IntRect& rect)
{
    if (paintingDisabled())
        return;

    m_data->drawEllipse(rect);
}

void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
{
    if (paintingDisabled())
        return;

    m_data->drawArc(rect, startAngle, angleSpan, VG_STROKE_PATH);
}

void GraphicsContext::drawConvexPolygon(size_t numPoints, const FloatPoint* points, bool shouldAntialias)
{
    if (paintingDisabled())
        return;

    m_data->drawPolygon(numPoints, points);

    UNUSED_PARAM(shouldAntialias); // FIXME
}

void GraphicsContext::fillPath()
{
    if (paintingDisabled())
        return;

    m_data->drawPath(VG_FILL_PATH, m_common->state.fillRule);
}

void GraphicsContext::strokePath()
{
    if (paintingDisabled())
        return;

    m_data->drawPath(VG_STROKE_PATH, m_common->state.fillRule);
}

void GraphicsContext::drawPath()
{
    if (paintingDisabled())
        return;

    m_data->drawPath(VG_FILL_PATH | VG_STROKE_PATH, m_common->state.fillRule);
}

void GraphicsContext::fillRect(const FloatRect& rect)
{
    if (paintingDisabled())
        return;

    m_data->drawRect(rect, VG_FILL_PATH);
}

void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace)
{
    if (paintingDisabled())
        return;

    Color oldColor = m_data->fillColor();
    m_data->setFillColor(color);
    m_data->drawRect(rect, VG_FILL_PATH);
    m_data->setFillColor(oldColor);

    UNUSED_PARAM(colorSpace); // FIXME
}

void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace)
{
    if (paintingDisabled())
        return;

    Color oldColor = m_data->fillColor();
    m_data->setFillColor(color);
    m_data->drawRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight, VG_FILL_PATH);
    m_data->setFillColor(oldColor);

    UNUSED_PARAM(colorSpace); // FIXME
}

void GraphicsContext::beginPath()
{
    if (paintingDisabled())
        return;

    m_data->beginPath();
}

void GraphicsContext::addPath(const Path& path)
{
    if (paintingDisabled())
        return;

    m_data->addPath(path);
}

void GraphicsContext::clip(const FloatRect& rect)
{
    if (paintingDisabled())
        return;

    m_data->intersectClipRect(rect);
}

void GraphicsContext::clipPath(WindRule clipRule)
{
    if (paintingDisabled())
        return;

    m_data->clipPath(*(m_data->currentPath()), PainterOpenVG::IntersectClip, clipRule);
}

void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int offset, const Color& color)
{
    if (paintingDisabled())
        return;

    if (rects.isEmpty())
        return;

    // FIXME: We just unite all focus ring rects into one for now.
    // We should outline the edge of the full region.
    offset += (width - 1) / 2;
    IntRect finalFocusRect;

    for (unsigned i = 0; i < rects.size(); i++) {
        IntRect focusRect = rects[i];
        focusRect.inflate(offset);
        finalFocusRect.unite(focusRect);
    }

    StrokeStyle oldStyle = m_data->strokeStyle();
    Color oldStrokeColor = m_data->strokeColor();
    m_data->setStrokeStyle(DashedStroke);
    m_data->setStrokeColor(color);
    strokeRect(FloatRect(finalFocusRect), 1.f);
    m_data->setStrokeStyle(oldStyle);
    m_data->setStrokeColor(oldStrokeColor);
}

void GraphicsContext::drawLineForText(const IntPoint& origin, int width, bool printing)
{
    if (paintingDisabled())
        return;

    if (width <= 0)
        return;

    StrokeStyle oldStyle = m_data->strokeStyle();
    m_data->setStrokeStyle(SolidStroke);
    drawLine(origin, origin + IntSize(width, 0));
    m_data->setStrokeStyle(oldStyle);

    UNUSED_PARAM(printing);
}

void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& origin, int width, bool grammar)
{
    if (paintingDisabled())
        return;

    notImplemented();
    UNUSED_PARAM(origin);
    UNUSED_PARAM(width);
    UNUSED_PARAM(grammar);
}

FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
{
    if (paintingDisabled())
        return FloatRect();

    return FloatRect(enclosingIntRect(m_data->transformation().mapRect(rect)));
}

void GraphicsContext::setPlatformShadow(const IntSize& size, int blur, const Color& color, ColorSpace colorSpace)
{
    if (paintingDisabled())
        return;

    notImplemented();
    UNUSED_PARAM(size);
    UNUSED_PARAM(blur);
    UNUSED_PARAM(color);
    UNUSED_PARAM(colorSpace);
}

void GraphicsContext::clearPlatformShadow()
{
    if (paintingDisabled())
        return;

    notImplemented();
}

void GraphicsContext::beginTransparencyLayer(float opacity)
{
    if (paintingDisabled())
        return;

    notImplemented();
    UNUSED_PARAM(opacity);
}

void GraphicsContext::endTransparencyLayer()
{
    if (paintingDisabled())
        return;

    notImplemented();
}

void GraphicsContext::clearRect(const FloatRect& rect)
{
    if (paintingDisabled())
        return;

    CompositeOperator op = m_data->compositeOperation();
    m_data->setCompositeOperation(CompositeClear);
    m_data->drawRect(rect, VG_FILL_PATH);
    m_data->setCompositeOperation(op);
}

void GraphicsContext::strokeRect(const FloatRect& rect)
{
    if (paintingDisabled())
        return;

    m_data->drawRect(rect, VG_STROKE_PATH);
}

void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
{
    if (paintingDisabled())
        return;

    float oldThickness = m_data->strokeThickness();
    m_data->setStrokeThickness(lineWidth);
    m_data->drawRect(rect, VG_STROKE_PATH);
    m_data->setStrokeThickness(oldThickness);
}

void GraphicsContext::setLineCap(LineCap lc)
{
    if (paintingDisabled())
        return;

    m_data->setLineCap(lc);
}

void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
{
    if (paintingDisabled())
        return;

    m_data->setLineDash(dashes, dashOffset);
}

void GraphicsContext::setLineJoin(LineJoin lj)
{
    if (paintingDisabled())
        return;

    m_data->setLineJoin(lj);
}

void GraphicsContext::setMiterLimit(float limit)
{
    if (paintingDisabled())
        return;

    m_data->setMiterLimit(limit);
}

void GraphicsContext::setAlpha(float opacity)
{
    if (paintingDisabled())
        return;

    m_data->setOpacity(opacity);
}

void GraphicsContext::setCompositeOperation(CompositeOperator op)
{
    if (paintingDisabled())
        return;

    m_data->setCompositeOperation(op);
}

void GraphicsContext::clip(const Path& path)
{
    if (paintingDisabled())
        return;

    m_data->clipPath(path, PainterOpenVG::IntersectClip, m_common->state.fillRule);
}

void GraphicsContext::canvasClip(const Path& path)
{
    clip(path);
}

void GraphicsContext::clipOut(const Path& path)
{
    if (paintingDisabled())
        return;

    m_data->clipPath(path, PainterOpenVG::SubtractClip, m_common->state.fillRule);
}

void GraphicsContext::scale(const FloatSize& scaleFactors)
{
    if (paintingDisabled())
        return;

    m_data->scale(scaleFactors);
}

void GraphicsContext::rotate(float radians)
{
    if (paintingDisabled())
        return;

    m_data->rotate(radians);
}

void GraphicsContext::translate(float dx, float dy)
{
    if (paintingDisabled())
        return;

    m_data->translate(dx, dy);
}

IntPoint GraphicsContext::origin()
{
    if (paintingDisabled())
        return IntPoint();

    AffineTransform transformation = m_data->transformation();
    return IntPoint(roundf(transformation.e()), roundf(transformation.f()));
}

void GraphicsContext::clipOut(const IntRect& rect)
{
    if (paintingDisabled())
        return;

    Path path;
    path.addRect(rect);
    m_data->clipPath(path, PainterOpenVG::SubtractClip, m_common->state.fillRule);
}

void GraphicsContext::clipOutEllipseInRect(const IntRect& rect)
{
    if (paintingDisabled())
        return;

    Path path;
    path.addEllipse(rect);
    m_data->clipPath(path, PainterOpenVG::SubtractClip, m_common->state.fillRule);
}

void GraphicsContext::clipToImageBuffer(const FloatRect& rect, const ImageBuffer* imageBuffer)
{
    if (paintingDisabled())
        return;

    notImplemented();
    UNUSED_PARAM(rect);
    UNUSED_PARAM(imageBuffer);
}

void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
{
    if (paintingDisabled())
        return;

    Path path;
    path.addEllipse(rect);
    path.addEllipse(FloatRect(rect.x() + thickness, rect.y() + thickness,
        rect.width() - (thickness * 2), rect.height() - (thickness * 2)));

    m_data->clipPath(path, PainterOpenVG::IntersectClip, m_common->state.fillRule);
}

void GraphicsContext::concatCTM(const AffineTransform& transformation)
{
    if (paintingDisabled())
        return;

    m_data->concatTransformation(transformation);
}

void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
{
    notImplemented();
    UNUSED_PARAM(link);
    UNUSED_PARAM(destRect);
}

void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace)
{
    if (paintingDisabled())
        return;

    m_data->setStrokeColor(color);

    UNUSED_PARAM(colorSpace); // FIXME
}

void GraphicsContext::setPlatformStrokeStyle(const StrokeStyle& strokeStyle)
{
    if (paintingDisabled())
        return;

    m_data->setStrokeStyle(strokeStyle);
}

void GraphicsContext::setPlatformStrokeThickness(float thickness)
{
    if (paintingDisabled())
        return;

    m_data->setStrokeThickness(thickness);
}

void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace)
{
    if (paintingDisabled())
        return;

    m_data->setFillColor(color);

    UNUSED_PARAM(colorSpace); // FIXME
}

void GraphicsContext::setPlatformShouldAntialias(bool enable)
{
    if (paintingDisabled())
        return;

    m_data->setAntialiasingEnabled(enable);
}

void GraphicsContext::setImageInterpolationQuality(InterpolationQuality)
{
    notImplemented();
}

InterpolationQuality GraphicsContext::imageInterpolationQuality() const
{
    notImplemented();
    return InterpolationDefault;
}

}
