/*
 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
 * Copyright (C) Research In Motion Limited 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"

#if ENABLE(SVG)
#include "RenderSVGResourcePattern.h"

#include "FrameView.h"
#include "GraphicsContext.h"
#include "PatternAttributes.h"
#include "SVGRenderSupport.h"

namespace WebCore {

RenderSVGResourceType RenderSVGResourcePattern::s_resourceType = PatternResourceType;

RenderSVGResourcePattern::RenderSVGResourcePattern(SVGPatternElement* node)
    : RenderSVGResourceContainer(node)
{
}

RenderSVGResourcePattern::~RenderSVGResourcePattern()
{
    deleteAllValues(m_pattern);
    m_pattern.clear();
}

void RenderSVGResourcePattern::invalidateClients()
{
    const HashMap<RenderObject*, PatternData*>::const_iterator end = m_pattern.end();
    for (HashMap<RenderObject*, PatternData*>::const_iterator it = m_pattern.begin(); it != end; ++it)
        markForLayoutAndResourceInvalidation(it->first, false);

    deleteAllValues(m_pattern);
    m_pattern.clear();
}

void RenderSVGResourcePattern::invalidateClient(RenderObject* object)
{
    ASSERT(object);
    if (!m_pattern.contains(object))
        return;

    delete m_pattern.take(object);
    markForLayoutAndResourceInvalidation(object, false);
}

bool RenderSVGResourcePattern::childElementReferencesResource(const SVGRenderStyle* style, const String& referenceId) const
{
    if (style->hasFill()) {
        if (style->fillPaint()->matchesTargetURI(referenceId))
            return true;
    }

    if (style->hasStroke()) {
        if (style->strokePaint()->matchesTargetURI(referenceId))
            return true;
    }

    return false;
}

bool RenderSVGResourcePattern::applyResource(RenderObject* object, RenderStyle* style, GraphicsContext*& context, unsigned short resourceMode)
{
    ASSERT(object);
    ASSERT(style);
    ASSERT(context);
    ASSERT(resourceMode != ApplyToDefaultMode);

    // Be sure to synchronize all SVG properties on the patternElement _before_ processing any further.
    // Otherwhise the call to collectPatternAttributes() in createTileImage(), may cause the SVG DOM property
    // synchronization to kick in, which causes invalidateClients() to be called, which in turn deletes our
    // PatternData object! Leaving out the line below will cause svg/dynamic-updates/SVGPatternElement-svgdom* to crash.
    SVGPatternElement* patternElement = static_cast<SVGPatternElement*>(node());
    if (!patternElement)
        return false;

    patternElement->updateAnimatedSVGAttribute(anyQName());

    if (!m_pattern.contains(object))
        m_pattern.set(object, new PatternData);

    PatternData* patternData = m_pattern.get(object);
    if (!patternData->pattern) {
        // Create tile image
        OwnPtr<ImageBuffer> tileImage = createTileImage(patternData, patternElement, object);
        if (!tileImage)
            return false;

        // Create pattern object
        buildPattern(patternData, tileImage.release());

        if (!patternData->pattern)
            return false;

        patternData->pattern->setPatternSpaceTransform(patternData->transform);
    }

    // Draw pattern
    context->save();

    const SVGRenderStyle* svgStyle = style->svgStyle();
    ASSERT(svgStyle);

    if (resourceMode & ApplyToFillMode) {
        context->setAlpha(svgStyle->fillOpacity());
        context->setFillPattern(patternData->pattern);
        context->setFillRule(svgStyle->fillRule());
    } else if (resourceMode & ApplyToStrokeMode) {
        if (svgStyle->vectorEffect() == VE_NON_SCALING_STROKE)
            patternData->pattern->setPatternSpaceTransform(transformOnNonScalingStroke(object, patternData->transform));
        context->setAlpha(svgStyle->strokeOpacity());
        context->setStrokePattern(patternData->pattern);
        SVGRenderSupport::applyStrokeStyleToContext(context, style, object);
    }

    if (resourceMode & ApplyToTextMode) {
        if (resourceMode & ApplyToFillMode) {
            context->setTextDrawingMode(cTextFill);

#if PLATFORM(CG)
            context->applyFillPattern();
#endif
        } else if (resourceMode & ApplyToStrokeMode) {
            context->setTextDrawingMode(cTextStroke);

#if PLATFORM(CG)
            context->applyStrokePattern();
#endif
        }
    }

    return true;
}

void RenderSVGResourcePattern::postApplyResource(RenderObject*, GraphicsContext*& context, unsigned short resourceMode)
{
    ASSERT(context);
    ASSERT(resourceMode != ApplyToDefaultMode);

    if (!(resourceMode & ApplyToTextMode)) {
        if (resourceMode & ApplyToFillMode)
            context->fillPath();
        else if (resourceMode & ApplyToStrokeMode)
            context->strokePath();
    }

    context->restore();
}

static inline FloatRect calculatePatternBoundaries(PatternAttributes& attributes,
                                                   const FloatRect& objectBoundingBox,
                                                   const SVGPatternElement* patternElement)
{
    if (attributes.boundingBoxMode())
        return FloatRect(attributes.x().valueAsPercentage() * objectBoundingBox.width(),
                         attributes.y().valueAsPercentage() * objectBoundingBox.height(),
                         attributes.width().valueAsPercentage() * objectBoundingBox.width(),
                         attributes.height().valueAsPercentage() * objectBoundingBox.height());

    return FloatRect(attributes.x().value(patternElement),
                     attributes.y().value(patternElement),
                     attributes.width().value(patternElement),
                     attributes.height().value(patternElement));
}

FloatRect RenderSVGResourcePattern::calculatePatternBoundariesIncludingOverflow(PatternAttributes& attributes,
                                                                                const FloatRect& objectBoundingBox,
                                                                                const AffineTransform& viewBoxCTM,
                                                                                const FloatRect& patternBoundaries) const
{
    // Eventually calculate the pattern content boundaries (only needed with overflow="visible").
    FloatRect patternContentBoundaries;

    const RenderStyle* style = this->style();
    if (style->overflowX() == OVISIBLE && style->overflowY() == OVISIBLE) {
        for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) {
            if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyledTransformable() || !node->renderer())
                continue;
            patternContentBoundaries.unite(node->renderer()->repaintRectInLocalCoordinates());
        }
    }

    if (patternContentBoundaries.isEmpty())
        return patternBoundaries;

    FloatRect patternBoundariesIncludingOverflow = patternBoundaries;

    // Respect objectBoundingBoxMode for patternContentUnits, if viewBox is not set.
    if (!viewBoxCTM.isIdentity())
        patternContentBoundaries = viewBoxCTM.mapRect(patternContentBoundaries);
    else if (attributes.boundingBoxModeContent())
        patternContentBoundaries = FloatRect(patternContentBoundaries.x() * objectBoundingBox.width(),
                                             patternContentBoundaries.y() * objectBoundingBox.height(),
                                             patternContentBoundaries.width() * objectBoundingBox.width(),
                                             patternContentBoundaries.height() * objectBoundingBox.height());

    patternBoundariesIncludingOverflow.unite(patternContentBoundaries);
    return patternBoundariesIncludingOverflow;
}

// FIXME: This method should be removed. RenderSVGResourcePatterns usage of it is just wrong.
static inline void clampImageBufferSizeToViewport(FrameView* frameView, IntSize& size)
{
    if (!frameView)
        return;

    int viewWidth = frameView->visibleWidth();
    int viewHeight = frameView->visibleHeight();

    if (size.width() > viewWidth)
        size.setWidth(viewWidth);

    if (size.height() > viewHeight)
        size.setHeight(viewHeight);
}

PassOwnPtr<ImageBuffer> RenderSVGResourcePattern::createTileImage(PatternData* patternData,
                                                                  const SVGPatternElement* patternElement,
                                                                  RenderObject* object) const
{
    PatternAttributes attributes = patternElement->collectPatternProperties();

    // If we couldn't determine the pattern content element root, stop here.
    if (!attributes.patternContentElement())
        return 0;

    // Early exit, if this resource contains a child which references ourselves.
    if (containsCyclicReference(attributes.patternContentElement()))
        return 0;

    FloatRect objectBoundingBox = object->objectBoundingBox();    
    FloatRect patternBoundaries = calculatePatternBoundaries(attributes, objectBoundingBox, patternElement); 
    AffineTransform patternTransform = attributes.patternTransform();

    AffineTransform viewBoxCTM = patternElement->viewBoxToViewTransform(patternElement->viewBox(),
                                                                        patternElement->preserveAspectRatio(),
                                                                        patternBoundaries.width(),
                                                                        patternBoundaries.height());

    FloatRect patternBoundariesIncludingOverflow = calculatePatternBoundariesIncludingOverflow(attributes,
                                                                                               objectBoundingBox,
                                                                                               viewBoxCTM,
                                                                                               patternBoundaries);

    IntSize imageSize(lroundf(patternBoundariesIncludingOverflow.width()), lroundf(patternBoundariesIncludingOverflow.height()));

    // FIXME: We should be able to clip this more, needs investigation
    clampImageBufferSizeToViewport(object->document()->view(), imageSize);

    // Don't create ImageBuffers with image size of 0
    if (imageSize.isEmpty())
        return 0;

    OwnPtr<ImageBuffer> tileImage = ImageBuffer::create(imageSize);

    GraphicsContext* context = tileImage->context();
    ASSERT(context);

    context->save();

    // Translate to pattern start origin
    if (patternBoundariesIncludingOverflow.location() != patternBoundaries.location()) {
        context->translate(patternBoundaries.x() - patternBoundariesIncludingOverflow.x(),
                           patternBoundaries.y() - patternBoundariesIncludingOverflow.y());

        patternBoundaries.setLocation(patternBoundariesIncludingOverflow.location());
    }

    // Process viewBox or boundingBoxModeContent correction
    if (!viewBoxCTM.isIdentity())
        context->concatCTM(viewBoxCTM);
    else if (attributes.boundingBoxModeContent()) {
        context->translate(objectBoundingBox.x(), objectBoundingBox.y());
        context->scale(FloatSize(objectBoundingBox.width(), objectBoundingBox.height()));
    }

    // Render subtree into ImageBuffer
    for (Node* node = attributes.patternContentElement()->firstChild(); node; node = node->nextSibling()) {
        if (!node->isSVGElement() || !static_cast<SVGElement*>(node)->isStyled() || !node->renderer())
            continue;
        SVGRenderSupport::renderSubtreeToImage(tileImage.get(), node->renderer());
    }

    patternData->boundaries = patternBoundaries;

    // Compute pattern transformation
    patternData->transform.translate(patternBoundaries.x(), patternBoundaries.y());
    patternData->transform.multiply(patternTransform);

    context->restore();
    return tileImage.release();
}

void RenderSVGResourcePattern::buildPattern(PatternData* patternData, PassOwnPtr<ImageBuffer> tileImage) const
{
    if (!tileImage->image()) {
        patternData->pattern = 0;
        return;
    }

    IntRect tileRect = tileImage->image()->rect();
    if (tileRect.width() <= patternData->boundaries.width() && tileRect.height() <= patternData->boundaries.height()) {
        patternData->pattern = Pattern::create(tileImage->image(), true, true);
        return;
    }

    // Draw the first cell of the pattern manually to support overflow="visible" on all platforms.
    int tileWidth = static_cast<int>(patternData->boundaries.width() + 0.5f);
    int tileHeight = static_cast<int>(patternData->boundaries.height() + 0.5f);

    // Don't create ImageBuffers with image size of 0
    if (!tileWidth || !tileHeight) {
        patternData->pattern = 0;
        return;
    }

    OwnPtr<ImageBuffer> newTileImage = ImageBuffer::create(IntSize(tileWidth, tileHeight));
    GraphicsContext* newTileImageContext = newTileImage->context();

    int numY = static_cast<int>(ceilf(tileRect.height() / tileHeight)) + 1;
    int numX = static_cast<int>(ceilf(tileRect.width() / tileWidth)) + 1;

    newTileImageContext->save();
    newTileImageContext->translate(-patternData->boundaries.width() * numX, -patternData->boundaries.height() * numY);
    for (int i = numY; i > 0; --i) {
        newTileImageContext->translate(0, patternData->boundaries.height());
        for (int j = numX; j > 0; --j) {
            newTileImageContext->translate(patternData->boundaries.width(), 0);
            newTileImageContext->drawImage(tileImage->image(), style()->colorSpace(), tileRect, tileRect);
        }
        newTileImageContext->translate(-patternData->boundaries.width() * numX, 0);
    }
    newTileImageContext->restore();

    patternData->pattern = Pattern::create(newTileImage->image(), true, true);
}

}

#endif
