/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <cmath>

#include <QAbstractTextDocumentLayout>
#include <QDebug>
#include <QFile>
#include <QGraphicsDropShadowEffect>
#include <QMouseEvent>
#include <QPainter>
#include <QTextDocument>
#include <QTextStream>

#include "MvQCursorData.h"
#include "MvQPlotView.h"
#include "MvQTheme.h"
#include "MgQPlotScene.h"
#include "MgQLayoutItem.h"
#include "MgQSceneItem.h"

#include "Transformation.h"

MvQCursorData::MvQCursorData(MgQPlotScene* scene, MvQPlotView* view, QGraphicsItem* parent) :
    MvQPlotItem(scene, view, parent)
{
    auto* effect = new QGraphicsDropShadowEffect;
    effect->setXOffset(3.);
    effect->setYOffset(3.);
    setGraphicsEffect(effect);

    textDoc_ = new QTextDocument(this);
    textDoc_->setDefaultStyleSheet(MvQTheme::htmlTableCss("cursor_data"));
}

void MvQCursorData::setActivated(bool flag)
{
    MvQPlotItem::setActivated(flag);

    if (flag) {
        show();
    }
    else {
        hide();
    }
}

void MvQCursorData::prepareForReset()
{
    dataLayout_ = nullptr;
    sceneItem_ = nullptr;
}

void MvQCursorData::reset()
{
    setData(cursorPos_);
    update();
}

QRectF MvQCursorData::boundingRect() const
{
    return boundingRect_;
}

void MvQCursorData::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    painter->setPen(QPen());
    painter->setBrush(Qt::white);
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->drawRoundedRect(boundingRect_.adjusted(1, 1, -1, -1), 5., 5.);

    // Render text
    painter->translate(0, boundingRect_.height());
    painter->scale(1., -1.);
    textDoc_->drawContents(painter);
}

void MvQCursorData::setPositionMode(MvQCursorData::PositionMode mode)
{
    positionMode_ = mode;

    if (positionMode_ == Anchored) {
        setPos(offsetFromCursor_);
    }
}

void MvQCursorData::changePositionMode(QPointF& cursor)
{
    if (positionMode_ == Anchored) {
        positionMode_ = FollowCursor;
    }
    else {
        positionMode_ = Anchored;
    }

    if (positionMode_ == FollowCursor) {
        offsetFromCursor_ = pos() - cursor;
    }
}

void MvQCursorData::changeProbeMode(QPointF& /*cursor*/)
{
    if (probeMode_ == AnchoredProbe) {
        probeMode_ = FollowCursorProbe;
    }
    else {
        probeMode_ = AnchoredProbe;
    }

    emit probeEnabled(probeMode_ == AnchoredProbe);
}

void MvQCursorData::setCursorPos(QPointF& cursor)
{
    cursorPos_ = cursor;

    if (positionMode_ == FollowCursor) {
        // setPos(cursor+offsetFromCursor_-boundingRect_.topLeft());
        setPos(cursor + offsetFromCursor_);
    }
    else if (probeMode_ == FollowCursorProbe) {
        update();
    }
    // update();
}


void MvQCursorData::setText(QPointF coord, QList<ValuesCollector> layerData, QString nameX, QString nameY, bool hasDistance)
{
    QString s;

    s += "<table>";
    s += "<tr><td class=\"first\">" + nameX + "</td><td>";
    s += QString::number(coord.x(), 'f', 3);
    s += "</td><td class=\"first\">" + nameY + "</td><td>";
    s += QString::number(coord.y(), 'f', 3);
    s += "</td></tr>";
    s += "</table>";

    bool collectedLayerExists = false;
    bool hasValue = false;
    bool hasScaledValue = false;
    bool hasVector = false;
    for (auto d : layerData) {
        if (d.collected()) {
            collectedLayerExists = true;
            if (d.hasValue())
                hasValue = true;
            if (d.scaled())
                hasScaledValue = true;

            if (!hasVector && d[0].size() > 0) {
                // Get values
                currentValues_.clear();
                d[0].at(0)->visit(*this);

                if (currentValues_.size() == 2) {
                    hasVector = true;
                }
            }
        }
    }

    if (!collectedLayerExists) {
        setText(s);
        return;
    }

    //----------------------------------
    // If any of the layers have data
    //----------------------------------

    s += "<br>";
    s += "<table>";

    // Create table header
    s += "<tr><td class=\"first\">Layer</td>";
    if (hasValue) {
        int colSpan = 1;
        QString valueLabel("Value");
        if (hasVector) {
            colSpan = 2;
            valueLabel = "Value(s)";
        }

        if (hasScaledValue) {
            s += "<td class=\"first\"> Scaled Value</td>";
        }

        s += "<td class=\"first\" colspan=\"" + QString::number(colSpan) + "\">" + valueLabel + "</td>";
    }

    s += "<td class=\"first\">" + nameX + "</td><td class=\"first\">" + nameY + "</td>";
    if (hasDistance) {
        s += "<td class=\"first\">Dist (km)</td>";
    }
    s += "</tr>";


    // Loop for each layer
    for (auto d : layerData) {
        if (d.collected() == false)
            continue;

        s += "<tr>";

        // Cell: layer name
        QStringList lst = QString::fromStdString(d.name()).split("/");
        if (!lst.isEmpty()) {
            QString layerName = lst.last();
            layerName.truncate(8);
            s += "<td>" + layerName + "</td>";
        }
        else {
            s += "<td></td>";
        }

        // We only check the first point in the collector (at present there
        // must be only one point anyway!!)
        if (d[0].size() == 0) {
            s += "<td>-</td><td>-</td><td>-</td>";
            if (hasValue) {
                s += "<td>-</td>";
                if (hasVector)
                    s += "<td>-</td>";
                if (hasScaledValue) {
                    s += "<td>-</td>";
                    if (hasVector)
                        s += "<td>-</td>";
                }
            }
        }
        else {
            ValuesCollectorData* data = d[0].at(0);

            // Get values
            if (d.hasValue()) {
                currentValues_.clear();
                d[0].at(0)->visit(*this);

                //----------------------------------------------------
                // Cell: Scalar value (can be scaled or/and unscaled)
                //----------------------------------------------------

                if (currentValues_.size() == 1) {
                    double value;
                    QString valueStr, units;
                    QString scaledTxt, unscaledTxt;

                    if (d.scaled()) {
                        value = data->scaledValue();
                        units = unitsString(QString::fromStdString(d.scaledUnits()));

                        if (data->missing()) {
                            valueStr = tr("Missing");
                            scaledTxt = valueStr;
                        }
                        else {
                            valueStr = formatNumber(value);
                            scaledTxt = valueStr + units;
                        }
                    }

                    value = data->value();
                    units = unitsString(QString::fromStdString(d.units()));
                    if (data->missing()) {
                        valueStr = tr("Missing");
                        unscaledTxt = valueStr;
                    }
                    else {
                        valueStr = formatNumber(value);
                        unscaledTxt = valueStr + units;
                    }

                    if (hasScaledValue) {
                        if (d.scaled()) {
                            s += "<td class=\"highlight\">" + scaledTxt + "</td>";
                            // if(hasVector) s+="<td></td>";
                            s += "<td>" + unscaledTxt + "</td>";
                            if (hasVector)
                                s += "<td></td>";
                        }
                        else {
                            s += "<td></td>";
                            // if(hasVector) s+="<td></td>";
                            s += "<td class=\"highlight\">" + unscaledTxt + "</td>";
                            if (hasVector)
                                s += "<td></td>";
                        }
                    }
                    else {
                        // s+="<td></td>";
                        // if(hasVector) s+="<td></td>";
                        s += "<td class=\"highlight\">" + unscaledTxt + "</td>";
                        if (hasVector)
                            s += "<td></td>";
                    }
                }

                //----------------------------------------------------
                // Cell: vector value (no scaling at the moment)
                //----------------------------------------------------

                if (currentValues_.size() == 2) {
                    double value;
                    QString valueStr;
                    QString value1Txt, value2Txt;
                    QStringList units;

                    value = currentValues_[0];
                    units = QString::fromStdString(d.units()).split("/");
                    for (int i = 0; i < units.count(); i++) {
                        units[i] = unitsString(units[i]);
                    }
                    for (int i = units.count(); i < 2; i++) {
                        units << "";
                    }

                    if (data->missing()) {
                        valueStr = tr("Missing");
                        value1Txt = valueStr;
                    }
                    else {
                        valueStr = formatNumber(value);
                        value1Txt = valueStr + units[0];
                    }


                    value = currentValues_[1];
                    if (data->missing()) {
                        valueStr = tr("Missing");
                        value2Txt = valueStr;
                    }
                    else {
                        valueStr = formatNumber(value);

                        if (units[1].contains("(deg)")) {
                            valueStr = QString::number(value, 'f', 2);
                        }

                        value2Txt = valueStr + units[1];
                    }


                    if (hasScaledValue) {
                        // s+="<td></td><td></td>";
                        s += "<td></td>";
                        s += "<td class=\"highlight\">" + value1Txt + "</td>";
                        s += "<td class=\"highlight\">" + value2Txt + "</td>";
                    }
                    else {
                        s += "<td class=\"highlight\">" + value1Txt + "</td>";
                        s += "<td class=\"highlight\">" + value2Txt + "</td>";
                    }
                }
            }

            // Cell: pos X
            s += "<td>" + QString::number(data->x(), 'f', 2) + "</td>";

            // Cell: pos Y
            s += "<td>" + QString::number(data->y(), 'f', 2) + "</td>";

            // Cell: distance
            if (hasDistance) {
                s += "<td>" + QString::number(data->distance(), 'f', 2) + "</td>";
            }
        }

        s += "</tr>";
    }

    s += "</table>";

    setText(s);
}

void MvQCursorData::setText()
{
    QString s(QObject::tr("Cursor outside plot area!"));
    setText(s);
}

void MvQCursorData::setText(QString str)
{
    text_ = str;
    textDoc_->setHtml(text_);

    QSizeF docSize = textDoc_->documentLayout()->documentSize();
    QRectF rect(0, 0, docSize.width(), docSize.height());

    if (boundingRect_ != rect) {
        prepareGeometryChange();
    }

    boundingRect_ = rect;
}


void MvQCursorData::setData(QPointF pos)
{
    // Scene position
    // QPointF pos = plotView_->mapToScene(event->pos());

    MgQLayoutItem* dataLayout = nullptr;

    // Now if dataLayout_ is not null it should contain the point
    if (plotScene_->identifyPos(pos, &sceneItem_, &dataLayout)) {
        QPointF posM = pos;
        QPointF coord;
        if (pointInMagnifier_) {
            posM = magnifierScenePos_ + (pos - magnifierScenePos_) / magnifierZoomFactor_;
        }

        double searchRadiusX = 2.;
        double searchRadiusY = 2.;

        float plotScale = plotScene_->plotScale();
        double cx = dataLayout->coordRatioX();
        double cy = dataLayout->coordRatioY();
        if (cx != 0.) {
            searchRadiusX = 20. / fabs(cx * plotScale);
        }
        if (cy != 0.) {
            searchRadiusY = 20. / fabs(cy * plotScale);
        }

        // qDebug() << "searchRadius:" <<  cx << cy << plotScale << searchRadiusX << searchRadiusY;

        dataLayout->mapFromSceneToGeoCoords(posM, coord);
        // cursorData_->setCoordinates(coord);

        /*QList<QPointF> posL;
            QPointF projCoord;
            dataLayout->mapFromSceneToProjectionCoords(pos,projCoord);
            posL << projCoord;*/

        QList<QPointF> posL;
        posL << coord;

        QList<ValuesCollector> layerData;

        /*QList<QStringList> txtL;
            foreach(QPointF pp,posL)
            {
                txtL << QStringList();
            }

            sceneItem_->collectLayerDataForCurrentStep(posL,txtL);

            setText(coord,txtL[0]);


            emit positionChanged(txtL[0]);*/

        sceneItem_->collectLayerDataForCurrentStep(posL, layerData,
                                                   searchRadiusX, searchRadiusY);

        const Transformation& transformation = dataLayout->layout().transformation();
        QString nameX, nameY;
        bool hasDistance = false;
        switch (transformation.coordinateType()) {
            case Transformation::GeoType:
                nameX = tr("Lon");
                nameY = tr("Lat");
                hasDistance = true;
                break;
            case Transformation::XyType:
            default:
                nameX = tr("X");
                nameY = tr("Y");
                break;
        }

        setText(coord, layerData, nameX, nameY, hasDistance);

        // emit positionChanged(coord,layerData);
    }
    else {
        setText();
    }
}


void MvQCursorData::mousePressEventFromView(QMouseEvent* event)
{
    if (activated_ && acceptMouseEvents_) {
        if (event->buttons() & Qt::LeftButton) {
            // Get scene position
            QPointF pos = plotView_->mapToScene(event->pos());
            changePositionMode(pos);
        }
        /*if(event->buttons() & Qt::MidButton)
        {
                //Get scene position
            QPointF pos=plotView_->mapToScene(event->pos());
            changeProbeMode(pos);
        }*/
    }
}

void MvQCursorData::mouseMoveEventFromView(QMouseEvent* event)
{
    if (!activated_ || !acceptMouseEvents_)
        return;

    // Scene position
    QPointF pos = plotView_->mapToScene(event->pos());

    if (probeMode_ == FollowCursorProbe) {
        setData(pos);
        setCursorPos(pos);
    }
    else {
        setCursorPos(pos);
    }
}

void MvQCursorData::mouseReleaseEventFromView(QMouseEvent* event)
{
    if (activated_ && acceptMouseEvents_) {
        if (zoomWasPerformedAfterMouseRelease_ == false) {
            QPointF pos = plotView_->mapToScene(event->pos());
            changePositionMode(pos);
        }
    }
}


QString MvQCursorData::unitsString(QString val)
{
    QString res;

    if (val == "m s**-1")
        res = "m/s";
    else if (val == "deg c")
        res = "&#176;C";
    else if (val == "degrees")
        res = "deg";
    else
        res = val;

    if (!res.isEmpty())
        res = " (" + res + ")";

    return res;
}


QString MvQCursorData::formatNumber(double value)
{
    QString valueStr;
    double absValue = abs(value);

    if (trunc(value) == value)                      // is the number an integer?
        valueStr = QString::number(value, 'f', 0);  // - cut off the decimal places
    else if (absValue < 0.000001 ||                 // is the number very small?
             absValue > 1000000000)                 // or very large?
        valueStr = QString::number(value, 'e');     // - use scientific notation
    else                                            // otherwise
        valueStr = QString::number(value, 'f');     // - use 6 decimal places (default)

    return valueStr;
}


void MvQCursorData::setMagnifierInfo(bool pointInMag, QPointF magPos, float magZoomFactor)
{
    pointInMagnifier_ = pointInMag;
    magnifierScenePos_ = magPos;
    magnifierZoomFactor_ = magZoomFactor;
}

void MvQCursorData::visit(const ValuesCollectorData& data)
{
    currentValues_.push_back(data.value());
}

void MvQCursorData::visit(const ValuesCollectorUVData& data)
{
    currentValues_.push_back(data.xComponent());
    currentValues_.push_back(data.yComponent());
}

void MvQCursorData::visit(const ValuesCollectorSDData& data)
{
    currentValues_.push_back(data.speed());
    currentValues_.push_back(data.direction());
}
