
/*
 * 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        = nullptr;
    fScaleType    = kMatrix_ScaleType;

    fData.fAnim    = nullptr;        // 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(nullptr);
    }
}

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 == nullptr)
            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 = nullptr;
            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(nullptr);
}

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

bool SkImageView::onEvent(const SkEvent& evt)
{
    if (evt.isType(SK_EventType_Inval))
    {
        if (fUriIsValid)
            this->inval(nullptr);
        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(nullptr);
        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(nullptr);
    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 = nullptr;    // 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;
}
