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

 Copyright 2017 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 "LocationView.h"

#include <QDebug>
#include <QGuiApplication>
#include <QGraphicsSvgItem>
#include <QMouseEvent>
#include <QSvgRenderer>
#include <QTimer>
#include <QWheelEvent>
#include <QGraphicsRectItem>

#include "MvMiscelaneous.h"

#include <cmath>

static bool initialSizeAdjsuted = false;

MapMarkerItem::Symbol MapMarkerItem::symbol_ = MapMarkerItem::CircleSymbol;
float MapMarkerItem::symbolSize_             = 10.;
QRectF MapMarkerItem::bRect_                 = QRectF(-4, -4, 8, 8);
QBrush MapMarkerItem::brush_(QColor(255, 57, 57));
QBrush MapMarkerItem::selectBrush_(QColor(0, 255, 0));
QPen MapMarkerItem::pen_(QPen(QColor(40, 40, 40)));
QPen MapMarkerItem::crossPen_(QColor(234, 78, 68));
QPen MapMarkerItem::selectPen_(QPen(QColor(0, 0, 0)));
QPen MapMarkerItem::crossSelectPen_(QColor(0, 255, 0));

//#define _MV_MAPVIEW_DEBUG


//============================================
//
// MapLayerItem
//
//============================================

MapLayerItem::MapLayerItem()
{
    std::string svgFile = metview::appDefDirFile("world_map_low.svg");
    svg_                = new QSvgRenderer(QString::fromStdString(svgFile));
    bRect_              = QRectF(-180, -90., 360., 180);
}

QRectF MapLayerItem::boundingRect() const
{
    return bRect_;
}

void MapLayerItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    svg_->render(painter, bRect_);
}

//============================================
//
// MapMarkerItem
//
//============================================

MapMarkerItem::MapMarkerItem(int id) :
    id_(id),
    current_(false)
{
    setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
    setFlag(QGraphicsItem::ItemIsSelectable);
}

QRectF MapMarkerItem::boundingRect() const
{
    return bRect_;
}

void MapMarkerItem::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    if (symbol_ == CircleSymbol) {
        painter->setRenderHint(QPainter::Antialiasing, true);
        painter->setPen((current_) ? selectPen_ : pen_);
        painter->setBrush((current_) ? selectBrush_ : brush_);
        painter->drawEllipse(bRect_);
    }
    else if (symbol_ == CrossSymbol) {
        float h = bRect_.width() / 2.;
        painter->setPen((current_) ? crossSelectPen_ : crossPen_);
        painter->drawLine(QPoint(-h, 0), QPoint(h, 0));
        painter->drawLine(QPoint(0, -h), QPoint(0, h));
    }
}

void MapMarkerItem::setSymbol(Symbol sym, float size)
{
    symbol_     = sym;
    symbolSize_ = size;

    MapMarkerItem::bRect_ = QRectF(-size / 2., -size / 2., size, size);
}

void MapMarkerItem::setCurrent(bool b)
{
    current_ = b;
    setZValue((current_) ? 1 : 0);
    update();
}

//============================================
//
// LocationView
//
//============================================

LocationView::LocationView(QWidget* parent) :
    QGraphicsView(parent),
    minZoomLevel_(0),
    maxZoomLevel_(28),
    zoomLevel_(0),
    zoomDelta_(0.18),
    selectedItem_(0),
    inDrag_(false)
{
    setProperty("mvStyle", "LocationView");

    //No scrollbars
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    scene_ = new QGraphicsScene(this);
    scene_->setSceneRect(QRect(-180, -90, 360, 180));
    setScene(scene_);

    //The background map
    svg_ = new MapLayerItem();
    scene_->addItem(svg_);

    //marker_=new MarkerLayerItem(this);
    //scene_->addItem(marker_);

    //Center the view on the map
    centerOn(svg_);

#ifdef _MV_MAPVIEW_DEBUG
    QList<float> testLat, testLon;
    testLon << -180 << 0 << 0;
    testLat << 50 << 50 << 0;

    for (int i = 0; i < testLat.count(); i++) {
        MapMarkerItem* rt = new MapMarkerItem(-1);
        scene_->addItem(rt);
        rt->setPos(testLon[i], -testLat[i]);
    }
#endif
}

void LocationView::keyPressEvent(QKeyEvent* event)
{
    int key = event->key();

    if (key == Qt::Key_Plus)
        zoomIn();
    else if (key == Qt::Key_Minus)
        zoomOut();
    else
        QGraphicsView::keyPressEvent(event);
}

void LocationView::wheelEvent(QWheelEvent* event)
{
    QPoint delta = event->angleDelta();
    delta.y() > 0 ? zoomIn() : zoomOut();
}

void LocationView::mousePressEvent(QMouseEvent* event)
{
    QGraphicsItem* item = itemAt(event->pos());

    //We clciked on an item
    if (item && item->type() == MapMarkerItem::Type) {
        if (selectedItem_) {
            Q_ASSERT(selectedItem_->type() == MapMarkerItem::Type);
            selectedItem_->setCurrent(false);
        }
        selectedItem_ = static_cast<MapMarkerItem*>(item);
        Q_ASSERT(selectedItem_);
        selectedItem_->setCurrent(true);

        Q_EMIT currentChanged(selectedItem_->id());
    }

    //We start a pan
    else if (event->button() == Qt::LeftButton) {
        inDrag_  = true;
        dragPos_ = event->pos();
        QGuiApplication::setOverrideCursor(QCursor(Qt::ClosedHandCursor));
    }

    QGraphicsView::mousePressEvent(event);
}

void LocationView::mouseMoveEvent(QMouseEvent* event)
{
    if (inDrag_) {
        //It is more accurate than using the delta in view coordinates (pixels)
        QPointF delta = mapToScene(event->pos()) - mapToScene(dragPos_);
        pan(delta);
    }

    QGraphicsView::mouseMoveEvent(event);
    dragPos_ = event->pos();
}

void LocationView::mouseReleaseEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton) {
        inDrag_ = false;
        QGuiApplication::restoreOverrideCursor();
    }

    QGraphicsView::mouseReleaseEvent(event);
}

void LocationView::zoomIn()
{
    zoom(zoomLevel_ + 1);
}

void LocationView::zoomOut()
{
    zoom(zoomLevel_ - 1);
}

void LocationView::zoom(int level)
{
    setZoomLevel(level);
    Q_EMIT zoomLevelChanged(zoomLevel());
}

void LocationView::setZoomLevel(int level)
{
    if (level >= minZoomLevel_ && level <= maxZoomLevel_) {
        float actScale = scaleFromLevel(zoomLevel_);
        zoomLevel_     = level;
        float newScale = scaleFromLevel(level);
        float factor   = newScale / actScale;
        scale(factor, factor);
    }
}

int LocationView::zoomLevelFromScale(float sc) const
{
    int level = static_cast<int>(round(log(sc) / log(1 + zoomDelta_)));
    if (level < minZoomLevel_)
        level = minZoomLevel_;
    if (level > maxZoomLevel_)
        level = maxZoomLevel_;
    return level;
}

float LocationView::currentScale() const
{
    return pow(1. + zoomDelta_, zoomLevel_);
}

float LocationView::scaleFromLevel(int level) const
{
    return pow(1. + zoomDelta_, level);
}

void LocationView::pan(QPointF delta)
{
#ifdef _MV_MAPVIEW_DEBUG
    qDebug() << "delta" << delta;
#endif

    //scale the pan delta
    delta *= currentScale();

    QSize size = maximumViewportSize();
    QPoint oldCenter(size.width() / 2, size.height() / 2);
    QPoint newCenter(size.width() / 2 - delta.x(), size.height() / 2 - delta.y());

#ifdef _MV_MAPVIEW_DEBUG
    QPoint oldCenter(size.width() / 2, size.height() / 2);
    qDebug() << "center in view old=" << oldCenter << "new=" << newCenter;
    qDebug() << "BEFORE center in scene old=" << mapToScene(oldCenter) << "new=" << mapToScene(newCenter);
#endif

    //This method does not work when the scollbars are visible
    centerOn(mapToScene(newCenter));


#ifdef _MV_MAPVIEW_DEBUG
    qDebug() << "AFTER centre in scene=" << mapToScene(oldCenter);
#endif
}

void LocationView::showFullMap()
{
    QRectF sr  = scene_->sceneRect();
    QSize size = maximumViewportSize();

    if (sr.width() > 0 && sr.height() > 0) {
        float rw = size.width() / sr.width();
        float rh = size.height() / sr.height();

        int level = zoomLevelFromScale((qMin(rw, rh) - 0.1));
        setZoomLevel(level);
        centerOn(svg_);
        Q_EMIT zoomLevelChanged(zoomLevel());
    }
}

void LocationView::zoomToData()
{
    QRectF sr  = scene_->sceneRect();
    QSize size = maximumViewportSize();

    if (sr.width() > 0 && sr.height() > 0 && dataRect_.isValid()) {
        float halo = 2;
        QRectF dr  = dataRect_.adjusted(-halo, -halo, halo, halo);
        if (dr.left() < -180.)
            dr.setLeft(-180.);
        if (dr.right() > 180.)
            dr.setRight(180.);
        if (dr.top() < -90.)
            dr.setTop(-90.);
        if (dr.bottom() > 90.)
            dr.setBottom(90.);

        float dataW = dr.width();
        float dataH = dr.height();

        float rw = size.width() / dataW;
        float rh = size.height() / dataH;

        int level = zoomLevelFromScale(qMin(rw, rh) - 0.1);
        setZoomLevel(level);

        //This method does not work when the scollbars are visible
        centerOn(dr.center());

        Q_EMIT zoomLevelChanged(zoomLevel());
    }
}

void LocationView::clearPoints()
{
    dataRect_       = QRectF();
    std::size_t num = markers_.size();
    for (std::size_t i = 0; i < num; i++) {
        delete markers_[i];
    }
    markers_      = std::vector<MapMarkerItem*>();
    selectedItem_ = 0;
}

void LocationView::addPoints(const std::vector<double>& lat, const std::vector<double>& lon)
{
    //marker_->addPoints(lat,lon);
    if (!initialSizeAdjsuted) {
        initialSizeAdjsuted = true;
        showFullMap();
    }

    clearPoints();
    std::size_t num         = lat.size();
    MapMarkerItem* nullItem = 0;
    markers_.resize(num, nullItem);

    float latMin = 1000;
    float latMax = -1000;
    float lonMin = 1000;
    float lonMax = -1000;
    double latVal, lonVal;
    bool hasValidPoint = false;

    adjustMarkers(num);

    for (std::size_t i = 0; i < num; i++) {
        latVal = -lat[i];
        lonVal = lon[i];

        markers_[i] = 0;

        if (lonVal <= 180. && lonVal >= -180. &&
            latVal >= -90. && latVal <= 90.) {
            hasValidPoint       = true;
            MapMarkerItem* item = new MapMarkerItem(i);
            markers_[i]         = item;
            item->setPos(QPointF(lonVal, latVal));
            scene_->addItem(item);

            if (lonVal < lonMin)
                lonMin = lonVal;
            if (lonVal > lonMax)
                lonMax = lonVal;
            if (latVal > latMax)
                latMax = latVal;
            if (latVal < latMin)
                latMin = latVal;
        }
    }

    if (hasValidPoint) {
        dataRect_ = QRectF(lonMin, latMin, lonMax - lonMin + 0.01, latMax - latMin + 0.01);
    }
}

void LocationView::setCurrentPoint(int index)
{
    Q_ASSERT(index >= 0);
    Q_ASSERT(index <= static_cast<int>(markers_.size()));

    if (!markers_[index])
        return;

    if (selectedItem_) {
        if (markers_[index] != selectedItem_) {
            Q_ASSERT(selectedItem_->type() == MapMarkerItem::Type);
            selectedItem_->setCurrent(false);
        }
    }

    selectedItem_ = markers_[index];
    Q_ASSERT(selectedItem_);
    selectedItem_->setCurrent(true);

    ensureVisible(selectedItem_, 50, 50);
}

void LocationView::adjustMarkers(int num)
{
    if (num > 100000)
        MapMarkerItem::setSymbol(MapMarkerItem::CrossSymbol, 4);
    else if (num > 50000)
        MapMarkerItem::setSymbol(MapMarkerItem::CircleSymbol, 6);
    else if (num > 10000)
        MapMarkerItem::setSymbol(MapMarkerItem::CircleSymbol, 8);
    else
        MapMarkerItem::setSymbol(MapMarkerItem::CircleSymbol, 10);
}
