/*
 * Copyright (C) 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "GradientImage.h"

#include "FloatRect.h"
#include "GraphicsContext.h"
#include "Length.h"

namespace WebCore {

static Vector< RefPtr<CachedGeneratorGeneratedImageBuffer> > sImageBufferCache;

void clearCachedGeneratorGeneratedImageBuffers()
{
	sImageBufferCache.clear();
}

static PassRefPtr<CachedGeneratorGeneratedImageBuffer> takeFromCache(unsigned generatorHash, const FloatSize& size)
{
	for(int i = 0; i < sImageBufferCache.size(); ++i){
		if(sImageBufferCache[i]->generatorHash() == generatorHash && sImageBufferCache[i]->adjustedSize() == size){
			RefPtr<CachedGeneratorGeneratedImageBuffer> entry = sImageBufferCache[i];
			sImageBufferCache.remove(i);
			return entry.release();
		}
	}

	return nullptr;
}

static void deleteFromCache(CachedGeneratorGeneratedImageBuffer* entry)
{
	for(int i = 0; i < sImageBufferCache.size(); ++i){
		if(sImageBufferCache[i] == entry){
			sImageBufferCache.remove(i);
			break;
		}
	}
}

void CachedGeneratorGeneratedImageBuffer::invalidateCacheTimerFired()
{
    m_image.clear();
    m_adjustedSize = FloatSize();
    deleteFromCache(this);
}

void CachedGeneratorGeneratedImageBuffer::scheduleEvict(double interval)
{
	if(m_timer.isActive())
		m_timer.stop();

	m_timer.startOneShot(interval);
}

GradientImage::GradientImage(PassRefPtr<Gradient> generator, const FloatSize& size)
: m_gradient(generator)
{
    m_size = size;
}

GradientImage::~GradientImage() = default;

void GradientImage::draw(GraphicsContext* destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator compositeOp, BlendMode blendMode)
{
	if(!m_gradient->canRender(m_size))
		return;

	if(m_gradient->shouldCache(m_size.width(), m_size.height())){
		unsigned generatorHash = m_gradient->hash();

		RefPtr<CachedGeneratorGeneratedImageBuffer> cache = takeFromCache(generatorHash, m_size);

		if( !cache ||
			!cache->image() ||
			cache->generatorHash() != generatorHash ||
			cache->adjustedSize() != m_size)
		{
			// Create a BitmapImage and call draw on it.
			OwnPtr<ImageBuffer> imageBuffer = destContext->createCompatibleBuffer(m_size);
			if (!imageBuffer)
				goto FALLBACK;

			// Fill with the generated image.
			imageBuffer->context()->fillRect(FloatRect(FloatPoint(), m_size), *m_gradient);
			imageBuffer->setHasAlpha(m_gradient->hasAlpha());

			cairo_surface_flush(imageBuffer->nativeSurface());

			cache = CachedGeneratorGeneratedImageBuffer::create(imageBuffer->copyImage(DontCopyBackingStore), generatorHash, m_size);
		}

		// Draw the image buffer to the destination
		destContext->drawImage(cache->image(), destRect, srcRect, compositeOp);

		sImageBufferCache.append(cache);
		cache->scheduleEvict(m_gradient->cacheDeletionInterval());

	    return;
	}

FALLBACK:
	//directly compose
	GraphicsContextStateSaver stateSaver(*destContext);
	destContext->setCompositeOperation(compositeOp, blendMode);
	destContext->clip(destRect);
	destContext->translate(destRect.x(), destRect.y());
	if (destRect.size() != srcRect.size())
		destContext->scale(FloatSize(destRect.width() / srcRect.width(), destRect.height() / srcRect.height()));
	destContext->translate(-srcRect.x(), -srcRect.y());
	destContext->fillRect(FloatRect(FloatPoint(), m_size), *m_gradient.get());
}

void GradientImage::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform,
	const FloatPoint& phase, const FloatSize& spacing, CompositeOperator compositeOp, const FloatRect& destRect, BlendMode)
{
    // Allow the generator to provide visually-equivalent tiling parameters for better performance.
    FloatSize adjustedSize = m_size;
    FloatRect adjustedSrcRect = srcRect;
    m_gradient->adjustParametersForTiledDrawing(adjustedSize, adjustedSrcRect, spacing);

    // Factor in the destination context's scale to generate at the best resolution
    AffineTransform destContextCTM = destContext->getCTM(GraphicsContext::DefinitelyIncludeDeviceScale);
    double xScale = fabs(destContextCTM.xScale());
    double yScale = fabs(destContextCTM.yScale());
    AffineTransform adjustedPatternCTM = patternTransform;
    adjustedPatternCTM.scale(1.0 / xScale, 1.0 / yScale);
    adjustedSrcRect.scale(xScale, yScale);

	if(!m_gradient->canRender(adjustedSize))
		return;

    unsigned generatorHash = m_gradient->hash();

	RefPtr<CachedGeneratorGeneratedImageBuffer> cache = takeFromCache(generatorHash, adjustedSize);

	if( !cache ||
		!cache->image() ||
		cache->generatorHash() != generatorHash ||
		cache->adjustedSize() != adjustedSize)
	{
		// Create a BitmapImage and call draw on it.
		OwnPtr<ImageBuffer> imageBuffer = destContext->createCompatibleBuffer(adjustedSize);
		if (!imageBuffer)
			return;

        // Fill with the generated image.
        imageBuffer->context()->fillRect(FloatRect(FloatPoint(), adjustedSize), *m_gradient);
        imageBuffer->setHasAlpha(m_gradient->hasAlpha());

		cairo_surface_flush(imageBuffer->nativeSurface());

        if (destContext->drawLuminanceMask())
            imageBuffer->convertToLuminanceMask();

		cache = CachedGeneratorGeneratedImageBuffer::create(imageBuffer->copyImage(DontCopyBackingStore), generatorHash, adjustedSize);
    }

    destContext->setDrawLuminanceMask(false);

    // Tile the image buffer into the context.
	cache->image()->drawPattern(destContext, adjustedSrcRect, adjustedPatternCTM, phase, spacing, compositeOp, destRect);
            
    if(m_gradient->shouldCache(adjustedSize.width(), adjustedSize.height())){
		sImageBufferCache.append(cache);
		cache->scheduleEvict(m_gradient->cacheDeletionInterval());
	}
}

void GeneratedImage::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
{
    Image::computeIntrinsicDimensions(intrinsicWidth, intrinsicHeight, intrinsicRatio);
    intrinsicRatio = FloatSize();
}

}
