/*
 *  Copyright (C) 2010 Parker Coates <coates@kde.org>
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "kcard.h"
#include "kcard_p.h"

// own
#include "kabstractcarddeck.h"
#include "kcardpile.h"
// Qt
#include <QPainter>
#include <QPropertyAnimation>
// Std
#include <cmath>

namespace
{
const qreal raisedZValue = 10000;
}

KCardAnimation::KCardAnimation(KCardPrivate *d, int duration, QPointF pos, qreal rotation, bool faceUp)
    : QAbstractAnimation(d)
    , d(d)
    , m_duration(duration)
    , m_x0(d->q->x())
    , m_y0(d->q->y())
    , m_rotation0(d->q->rotation())
    , m_flippedness0(d->flippedness())
    , m_xDelta(pos.x() - m_x0)
    , m_yDelta(pos.y() - m_y0)
    , m_rotationDelta(rotation - m_rotation0)
    , m_flippednessDelta((faceUp ? 1.0 : 0.0) - m_flippedness0)
{
    qreal w = d->deck->cardWidth();
    qreal h = d->deck->cardHeight();
    qreal diagSquared = w * w + h * h;
    qreal distSquared = m_xDelta * m_xDelta + m_yDelta * m_yDelta;

    m_flipProgressFactor = qMax<qreal>(1, sqrt(distSquared / diagSquared));
}

int KCardAnimation::duration() const
{
    return m_duration;
}

void KCardAnimation::updateCurrentTime(int msec)
{
    qreal progress = qreal(msec) / m_duration;
    qreal flipProgress = qMin<qreal>(1, progress * m_flipProgressFactor);

    d->q->setPos(m_x0 + m_xDelta * progress, m_y0 + m_yDelta * progress);
    d->q->setRotation(m_rotation0 + m_rotationDelta * progress);
    d->setFlippedness(m_flippedness0 + m_flippednessDelta * flipProgress);
}

KCardPrivate::KCardPrivate(KCard *card)
    : QObject(card)
    , q(card)
{
}

void KCardPrivate::setFlippedness(qreal flippedness)
{
    if (flippedness == flipValue)
        return;

    if (flipValue < 0.5 && flippedness >= 0.5)
        q->setPixmap(frontPixmap);
    else if (flipValue >= 0.5 && flippedness < 0.5)
        q->setPixmap(backPixmap);

    flipValue = flippedness;

    qreal xOffset = deck->cardWidth() * (0.5 - qAbs(flippedness - 0.5));
    qreal xScale = qAbs(2 * flippedness - 1);

    q->setTransform(QTransform().translate(xOffset, 0).scale(xScale, 1));
}

qreal KCardPrivate::flippedness() const
{
    return flipValue;
}

void KCardPrivate::setHighlightedness(qreal highlightedness)
{
    highlightValue = highlightedness;
    q->update();
}

qreal KCardPrivate::highlightedness() const
{
    return highlightValue;
}

KCard::KCard(quint32 id, KAbstractCardDeck *deck)
    : QObject()
    , QGraphicsPixmapItem()
    , d(new KCardPrivate(this))
{
    d->id = id;
    d->deck = deck;

    d->faceUp = true;
    d->flipValue = d->faceUp ? 1 : 0;
    d->highlighted = false;
    d->highlightValue = d->highlighted ? 1 : 0;

    d->pile = nullptr;

    d->animation = nullptr;

    d->fadeAnimation = new QPropertyAnimation(d, "highlightedness", d);
    d->fadeAnimation->setDuration(150);
    d->fadeAnimation->setKeyValueAt(0, 0);
    d->fadeAnimation->setKeyValueAt(1, 1);
}

KCard::~KCard()
{
    stopAnimation();

    // If the card is in a pile, remove it from there.
    if (pile())
        pile()->remove(this);
}

int KCard::type() const
{
    return KCard::Type;
}

quint32 KCard::id() const
{
    return d->id;
}

int KCard::rank() const
{
    return d->deck->rankFromId(d->id);
}

int KCard::suit() const
{
    return d->deck->suitFromId(d->id);
}

int KCard::color() const
{
    return d->deck->colorFromId(d->id);
}

void KCard::setPile(KCardPile *pile)
{
    d->pile = pile;
}

KCardPile *KCard::pile() const
{
    return d->pile;
}

void KCard::setFaceUp(bool faceUp)
{
    qreal flippedness = faceUp ? 1.0 : 0.0;
    if (d->faceUp != faceUp || d->flipValue != flippedness) {
        d->faceUp = faceUp;
        d->setFlippedness(flippedness);
    }
}

bool KCard::isFaceUp() const
{
    return d->faceUp;
}

void KCard::animate(QPointF pos, qreal z, qreal rotation, bool faceUp, bool raised, int duration)
{
    stopAnimation();

    if (duration > 0 && (qAbs(pos.x() - x()) > 2 || qAbs(pos.y() - y()) > 2 || qAbs(rotation - this->rotation()) > 2 || faceUp != d->faceUp)) {
        if (raised)
            raise();

        d->destZ = z;
        d->faceUp = faceUp;

        d->animation = new KCardAnimation(d, duration, pos, rotation, faceUp);
        connect(d->animation, &KCardAnimation::finished, this, &KCard::stopAnimation);
        d->animation->start();
        Q_EMIT animationStarted(this);
    } else {
        setPos(pos);
        setZValue(z);
        setRotation(rotation);
        setFaceUp(faceUp);
    }
}

bool KCard::isAnimated() const
{
    return d->animation != nullptr;
}

void KCard::raise()
{
    if (zValue() < raisedZValue)
        setZValue(raisedZValue + zValue());
}

void KCard::setHighlighted(bool flag)
{
    if (flag != d->highlighted) {
        d->highlighted = flag;

        d->fadeAnimation->setDirection(flag ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);

        if (d->fadeAnimation->state() != QAbstractAnimation::Running)
            d->fadeAnimation->start();
    }
}

bool KCard::isHighlighted() const
{
    return d->highlighted;
}

void KCard::completeAnimation()
{
    if (!d->animation)
        return;

    d->animation->disconnect(this);
    if (d->animation->state() != QAbstractAnimation::Stopped)
        d->animation->setCurrentTime(d->animation->duration());

    stopAnimation();
}

void KCard::stopAnimation()
{
    if (!d->animation)
        return;

    delete d->animation;
    d->animation = nullptr;

    setZValue(d->destZ);

    Q_EMIT animationStopped(this);
}

void KCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    if (pixmap().size() != d->deck->cardSize() * pixmap().devicePixelRatio()) {
        QPixmap newPix = d->deck->cardPixmap(d->id, d->faceUp);
        if (d->faceUp)
            setFrontPixmap(newPix);
        else
            setBackPixmap(newPix);
    }

    // Enable smooth pixmap transformation only if the card is rotated. We
    // don't really need it otherwise and it slows down our flip animations.
    painter->setRenderHint(QPainter::SmoothPixmapTransform, int(rotation()) % 90);

    QPixmap pix = pixmap();

    if (d->highlightValue > 0) {
        QPainter p(&pix);
        p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
        p.fillRect(0, 0, pix.width(), pix.height(), QColor::fromRgbF(0, 0, 0, 0.5 * d->highlightValue));
    }

    painter->drawPixmap(0, 0, pix);
}

void KCard::setFrontPixmap(const QPixmap &pix)
{
    d->frontPixmap = pix;
    if (d->flipValue >= 0.5)
        setPixmap(d->frontPixmap);
}

void KCard::setBackPixmap(const QPixmap &pix)
{
    d->backPixmap = pix;
    if (d->flipValue < 0.5)
        setPixmap(d->backPixmap);
}

void KCard::setPixmap(const QPixmap &pix)
{
    QGraphicsPixmapItem::setPixmap(pix);
}

#include "moc_kcard.cpp"
#include "moc_kcard_p.cpp"
