/*
 *   Copyright (C) 2007 Petri Damsten <damu@iki.fi>
 *
 *   This program 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, 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 Library General Public
 *   License along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "meter.h"

#include <cmath>

#include <QPainter>
#include <QTimeLine>

#include <kdebug.h>
#include <kglobalsettings.h>

#include "plasma/animator.h"
#include "plasma/framesvg.h"
#include "plasma/theme.h"

namespace Plasma {

class MeterPrivate
{
public:
    MeterPrivate(Meter *m)
        : minimum(0),
          maximum(100),
          value(0),
          targetValue(0),
          meterType(Meter::AnalogMeter),
          image(0),
          minrotate(0),
          maxrotate(360),
          meter(m),
          movementId(0)
    {
    }

    void progressChanged(qreal progress)
    {
        bool over = qFuzzyCompare(progress, qreal(1.0));

        if (value == targetValue) {
            if (!over && movementId) {
                Animator::self()->stopCustomAnimation(movementId);
            }

            return;
        }

        if (over) {
            value = targetValue;
            //kDebug() << "done";
            movementId = 0;
        } else {
            int frame = progress * 10;
            int delta = targetValue - value;
            value += (delta / qreal(10 - frame));
            //kDebug() << frame << value << targetValue;
        }

        meter->update();
    }

    void paint(QPainter *p, const QString &elementID)
    {
        if (image->hasElement(elementID)) {
            QRectF elementRect = image->elementRect(elementID);
            image->paint(p, elementRect, elementID);
        }
    }

    void text(QPainter *p, int index)
    {
        QString elementID = QString("label%1").arg(index);
        QString text = labels[index];

        if (image->hasElement(elementID)) {
            QRectF elementRect = image->elementRect(elementID);
            Qt::Alignment align = Qt::AlignCenter;

            if (colors.count() > index) {
                p->setPen(QPen(colors[index]));
            }
            if (fonts.count() > index) {
                p->setFont(fonts[index]);
            }
            if (alignments.count() > index) {
                align = alignments[index];
            }
            if (elementRect.width() > elementRect.height()) {
                p->drawText(elementRect, align, text);
            } else {
                p->save();
                QPointF rotateCenter(
                        elementRect.left() + elementRect.width() / 2,
                        elementRect.top() + elementRect.height() / 2);
                p->translate(rotateCenter);
                p->rotate(-90);
                p->translate(elementRect.height() / -2,
                             elementRect.width() / -2);
                QRectF r(0, 0, elementRect.height(), elementRect.width());
                p->drawText(r, align, text);
                p->restore();
            }
        }
    }

    QRectF barRect()
    {
        QRectF elementRect;

        if (labels.count() > 0) {
            elementRect = image->elementRect("background");
        } else {
            elementRect = QRectF(QPoint(0,0), meter->size());
        }

        if (image->hasElement("hint-bar-stretch") || !image->hasElement("bar-active-center")) {
            return elementRect;
        }

        QSize imageSize = image->size();
        image->resize();
        QSize tileSize = image->elementSize("bar-active-center");
        image->resize(imageSize);

        if (elementRect.width() > elementRect.height()) {
            qreal ratio = qMax(1, tileSize.height() / tileSize.width());
            int numTiles = qMax(qreal(1.0), qreal(elementRect.width())/(qreal(elementRect.height())/ratio));
            tileSize = QSize(elementRect.width()/numTiles, elementRect.height());

            QPoint center = elementRect.center().toPoint();
            elementRect.setWidth(tileSize.width()*numTiles);
            elementRect.moveCenter(center);
        } else {
            qreal ratio = qMax(1, tileSize.width() / tileSize.height());
            int numTiles = qMax(qreal(1.0), qreal(elementRect.height())/(qreal(elementRect.width())/ratio));
            tileSize = QSize(elementRect.width(), elementRect.height()/numTiles);

            QPoint center = elementRect.center().toPoint();
            elementRect.setHeight(tileSize.height()*numTiles);
            elementRect.moveCenter(center);
        }

        return elementRect;
    }

    void paintBackground(QPainter *p)
    {
        //be retrocompatible with themes for kde <= 4.1
        if (image->hasElement("background-center")) {
            QRectF elementRect = barRect();
            if (elementRect.isEmpty()) {
                return; // nothing to be done 
            }

            QSize imageSize = image->size();
            image->resize();

            image->setElementPrefix("background");
            image->resizeFrame(elementRect.size());
            image->paintFrame(p, elementRect.topLeft());
            image->resize(imageSize);

            paintBar(p, "bar-inactive");
        } else {
            paint(p, "background");
        }
    }

    void paintBar(QPainter *p, const QString &prefix)
    {
        QRectF elementRect = barRect();

        image->setUsingRenderingCache(false);
        if (image->hasElement("hint-bar-stretch")) {
            image->resizeFrame(elementRect.size());
            image->paintFrame(p);
        } else {
            QSize imageSize = image->size();
            image->resize();
            QSize tileSize = image->elementSize("bar-active-center");

            if (elementRect.width() > elementRect.height()) {
                qreal ratio = tileSize.height() / tileSize.width();
                int numTiles = elementRect.width()/(elementRect.height()/ratio);
                tileSize = QSize(elementRect.width()/numTiles, elementRect.height());
            } else {
                qreal ratio = tileSize.width() / tileSize.height();
                int numTiles = elementRect.height()/(elementRect.width()/ratio);
                tileSize = QSize(elementRect.width(), elementRect.height()/numTiles);
            }

            image->setElementPrefix(prefix);
            image->resizeFrame(tileSize);
            p->drawTiledPixmap(elementRect, image->framePixmap());
            image->resize(imageSize);
        }
        image->setUsingRenderingCache(true);
    }

    void paintForeground(QPainter *p)
    {
        for (int i = 0; i < labels.count(); ++i) {
            text(p, i);
        }

        paint(p, "foreground");
    }

    void setSizePolicyAndPreferredSize()
    {
        switch (meterType) {
            case Meter::BarMeterHorizontal:
                meter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
                break;
            case Meter::BarMeterVertical:
                meter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
                break;
            case Meter::AnalogMeter:
            default:
                meter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
                break;
        }

        if (image) {
            //set a sane preferredSize. We can't just use the svg's native size, since that way
            //letters get cut off if the user uses a font larger then usual. Check how many rows of
            //labels we have, add 1 (the progress bar), and multiply by the font height to get a
            //somewhat sane size height. This is not perfect but work well enough for 4.2. I suggest
            //we look into alternatives for 4.3.
            uint i = 0;
            uint rows = 0;
            qreal prevY = -1;
            QString labelName = "label0";
            while (image->hasElement(labelName)) {
                if (image->elementRect(labelName).y() > prevY) {
                    prevY = image->elementRect(labelName).y();
                    rows++;
                }
                i++;
                labelName = QString("label%0").arg(i);
            }

            Plasma::Theme *theme = Plasma::Theme::defaultTheme();
            QFont font = theme->font(Plasma::Theme::DefaultFont);
            QFontMetrics fm(font);

            meter->setPreferredHeight((rows + 1) * fm.height());
        } else {
            meter->setPreferredSize(QSizeF(30, 30));
        }
    }

    int minimum;
    int maximum;
    int value;
    int targetValue;
    QStringList labels;
    QList<Qt::Alignment> alignments;
    QList<QColor> colors;
    QList<QFont> fonts;
    QString svg;
    Meter::MeterType meterType;
    Plasma::FrameSvg *image;
    int minrotate;
    int maxrotate;
    Meter *meter;
    int movementId;
};

Meter::Meter(QGraphicsItem *parent) :
        QGraphicsWidget(parent),
        d(new MeterPrivate(this))
{
    d->setSizePolicyAndPreferredSize();
}

Meter::~Meter()
{
    delete d;
}

void Meter::setMaximum(int maximum)
{
    d->maximum = maximum;
}

int Meter::maximum() const
{
    return d->maximum;
}

void Meter::setMinimum(int minimum)
{
    d->minimum = minimum;
}

int Meter::minimum() const
{
    return d->minimum;
}

void Meter::setValue(int value)
{
    if (value == d->targetValue) {
        return;
    }

    d->targetValue = qBound(d->minimum, value, d->maximum);
    int delta = abs(d->value - d->targetValue);

    if (d->movementId) {
        Animator::self()->stopCustomAnimation(d->movementId);
        d->movementId = 0;
    }

    //kDebug() << d->targetValue << d->value << delta;
    if (!(KGlobalSettings::graphicEffectsLevel() & KGlobalSettings::SimpleAnimationEffects) ||
        delta / qreal(d->maximum) < 0.1) {
        d->value = value;
        update();
    } else  {
        d->movementId = Animator::self()->customAnimation(10, 100, Animator::EaseOutCurve,
                                                          this, "progressChanged");
    }
}

int Meter::value() const
{
    return d->value;
}

void Meter::setLabel(int index, const QString &text)
{
    while (d->labels.count() <= index) {
        d->labels << QString();
    }
    d->labels[index] = text;
}

QString Meter::label(int index) const
{
    return d->labels[index];
}

void Meter::setLabelColor(int index, const QColor &color)
{
    while (d->colors.count() <= index) {
        d->colors << color;
    }
    d->colors[index] = color;
}

QColor Meter::labelColor(int index) const
{
    return d->colors[index];
}

void Meter::setLabelFont(int index, const QFont &font)
{
    while (d->fonts.count() <= index) {
        d->fonts << font;
    }
    d->fonts[index] = font;
}

QFont Meter::labelFont(int index) const
{
    return d->fonts[index];
}

void Meter::setLabelAlignment(int index, const Qt::Alignment alignment)
{
    while (d->alignments.count() <= index) {
        d->alignments << alignment;
    }
    d->alignments[index] = alignment;
}

Qt::Alignment Meter::labelAlignment(int index) const
{
    return d->alignments[index];
}

QRectF Meter::labelRect(int index) const
{
    QString elementID = QString("label%1").arg(index);
    return d->image->elementRect(elementID);
}

void Meter::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data)
{
    Q_UNUSED(sourceName)

    foreach (const QVariant &v, data) {
        if (v.type() == QVariant::Int ||
            v.type() == QVariant::UInt ||
            v.type() == QVariant::LongLong ||
            v.type() == QVariant::ULongLong) {
            setValue(v.toInt());
            return;
        }
    }
}

void Meter::setSvg(const QString &svg)
{
    d->svg = svg;
    delete d->image;
    d->image = new Plasma::FrameSvg(this);
    d->image->setImagePath(svg);
    // To create renderer and get default size
    d->image->resize();
    d->setSizePolicyAndPreferredSize();
    if (d->image->hasElement("rotateminmax")) {
        QRectF r = d->image->elementRect("rotateminmax");
        d->minrotate = (int)r.height();
        d->maxrotate = (int)r.width();
    }
}

QString Meter::svg() const
{
    return d->svg;
}

void Meter::setMeterType(MeterType meterType)
{
    d->meterType = meterType;
    if (d->svg.isEmpty()) {
        if (meterType == BarMeterHorizontal) {
            setSvg("widgets/bar_meter_horizontal");
        } else if (meterType == BarMeterVertical) {
            setSvg("widgets/bar_meter_vertical");
        } else if (meterType == AnalogMeter) {
            setSvg("widgets/analog_meter");
        }
    }
    d->setSizePolicyAndPreferredSize();
}

Meter::MeterType Meter::meterType() const
{
    return d->meterType;
}

void Meter::paint(QPainter *p,
                  const QStyleOptionGraphicsItem *option,
                  QWidget *widget)
{
    Q_UNUSED(option)
    Q_UNUSED(widget)

    if (!d->image) {
        return;
    }

    QRectF rect(QPointF(0, 0), size());
    QRectF clipRect;
    qreal percentage = 0.0;
    qreal angle = 0.0;
    QPointF rotateCenter;
    QSize intSize = QSize((int)size().width(), (int)size().height());

    if (intSize != d->image->size()) {
        d->image->resize(intSize);
    }

    if (d->maximum != d->minimum) {
        percentage = (qreal)d->value / (d->maximum - d->minimum);
    }

    p->setRenderHint(QPainter::SmoothPixmapTransform);
    switch (d->meterType) {
    case BarMeterHorizontal:
    case BarMeterVertical:
        d->paintBackground(p);

        p->save();
        clipRect = d->barRect();
        if (clipRect.width() > clipRect.height()) {
            clipRect.setWidth(clipRect.width() * percentage);
        } else {
            qreal bottom = clipRect.bottom();
            clipRect.setHeight(clipRect.height() * percentage);
            clipRect.moveBottom(bottom);
        }
        p->setClipRect(clipRect);

        //be retrocompatible
        if (d->image->hasElement("bar-active-center")) {
            d->paintBar(p, "bar-active");
        } else {
            d->paint(p, "bar");
        }
        p->restore();

        d->paintForeground(p);
        break;
    case AnalogMeter:
        d->paintBackground(p);

        p->save();
        if (d->image->hasElement("rotatecenter")) {
            QRectF r = d->image->elementRect("rotatecenter");
            rotateCenter = QPointF(r.left() + r.width() / 2,
                                   r.top() + r.height() / 2);
        } else {
            rotateCenter = QPointF(rect.width() / 2, rect.height() / 2);
        }
        angle = percentage * (d->maxrotate - d->minrotate) + d->minrotate;

        if (d->image->hasElement("pointer-shadow")) {
            p->save();
            p->translate(rotateCenter+QPoint(2,3));
            p->rotate(angle);
            p->translate(-1 * rotateCenter);
            d->paint(p, "pointer-shadow");
            p->restore();
        }

        p->translate(rotateCenter);
        p->rotate(angle);
        p->translate(-1 * rotateCenter);
        d->paint(p, "pointer");
        p->restore();

        d->paintForeground(p);
        break;
    }
}

} // End of namepace

#include "meter.moc"