/*
 *   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 version 2 as
 *   published by the Free Software Foundation
 *
 *   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 "applet.h"
#include <math.h>
#include <Plasma/DataEngine>
#include <Plasma/Containment>
#include <Plasma/Frame>
#include <Plasma/IconWidget>
#include <Plasma/SignalPlotter>
#include <Plasma/ToolTipManager>
#include <KIcon>
#include <KDebug>
#include <QGraphicsLinearLayout>

namespace SM {

Applet::Applet(QObject *parent, const QVariantList &args)
   : Plasma::Applet(parent, args),
     m_interval(10000),
     m_preferredItemHeight(42),
     m_minimumWidth(MINIMUM),
     m_titleSpacer(false),
     m_header(0),
     m_engine(0),
     m_ratioOrientation(Qt::Vertical),
     m_orientation(Qt::Vertical),
     m_noSourcesIcon(0),
     m_mode(Desktop),
     m_detail(Low),
     m_mainLayout(0),
     m_configSource(0)
{
    if (args.count() > 0 && args[0].toString() == "SM") {
        m_mode = Monitor;
    }

    Plasma::ToolTipManager::self()->registerWidget(this);
}

Applet::~Applet()
{
    deleteMeters();
}

void Applet::constraintsEvent(Plasma::Constraints constraints)
{
    if (constraints & Plasma::FormFactorConstraint) {
        if (m_mode == Monitor) {
            setBackgroundHints(NoBackground);
            m_orientation = Qt::Vertical;
        } else {
            SM::Applet::Mode mode = m_mode;
            switch (formFactor()) {
                case Plasma::Planar:
                case Plasma::MediaCenter:
                    mode = Desktop;
                    m_orientation = Qt::Vertical;
                    break;
                case Plasma::Horizontal:
                    mode = Panel;
                    m_orientation = Qt::Horizontal;
                    break;
                case Plasma::Vertical:
                    mode = Panel;
                    m_orientation = Qt::Vertical;
                    break;
            }
            if (mode != m_mode) {
                m_mode = mode;
                m_ratioOrientation = m_orientation;
                connectToEngine();
            }
        }
    } else if (constraints & Plasma::SizeConstraint) {
        checkGeometry();
        checkPlotters();
        if (m_keepRatio.count() > 0) {
            foreach (QGraphicsWidget* item, m_keepRatio) {
                QSizeF size = QSizeF(qMin(item->size().width(), contentsRect().size().width()),
                                     qMin(item->size().height(), contentsRect().size().height()));

                if (size == QSizeF(0, 0)) {
                    continue;
                }
                qreal ratio = item->preferredSize().height() / item->preferredSize().width();
                if (m_ratioOrientation == Qt::Vertical) {
                    size = QSizeF(size.width(), size.width() * ratio);
                } else {
                    size = QSizeF(size.height() * (1.0 / ratio), size.height());
                }
                item->setPreferredSize(size);
                if (m_mode == Panel) {
                    item->setMaximumSize(size);
                    item->setMinimumSize(size);
                }
            }
            for (int i = mainLayout()->count() - 1; i >= 0; --i) {
                QGraphicsLayoutItem* item = mainLayout()->itemAt(i);
                if (item) {
                    QGraphicsLinearLayout* l = dynamic_cast<QGraphicsLinearLayout *>(item);
                    if (l) {
                        l->invalidate();
                    }
                }
            }
        }
    }
}

void Applet::setTitle(const QString& title, bool spacer)
{
    m_title = title;
    m_titleSpacer = spacer;
    if (m_header) {
        m_header->setText(m_title);
    }
}

QGraphicsLinearLayout* Applet::mainLayout()
{
   if (!m_mainLayout) {
      m_mainLayout = new QGraphicsLinearLayout(m_orientation);
      m_mainLayout->setContentsMargins(0, 0, 0, 0);
      m_mainLayout->setSpacing(0);
      setLayout(m_mainLayout);
   }
   return m_mainLayout;
}

void Applet::connectToEngine()
{
    deleteMeters();
    // We delete the layout since it seems to be only way to remove stretch set for some applets.
    setLayout(0);
    m_mainLayout = 0;
    disconnectSources();

    mainLayout()->setOrientation(m_orientation);
    if (m_mode != Panel) {
        m_header = new Plasma::Frame(this);
        m_header->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
        m_header->setText(m_title);
        mainLayout()->addItem(m_header);
    }

    if (m_items.isEmpty()){
        displayNoAvailableSources();
        return;
    }

    foreach (const QString &item, m_items) {
        if (addMeter(item)) {
            connectSource(item);
        }
    }

    if (m_titleSpacer) {
        mainLayout()->addStretch();
    }
    mainLayout()->activate();
    constraintsEvent(Plasma::SizeConstraint);
}

void Applet::checkPlotters()
{
    if (m_plotters.isEmpty()) {
        return;
    }

    Plasma::SignalPlotter *plotter = m_plotters.begin().value();
    QFontMetrics metrics(plotter->font());
    bool showTopBar = (metrics.height() < plotter->size().height() / 3);

    foreach (plotter, m_plotters) {
        plotter->setShowTopBar(showTopBar);
    }

    Detail detail;

    if (size().width() > 250 && size().height() / m_items.count() > 150) {
        detail = High;
    } else {
        detail = Low;
    }

    if (m_detail != detail && m_mode != Monitor) {
        m_detail = detail;
        foreach (plotter, m_plotters) {
            plotter->setShowLabels(detail == SM::Applet::High);
            plotter->setShowHorizontalLines(detail == SM::Applet::High);
        }
    }
}

void Applet::checkGeometry()
{
    if (m_mode != Panel) {
        qreal height = 0;
        qreal width = MINIMUM;

        if (m_header) {
            height = m_header->minimumSize().height();
            width = m_header->minimumSize().width();
        }
        m_min.setHeight(qMax(height + m_items.count() * MINIMUM,
                             mainLayout()->minimumSize().height()));
        m_min.setWidth(qMax(width + MINIMUM, m_minimumWidth));
        m_pref.setHeight(height + m_items.count() * m_preferredItemHeight);
        m_pref.setWidth(PREFERRED);
        m_max = QSizeF();
        if (m_mode != Monitor) {
            m_min += size() - contentsRect().size();
            m_pref += size() - contentsRect().size();
        } else {
            // Reset margins
            setBackgroundHints(NoBackground);
        }
        //kDebug() << minSize << m_preferredItemHeight << height
        //         << m_minimumHeight << metaObject()->className();

        setAspectRatioMode(Plasma::IgnoreAspectRatio);
        setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
        update();
    } else {
        int x = 1;
        int y = 1;
        QSizeF size = containment()->size();
        qreal s;

        if (m_orientation == Qt::Horizontal) {
            x = m_items.count();
            s = size.height();
        } else {
            y = m_items.count();
            s = size.width();
        }
        m_min = QSizeF(16 * x, 16 * y);
        m_max = m_pref = QSizeF(s * x, s * y);
        setAspectRatioMode(Plasma::KeepAspectRatio);
        setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    }
    setMinimumSize(m_min);
    setPreferredSize(m_pref);
    setMaximumSize(m_max);
    //kDebug() << m_min << m_pref << m_max << metaObject()->className();
    emit geometryChecked();
}

void Applet::connectSource(const QString& source)
{
   if (m_engine) {
      m_engine->connectSource(source, this, m_interval);
      m_connectedSources << source;
   }
}

void Applet::disconnectSources()
{
   Plasma::DataEngine *engine = dataEngine("soliddevice");
   if (engine) {
      foreach (const QString &source, m_connectedSources) {
         engine->disconnectSource(source, this);
      }
   }
   m_connectedSources.clear();
}

void Applet::deleteMeters(QGraphicsLinearLayout* layout)
{
    if (!layout) {
        layout = m_mainLayout;
        if (!layout) {
            return;
        }
        m_meters.clear();
        m_plotters.clear();
        m_keepRatio.clear();
        m_toolTips.clear();
        m_header = 0;
    }
    m_overlayFrames.clear();
    for (int i = layout->count() - 1; i >= 0; --i) {
        QGraphicsLayoutItem* item = layout->itemAt(i);
        if (item) {
            QGraphicsLinearLayout* l = dynamic_cast<QGraphicsLinearLayout *>(item);
            if (l) {
                deleteMeters(l);
            }
        }
        layout->removeAt(i);
        delete item;
    }
}

void Applet::displayNoAvailableSources()
{
    KIcon appletIcon(icon());
    m_noSourcesIcon = new Plasma::IconWidget(appletIcon, "", this);
    mainLayout()->addItem(m_noSourcesIcon);
}

KConfigGroup Applet::config()
{
    if (m_configSource) {
        return m_configSource->config();
    }

    return Plasma::Applet::config();
}

void Applet::save(KConfigGroup &config) const
{
    // work around for saveState being protected
    if (m_mode != Monitor) {
        Plasma::Applet::save(config);
    }
}

void Applet::saveConfig(KConfigGroup &config)
{
    // work around for saveState being protected
    saveState(config);
}

QVariant Applet::itemChange(GraphicsItemChange change, const QVariant &value)
{
    if (m_mode == Monitor && change == ItemParentHasChanged) {
        QGraphicsWidget *parent = parentWidget();
        Plasma::Applet *container = 0;
        while (parent) {
            container = qobject_cast<Plasma::Applet *>(parent);

            if (container) {
                break;
            }

            parent = parent->parentWidget();
        }

        if (container && container != containment()) {
            m_configSource = container;
        }
    }

    // We must be able to change position when in monitor even if not mutable
    if (m_mode == Monitor && change == ItemPositionChange) {
        return QGraphicsWidget::itemChange(change, value);
    } else {
        return Plasma::Applet::itemChange(change, value);
    }
}

QColor Applet::adjustColor(const QColor& color, uint percentage)
{
    qreal h, s, v, a;
    color.getHsvF(&h, &s, &v, &a);
    qreal d = fabs(v - 0.5) * (percentage / 100.0);
    if (v > 0.5) {
        v -= d;
    } else {
        v += d;
    }
    return QColor::fromHsvF(h, s, v, a);
}

void Applet::toolTipAboutToShow()
{
    if (mode() == SM::Applet::Panel && !m_toolTips.isEmpty()) {
        QString html = "<table>";
        foreach (const QString& s, items()) {
            QString senstorHtml = m_toolTips.value(s);
            if (!senstorHtml.isEmpty()) {
                html += senstorHtml;
            }
        }
        html += "</table>";
        Plasma::ToolTipContent data(title(), html);
        Plasma::ToolTipManager::self()->setContent(this, data);
    }
}

void Applet::setPlotterOverlayText(Plasma::SignalPlotter* plotter, const QString& text)
{
    if (!m_overlayFrames.contains(plotter)) {
        Plasma::Frame* frame;
        QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(Qt::Vertical, plotter);
        plotter->setLayout(layout);
        frame = new Plasma::Frame(plotter);
        frame->setZValue(10);
        frame->resize(frame->size().height() * 2.5, frame->size().height());
        m_overlayFrames[plotter] = frame;
        frame->hide();
        layout->addStretch();
        QGraphicsLinearLayout* layout2 = new QGraphicsLinearLayout(Qt::Horizontal, layout);
        layout2->addStretch();
        layout2->addItem(frame);
        layout2->addStretch();
        layout->addItem(layout2);
    }
    m_overlayFrames[plotter]->setText(text);
    m_overlayFrames[plotter]->show();
}

}
