/*
 * 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 "SkImageView.h"
#include "SkAnimator.h"
#include "SkBitmap.h"
#include "SkCanvas.h"
#include "SkImageDecoder.h"
#include "SkMatrix.h"
#include "SkSystemEventTypes.h"
#include "SkTime.h"

SkImageView::SkImageView()
{
	fMatrix		= NULL;
	fScaleType	= kMatrix_ScaleType;

	fData.fAnim	= NULL;		// handles initializing the other union values
	fDataIsAnim	= true;
	
	fUriIsValid	= false;	// an empty string is not valid
}

SkImageView::~SkImageView()
{
	if (fMatrix)
		sk_free(fMatrix);
		
	this->freeData();
}

void SkImageView::getUri(SkString* uri) const
{
	if (uri)
		*uri = fUri;
}

void SkImageView::setUri(const char uri[])
{
	if (!fUri.equals(uri))
	{
		fUri.set(uri);
		this->onUriChange();
	}
}

void SkImageView::setUri(const SkString& uri)
{
	if (fUri != uri)
	{
		fUri = uri;
		this->onUriChange();
	}
}

void SkImageView::setScaleType(ScaleType st)
{
	SkASSERT((unsigned)st <= kFitEnd_ScaleType);

	if ((ScaleType)fScaleType != st)
	{
		fScaleType = SkToU8(st);
		if (fUriIsValid)
			this->inval(NULL);
	}
}

bool SkImageView::getImageMatrix(SkMatrix* matrix) const
{
	if (fMatrix)
	{
		SkASSERT(!fMatrix->isIdentity());
		if (matrix)
			*matrix = *fMatrix;
		return true;
	}
	else
	{
		if (matrix)
			matrix->reset();
		return false;
	}
}

void SkImageView::setImageMatrix(const SkMatrix* matrix)
{
	bool changed = false;

	if (matrix && !matrix->isIdentity())
	{
		if (fMatrix == NULL)
			fMatrix = (SkMatrix*)sk_malloc_throw(sizeof(SkMatrix));
		*fMatrix = *matrix;
		changed = true;
	}
	else	// set us to identity
	{
		if (fMatrix)
		{
			SkASSERT(!fMatrix->isIdentity());
			sk_free(fMatrix);
			fMatrix = NULL;
			changed = true;
		}
	}

	// only redraw if we changed our matrix and we're not in scaleToFit mode
	if (changed && this->getScaleType() == kMatrix_ScaleType && fUriIsValid)
		this->inval(NULL);
}

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

bool SkImageView::onEvent(const SkEvent& evt)
{
	if (evt.isType(SK_EventType_Inval))
	{
		if (fUriIsValid)
			this->inval(NULL);
		return true;
	}
	return this->INHERITED::onEvent(evt);
}

static inline SkMatrix::ScaleToFit scaleTypeToScaleToFit(SkImageView::ScaleType st)
{
	SkASSERT(st != SkImageView::kMatrix_ScaleType);
	SkASSERT((unsigned)st <= SkImageView::kFitEnd_ScaleType);

	SkASSERT(SkImageView::kFitXY_ScaleType - 1 == SkMatrix::kFill_ScaleToFit);
	SkASSERT(SkImageView::kFitStart_ScaleType - 1 == SkMatrix::kStart_ScaleToFit);
	SkASSERT(SkImageView::kFitCenter_ScaleType - 1 == SkMatrix::kCenter_ScaleToFit);
	SkASSERT(SkImageView::kFitEnd_ScaleType - 1 == SkMatrix::kEnd_ScaleToFit);
	
	return (SkMatrix::ScaleToFit)(st - 1);
}

void SkImageView::onDraw(SkCanvas* canvas)
{
	SkRect	src;
	if (!this->getDataBounds(&src))
	{
		SkDEBUGCODE(canvas->drawColor(SK_ColorRED);)
		return;		// nothing to draw
	}
		
	SkAutoCanvasRestore	restore(canvas, true);
	SkMatrix			matrix;
	
	if (this->getScaleType() == kMatrix_ScaleType)
		(void)this->getImageMatrix(&matrix);
	else
	{
		SkRect	dst;		
		dst.set(0, 0, this->width(), this->height());
		matrix.setRectToRect(src, dst, scaleTypeToScaleToFit(this->getScaleType()));
	}
	canvas->concat(matrix);

	SkPaint	paint;
	
	paint.setAntiAlias(true);

	if (fDataIsAnim)
	{
		SkMSec	now = SkTime::GetMSecs();
		
		SkAnimator::DifferenceType diff = fData.fAnim->draw(canvas, &paint, now);
		
SkDEBUGF(("SkImageView : now = %X[%12.3f], diff = %d\n", now, now/1000., diff));

		if (diff == SkAnimator::kDifferent)
			this->inval(NULL);
		else if (diff == SkAnimator::kPartiallyDifferent)
		{
			SkRect	bounds;
			fData.fAnim->getInvalBounds(&bounds);
			matrix.mapRect(&bounds);	// get the bounds into view coordinates
			this->inval(&bounds);
		}
	}
	else
		canvas->drawBitmap(*fData.fBitmap, 0, 0, &paint);
}

void SkImageView::onInflate(const SkDOM& dom, const SkDOMNode* node)
{
	this->INHERITED::onInflate(dom, node);
	
	const char* src = dom.findAttr(node, "src");
	if (src)
		this->setUri(src);

	int	index = dom.findList(node, "scaleType", "matrix,fitXY,fitStart,fitCenter,fitEnd");
	if (index >= 0)
		this->setScaleType((ScaleType)index);
		
	// need inflate syntax/reader for matrix
}

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

void SkImageView::onUriChange()
{
	if (this->freeData())
		this->inval(NULL);
	fUriIsValid = true;		// give ensureUriIsLoaded() a shot at the new uri
}

bool SkImageView::freeData()
{
	if (fData.fAnim)	// test is valid for all union values
	{
		if (fDataIsAnim)
			delete fData.fAnim;
		else
			delete fData.fBitmap;

		fData.fAnim = NULL;	// valid for all union values
		return true;
	}
	return false;
}

bool SkImageView::getDataBounds(SkRect* bounds)
{
	SkASSERT(bounds);

	if (this->ensureUriIsLoaded())
	{
		SkScalar width, height;

		if (fDataIsAnim)
		{			
			if (SkScalarIsNaN(width = fData.fAnim->getScalar("dimensions", "x")) ||
				SkScalarIsNaN(height = fData.fAnim->getScalar("dimensions", "y")))
			{
				// cons up fake bounds
				width = this->width();
				height = this->height();
			}
		}
		else
		{
			width = SkIntToScalar(fData.fBitmap->width());
			height = SkIntToScalar(fData.fBitmap->height());
		}
		bounds->set(0, 0, width, height);
		return true;
	}
	return false;
}

bool SkImageView::ensureUriIsLoaded()
{
	if (fData.fAnim)	// test is valid for all union values
	{
		SkASSERT(fUriIsValid);
		return true;
	}
	if (!fUriIsValid)
		return false;

	// try to load the url
	if (fUri.endsWith(".xml"))	// assume it is screenplay
	{
		SkAnimator* anim = new SkAnimator;
		
		if (!anim->decodeURI(fUri.c_str()))
		{
			delete anim;
			fUriIsValid = false;
			return false;
		}
		anim->setHostEventSink(this);

		fData.fAnim = anim;
		fDataIsAnim = true;
	}
	else	// assume it is an image format
	{
    #if 0
		SkBitmap* bitmap = new SkBitmap;

		if (!SkImageDecoder::DecodeURL(fUri.c_str(), bitmap))
		{
			delete bitmap;
			fUriIsValid = false;
			return false;
		}
		fData.fBitmap = bitmap;
		fDataIsAnim = false;
    #else
        return false;
    #endif
	}
	return true;
}