/**
 ******************************************************************************
 *
 * @file       dialgadgetwidget.cpp
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @see        The GNU Public License (GPL) Version 3
 * @addtogroup GCSPlugins GCS Plugins
 * @{
 * @addtogroup DialPlugin Dial Plugin
 * @{
 * @brief Plots flight information rotary style dials 
 *****************************************************************************/
/*
 * 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 3 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, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "dialgadgetwidget.h"
#include <utils/stylehelper.h>
#include <iostream>
#include <QDebug>

DialGadgetWidget::DialGadgetWidget(QWidget *parent) : QGraphicsView(parent)
{
    // TODO: create a proper "needle" object instead of hardcoding all this
    // which is ugly (but easy).

    setMinimumSize(64,64);
    setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    setScene(new QGraphicsScene(this));
    setRenderHints(QPainter::Antialiasing);

    m_renderer = new QSvgRenderer();

    obj1 = NULL;
    obj2 = NULL;
    obj3 = NULL;
    m_text1 = NULL;
    m_text2 = NULL;
    m_text3 = NULL; // Should be initialized to NULL otherwise the setFont method
                    // might segfault upon initialization if called before SetDialFile

    needle1Target = 0;
    needle2Target = 0;
    needle3Target = 0;

    // This timer mechanism makes needles rotate smoothly
    connect(&dialTimer, SIGNAL(timeout()), this, SLOT(rotateNeedles()));
}

DialGadgetWidget::~DialGadgetWidget()
{
   // Do nothing
}

/*!
  \brief Connects the widget to the relevant UAVObjects
  */
void DialGadgetWidget::connectNeedles(QString object1, QString nfield1,
                                          QString object2, QString nfield2,
                                          QString object3, QString nfield3) {
    if (obj1 != NULL)
        disconnect(obj1,SIGNAL(objectUpdated(UAVObject*)),this,SLOT(updateNeedle1(UAVObject*)));
    if (obj2 != NULL)
        disconnect(obj2,SIGNAL(objectUpdated(UAVObject*)),this,SLOT(updateNeedle2(UAVObject*)));
    if (obj3 != NULL)
        disconnect(obj3,SIGNAL(objectUpdated(UAVObject*)),this,SLOT(updateNeedle3(UAVObject*)));

    ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
    UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();

    // Check validity of arguments first, reject empty args and unknown fields.
    if (!(object1.isEmpty() || nfield1.isEmpty())) {
        obj1 = dynamic_cast<UAVDataObject*>( objManager->getObject(object1) );
        if (obj1 != NULL ) {
            qDebug() << "Connected Object 1 (" << object1 << ").";
            connect(obj1, SIGNAL(objectUpdated(UAVObject*)), this, SLOT(updateNeedle1(UAVObject*)));
            field1 = nfield1;
        } else {
            qDebug() << "Error: Object is unknown (" << object1 << ").";
        }
    }

    // And do the same for the second needle.
    if (!(object2.isEmpty() || nfield2.isEmpty())) {
        obj2 = dynamic_cast<UAVDataObject*>( objManager->getObject(object2) );
        if (obj2 != NULL ) {
            qDebug() << "Connected Object 2 (" << object2 << ").";
            connect(obj2, SIGNAL(objectUpdated(UAVObject*)), this, SLOT(updateNeedle2(UAVObject*)));
            field2 = nfield2;
        } else {
            qDebug() << "Error: Object is unknown (" << object2 << ").";
        }
    }

    // And do the same for the third needle.
    if (!(object3.isEmpty() || nfield3.isEmpty())) {
        obj3 = dynamic_cast<UAVDataObject*>( objManager->getObject(object3) );
        if (obj3 != NULL ) {
            qDebug() << "Connected Object 3 (" << object3 << ").";
            connect(obj3, SIGNAL(objectUpdated(UAVObject*)), this, SLOT(updateNeedle3(UAVObject*)));
            field3 = nfield3;
        } else {
            qDebug() << "Error: Object is unknown (" << object3 << ").";
        }
    }
}

/*!
  \brief Called by the UAVObject which got updated
  */
void DialGadgetWidget::updateNeedle1(UAVObject *object1) {
    // Double check that the field exists:
    UAVObjectField* field = object1->getField(field1);
    if (field) {
        setNeedle1(field->getDouble());
    } else {
        qDebug() << "Wrong field, maybe an issue with object disconnection ?";
    }
}

/*!
  \brief Called by the UAVObject which got updated
  */
void DialGadgetWidget::updateNeedle2(UAVObject *object2) {
    UAVObjectField* field = object2->getField(field2);
    if (field) {
        setNeedle2(field->getDouble());
    } else {
        qDebug() << "Wrong field, maybe an issue with object disconnection ?";
    }
}

/*!
  \brief Called by the UAVObject which got updated
  */
void DialGadgetWidget::updateNeedle3(UAVObject *object3) {
    UAVObjectField* field = object3->getField(field3);
    if (field) {
        setNeedle3(field->getDouble());
    } else {
        qDebug() << "Wrong field, maybe an issue with object disconnection ?";
    }
}

/*
  Initializes the dial file, and does all the one-time calculations for
  display later. This is the method which really initializes the dial.
  */
void DialGadgetWidget::setDialFile(QString dfn, QString bg, QString fg, QString n1, QString n2,
                                       QString n3, QString n1Move, QString n2Move, QString n3Move)
{
   fgenabled = false;
   n2enabled = false;
   n3enabled = false;
   QGraphicsScene *l_scene = scene();
   setBackgroundBrush(QBrush(Utils::StyleHelper::baseColor()));
   if (QFile::exists(dfn) && m_renderer->load(dfn) && m_renderer->isValid())
   {
     l_scene->clear(); // This also deletes all items contained in the scene.
     m_background = new QGraphicsSvgItem();
     // All other items will be clipped to the shape of the background
     m_background->setFlags(QGraphicsItem::ItemClipsChildrenToShape|
                            QGraphicsItem::ItemClipsToShape);
     m_foreground = new QGraphicsSvgItem();
     m_needle1 = new QGraphicsSvgItem();
     m_needle2 = new QGraphicsSvgItem();
     m_needle3 = new QGraphicsSvgItem();
     m_needle1->setParentItem(m_background);
     m_needle2->setParentItem(m_background);
     m_needle3->setParentItem(m_background);
     m_foreground->setParentItem(m_background);

     // We assume the dial contains at least the background
     // and needle1
     m_background->setSharedRenderer(m_renderer);
     m_background->setElementId(bg);
     l_scene->addItem(m_background);

     m_needle1->setSharedRenderer(m_renderer);
     m_needle1->setElementId(n1);
     // Note: no need to add the item explicitely because it
     // is done automatically since it's a child item of the
     // background.
     //l_scene->addItem(m_needle1);

    // The dial gadget allows Needle1 and Needle2 to be
    // the same element, for combined movement. Needle3
    // is always independent.
    if (n1 == n2) {
        m_needle2 = m_needle1;
        n2enabled = true;
    } else {
     if (m_renderer->elementExists(n2)) {
         m_needle2->setSharedRenderer(m_renderer);
         m_needle2->setElementId(n2);
         //l_scene->addItem(m_needle2);
         n2enabled = true;
        }
     }

    if (m_renderer->elementExists(n3)) {
        m_needle3->setSharedRenderer(m_renderer);
        m_needle3->setElementId(n3);
        //l_scene->addItem(m_needle3);
        n3enabled = true;
       }

    if (m_renderer->elementExists(fg)) {
        m_foreground->setSharedRenderer(m_renderer);
        m_foreground->setElementId(fg);
        //l_scene->addItem(m_foreground);
        fgenabled = true;
    }

    rotateN1 = false;
    horizN1 =  false;
    vertN1 = false;
    rotateN2 = false;
    horizN2 = false;
    vertN2 = false;
    rotateN3 = false;
    horizN3 = false;
    vertN3 = false;

    // Now setup the rotation/translation settings:
    // this is UGLY UGLY UGLY, sorry...
    if (n1Move.contains("Rotate")) {
        rotateN1 = true;
    } else if (n1Move.contains("Horizontal")) {
        horizN1 = true;
    } else if (n1Move.contains("Vertical")) {
        vertN1 = true;
    }

    if (n2Move.contains("Rotate")) {
        rotateN2 = true;
    } else if (n2Move.contains("Horizontal")) {
        horizN2 = true;
    } else if (n2Move.contains("Vertical")) {
        vertN2 = true;
    }

    if (n3Move.contains("Rotate")) {
        rotateN3 = true;
    } else if (n3Move.contains("Horizontal")) {
        horizN3 = true;
    } else if (n3Move.contains("Vertical")) {
        vertN3 = true;
    }

    l_scene->setSceneRect(m_background->boundingRect());

    // Now Initialize the center for all transforms of the dial needles to the
    // center of the background:
    // - Move the center of the needle to the center of the background.
    QRectF rectB = m_background->boundingRect();
    QRectF rectN = m_needle1->boundingRect();
    m_needle1->setPos(rectB.width()/2-rectN.width()/2,rectB.height()/2-rectN.height()/2);
    // - Put the transform origin point of the needle at its center.
    m_needle1->setTransformOriginPoint(rectN.width()/2,rectN.height()/2);

    // Check whether the dial also wants display the numeric value:
    if (m_renderer->elementExists(n1+"-text")) {
        QMatrix textMatrix = m_renderer->matrixForElement(n1+"-text");
        qreal startX = textMatrix.mapRect(m_renderer->boundsOnElement(n1+"-text")).x();
        qreal startY = textMatrix.mapRect(m_renderer->boundsOnElement(n1+"-text")).y();
        QTransform matrix;
        matrix.translate(startX,startY);
        m_text1 = new QGraphicsTextItem("0.00");
        m_text1->setDefaultTextColor(QColor("White"));
        m_text1->setTransform(matrix,false);
        l_scene->addItem(m_text1);
    } else {
        m_text1 = NULL;
    }


    if ((n1 != n2) && n2enabled) {
        // Only do it for needle2 if it is not the same as n1
        rectN = m_needle2->boundingRect();
        m_needle2->setPos(rectB.width()/2-rectN.width()/2,rectB.height()/2-rectN.height()/2);
        m_needle2->setTransformOriginPoint(rectN.width()/2,rectN.height()/2);
        // Check whether the dial also wants display the numeric value:
        if (m_renderer->elementExists(n2+"-text")) {
            QMatrix textMatrix = m_renderer->matrixForElement(n2+"-text");
            qreal startX = textMatrix.mapRect(m_renderer->boundsOnElement(n2+"-text")).x();
            qreal startY = textMatrix.mapRect(m_renderer->boundsOnElement(n2+"-text")).y();
            QTransform matrix;
            matrix.translate(startX,startY);
            m_text2 = new QGraphicsTextItem("0.00");
            m_text2->setDefaultTextColor(QColor("White"));
            m_text2->setTransform(matrix,false);
            l_scene->addItem(m_text2);
        } else {
            m_text2 = NULL;
        }

    }
    if (n3enabled) {
        rectN = m_needle3->boundingRect();
        m_needle3->setPos(rectB.width()/2-rectN.width()/2,rectB.height()/2-rectN.height()/2);
        m_needle3->setTransformOriginPoint(rectN.width()/2,rectN.height()/2);
        // Check whether the dial also wants display the numeric value:
        if (m_renderer->elementExists(n3+"-text")) {
            QMatrix textMatrix = m_renderer->matrixForElement(n3+"-text");
            qreal startX = textMatrix.mapRect(m_renderer->boundsOnElement(n3+"-text")).x();
            qreal startY = textMatrix.mapRect(m_renderer->boundsOnElement(n3+"-text")).y();
            QTransform matrix;
            matrix.translate(startX,startY);
            m_text3 = new QGraphicsTextItem("0.00");
            m_text3->setDefaultTextColor(QColor("White"));
            m_text3->setTransform(matrix,false);
            l_scene->addItem(m_text3);
        } else {
            m_text3 = NULL;
        }

    }

    // Last: we just loaded the dial file which is by default positioned on a "zero" value
    // of the needles, so we have to reset the needle values too upon dial file loading, otherwise
    // we would end up with an offset whenever we change a dial file and the needle value
    // is not zero at that time.
    needle1Value = 0;
    needle2Value = 0;
    needle3Value = 0;
    if (!dialTimer.isActive())
        dialTimer.start();
    dialError = false;
   }
   else
   {
       qDebug()<<"no file: display default background.";
       m_renderer->load(QString(":/dial/images/empty.svg"));
       l_scene->clear(); // This also deletes all items contained in the scene.
       m_background = new QGraphicsSvgItem();
       m_background->setSharedRenderer(m_renderer);
       l_scene->addItem(m_background);
       m_text1 = NULL;
       m_text2 = NULL;
       m_text3 = NULL;
       m_needle1 = NULL;
       m_needle2 = NULL;
       m_needle3 = NULL;
       dialError = true;
   }
}

void DialGadgetWidget::paint()
{
    update();
}

void DialGadgetWidget::paintEvent(QPaintEvent *event)
{
    // Skip painting until the dial file is loaded
    if (! m_renderer->isValid()) {
        qDebug()<<"Dial file not loaded, not rendering";
        return;
    }
   QGraphicsView::paintEvent(event);
}

// This event enables the dial to be dynamically resized
// whenever the gadget is resized, taking advantage of the vector
// nature of SVG dials.
void DialGadgetWidget::resizeEvent(QResizeEvent *event)
{
    fitInView(m_background, Qt::KeepAspectRatio );
}

void DialGadgetWidget::setDialFont(QString fontProps)
{
    QFont font = QFont("Arial",12);
    font.fromString(fontProps);
    if (m_text1) {
        m_text1->setFont(font);
    }
}


// Converts the value into an angle:
// this enables smooth rotation in rotateNeedles below
void DialGadgetWidget::setNeedle1(double value) {
    if (rotateN1) {
        needle1Target = 360*value*n1Factor/(n1MaxValue-n1MinValue);
    }
    if (horizN1) {
        needle1Target = value*n1Factor/(n1MaxValue-n1MinValue);
    }
    if (vertN1) {
        needle1Target = value*n1Factor/(n1MaxValue-n1MinValue);
    }
    if (!dialTimer.isActive())
        dialTimer.start();
    if (m_text1) {
        QString s;
        s.sprintf("%.2f",value*n1Factor);
        m_text1->setPlainText(s);
    }
}

void DialGadgetWidget::setNeedle2(double value) {
    if (rotateN2) {
        needle2Target = 360*value*n2Factor/(n2MaxValue-n2MinValue);
    }
    if (horizN2) {
        needle2Target = value*n2Factor/(n2MaxValue-n2MinValue);
    }
    if (vertN2) {
        needle2Target = value*n2Factor/(n2MaxValue-n2MinValue);
    }
    if (!dialTimer.isActive())
        dialTimer.start();
    if (m_text2) {
        QString s;
        s.sprintf("%.2f",value*n1Factor);
        m_text2->setPlainText(s);
    }

}

void DialGadgetWidget::setNeedle3(double value) {
    if (rotateN3) {
        needle3Target = 360*value*n3Factor/(n3MaxValue-n3MinValue);
    }
    if (horizN3) {
        needle3Target = value*n3Factor/(n3MaxValue-n3MinValue);
    }
    if (vertN3) {
        needle3Target = value*n3Factor/(n3MaxValue-n3MinValue);
    }
    if (!dialTimer.isActive())
        dialTimer.start();
    if (m_text3) {
        QString s;
        s.sprintf("%.2f",value*n1Factor);
        m_text3->setPlainText(s);
    }
}

// Take an input value and rotate the dial accordingly
// Rotation is smooth, starts fast and slows down when
// approaching the target.
// We aim for a 0.5 degree precision.
//
// Note: this code is valid even if needle1 and needle2 point
// to the same element.
void DialGadgetWidget::rotateNeedles()
{
    if (dialError) {
        // We get there in case the dial file is missing or corrupt.
        dialTimer.stop();
        return;
    }
    int dialRun = 3;
    if (n2enabled) {
        double needle2Diff;
        if (abs((needle2Value-needle2Target)*10) > 5) {
            needle2Diff =(needle2Target - needle2Value)/5;
        } else {
            needle2Diff = needle2Target - needle2Value;
            dialRun--;
        }
        if (rotateN2) {
            m_needle2->setRotation(m_needle2->rotation()+needle2Diff);
        } else {
            QPointF opd = QPointF(0,0);
           if (horizN2)  {
               opd = QPointF(needle2Diff,0);
           }
           if (vertN2) {
               opd = QPointF(0,needle2Diff);
           }
           m_needle2->setTransform(QTransform::fromTranslate(opd.x(),opd.y()), true);
           // Since we have moved the needle, we need to move
           // the transform origin point the opposite way
           // so that it keeps rotating from the same point.
           // (this is only useful if needle1 and needle2 are the
           // same object, for combined movement such as attitude indicator).
           QPointF oop = m_needle2->transformOriginPoint();
           m_needle2->setTransformOriginPoint(oop.x()-opd.x(),oop.y()-opd.y());
        }
        needle2Value += needle2Diff;
    } else {
        dialRun--;
    }

    // We assume that needle1 always exists!
    double needle1Diff;
    if ((abs((needle1Value-needle1Target)*10) > 5)) {
        needle1Diff = (needle1Target - needle1Value)/5;
    } else {
        needle1Diff = needle1Target - needle1Value;
        dialRun--;
    }
    if (rotateN1) {
       m_needle1->setRotation(m_needle1->rotation()+needle1Diff);
    } else {
        QPointF opd = QPointF(0,0);
        if (horizN1) {
           opd = QPointF(needle1Diff,0);
        }
        if (vertN1) {
           opd = QPointF(0,needle1Diff);
        }
        m_needle1->setTransform(QTransform::fromTranslate(opd.x(),opd.y()), true);
        QPointF oop = m_needle1->transformOriginPoint();
        m_needle1->setTransformOriginPoint((oop.x()-opd.x()),(oop.y()-opd.y()));
    }
    needle1Value += needle1Diff;

   if (n3enabled) {
       double needle3Diff;
       if ((abs((needle3Value-needle3Target)*10) > 5)) {
           needle3Diff = (needle3Target - needle3Value)/5;
       } else {
           needle3Diff = needle3Target - needle3Value;
           dialRun--;
       }
       if (rotateN3) {
            m_needle3->setRotation(m_needle3->rotation()+needle3Diff);
       } else {
            QPointF opd = QPointF(0,0);
            if (horizN3) {
                opd = QPointF(needle3Diff,0);
            }
       if (vertN3) {
           opd = QPointF(0,needle3Diff);
       }
       m_needle3->setTransform(QTransform::fromTranslate(opd.x(),opd.y()), true);
       QPointF oop = m_needle3->transformOriginPoint();
       m_needle3->setTransformOriginPoint((oop.x()-opd.x()),(oop.y()-opd.y()));
       }
       needle3Value += needle3Diff;
    } else {
        dialRun--;
    }

    // Now check: if dialRun is now zero, we should
    // just stop the timer since all needles have finished moving
    if (!dialRun) dialTimer.stop();
}
