/* gGraph Implemntation
 *
 * Copyright (c) 2019-2022 The OSCAR Team
 * Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License. See the file COPYING in the main directory of the source code
 * for more details. */

#define TEST_MACROS_ENABLEDoff
#include "test_macros.h"

#include "Graphs/gGraph.h"

#include <QLabel>
#include <QTimer>
#include <cmath>
#include <exception>

#include "mainwindow.h"
#include "Graphs/gGraphView.h"
#include "Graphs/layer.h"
#include "SleepLib/profiles.h"

extern MainWindow *mainwin;

// Graph globals.
QFont *defaultfont = nullptr;
QFont *mediumfont = nullptr;
QFont *bigfont = nullptr;
QHash<QString, QImage *> images;

static bool globalsInitialized = false;

// Graph constants.
static const double zoom_hard_limit = 500.0;


// Calculate Catmull-Rom Spline of given 4 samples, with t between 0-1;
float CatmullRomSpline(float p0, float p1, float p2, float p3, float t)
{
    float t2 = t*t;
    float t3 = t2 * t;

    return (float)0.5 * ((2 * p1) +
    (-p0 + p2) * t +
    (2*p0 - 5*p1 + 4*p2 - p3) * t2 +
    (-p0 + 3*p1- 3*p2 + p3) * t3);
}

 // Must be called from a thread inside the application.
bool InitGraphGlobals()
{
    if (globalsInitialized) {
        return true;
    }

    if (!p_pref->contains("Fonts_Graph_Name")) {
        (*p_pref)["Fonts_Graph_Name"] = "Sans Serif";
        (*p_pref)["Fonts_Graph_Size"] = 10;
        (*p_pref)["Fonts_Graph_Bold"] = false;
        (*p_pref)["Fonts_Graph_Italic"] = false;
    }

    if (!p_pref->contains("Fonts_Title_Name")) {
        (*p_pref)["Fonts_Title_Name"] = "Sans Serif";
        (*p_pref)["Fonts_Title_Size"] = 12;
        (*p_pref)["Fonts_Title_Bold"] = true;
        (*p_pref)["Fonts_Title_Italic"] = false;
    }

    if (!p_pref->contains("Fonts_Big_Name")) {
        (*p_pref)["Fonts_Big_Name"] = "Serif";
        (*p_pref)["Fonts_Big_Size"] = 35;
        (*p_pref)["Fonts_Big_Bold"] = false;
        (*p_pref)["Fonts_Big_Italic"] = false;
    }

    defaultfont = new QFont((*p_pref)["Fonts_Graph_Name"].toString(),
                            (*p_pref)["Fonts_Graph_Size"].toInt(),
                            (*p_pref)["Fonts_Graph_Bold"].toBool() ? QFont::Bold : QFont::Normal,
                            (*p_pref)["Fonts_Graph_Italic"].toBool()
                           );
    mediumfont = new QFont((*p_pref)["Fonts_Title_Name"].toString(),
                           (*p_pref)["Fonts_Title_Size"].toInt(),
                           (*p_pref)["Fonts_Title_Bold"].toBool() ? QFont::Bold : QFont::Normal,
                           (*p_pref)["Fonts_Title_Italic"].toBool()
                          );
    bigfont = new QFont((*p_pref)["Fonts_Big_Name"].toString(),
                        (*p_pref)["Fonts_Big_Size"].toInt(),
                        (*p_pref)["Fonts_Big_Bold"].toBool() ? QFont::Bold : QFont::Normal,
                        (*p_pref)["Fonts_Big_Italic"].toBool()
                       );

    defaultfont->setStyleHint(QFont::AnyStyle, QFont::OpenGLCompatible);
    mediumfont->setStyleHint(QFont::AnyStyle, QFont::OpenGLCompatible);
    bigfont->setStyleHint(QFont::AnyStyle, QFont::OpenGLCompatible);

    //images["mask"] = new QImage(":/icons/mask.png");
    images["oximeter"] = new QImage(":/icons/cubeoximeter.png");
    images["smiley"] = new QImage(":/icons/smileyface.png");
    //images["sad"] = new QImage(":/icons/sadface.png");

    images["logo"] = new QImage(":/icons/logo-lm.png");
    images["brick"] = new QImage(":/icons/brick.png");
    images["nographs"] = new QImage(":/icons/nographs.png");
    images["nodata"] = new QImage(":/icons/nodata.png");

    globalsInitialized = true;
    return true;
}

void DestroyGraphGlobals()
{
    if (!globalsInitialized) {
        return;
    }

    delete defaultfont;
    delete bigfont;
    delete mediumfont;

    for (auto & image : images) {
        delete image;
    }

    globalsInitialized = false;
}

gGraph::gGraph(QString name, gGraphView *graphview, QString title, QString units, int height, short group)
    : m_name(name),
      m_graphview(graphview),
      m_title(title),
      m_units(units),
      m_visible(true)
{
    // DEBUGF Q(name) Q(title) QQ("UNITS",units) Q(height) Q(group);
    if (height == 0) {
        height = AppSetting->graphHeight();
        Q_UNUSED(height)
    }

    if (graphview && graphview->contains(name)) {
        qDebug() << "Trying to duplicate " << name << " when a graph with the same name already exists";
        name+="-1";
    }
    m_min_height = 60;
    m_width = 0;

    m_layers.clear();

    m_snapshot = false;
    f_miny = f_maxy = 0;
    rmin_x = rmin_y = 0;
    rmax_x = rmax_y = 0;
    max_x = max_y = 0;
    min_x = min_y = 0;
    rec_miny = rec_maxy = 0;
    rphysmax_y = rphysmin_y = 0;
    m_zoomY = ZS_AUTO_FIT;
    m_selectedDuration = 0;

    if (graphview) {
        graphview->addGraph(this, group);
        timer = new QTimer(graphview);
        connect(timer, SIGNAL(timeout()), SLOT(Timeout()));
    } else {
        timer = new QTimer();
        connect(timer, SIGNAL(timeout()), SLOT(Timeout()));
        // know what I'm doing now.. ;)
     //   qWarning() << "gGraph created without a gGraphView container.. Naughty programmer!! Bad!!!";
    }

    m_margintop = 14;
    m_marginbottom = 5;
    m_marginleft = 0;
    m_marginright = 15;
    m_selecting_area = m_blockzoom = false;
    m_pinned = false;
    m_lastx23 = 0;

    invalidate_yAxisImage = true;
    invalidate_xAxisImage = true;

    m_block_select = false;

    m_enforceMinY = m_enforceMaxY = false;
    m_showTitle = true;
    m_printing = false;

    left = right = top = bottom = 0;
}
gGraph::~gGraph()
{
    for (auto & layer : m_layers) {
        if (layer->unref()) {
            delete layer;
        }
    }

    m_layers.clear();

    if (timer) {
        timer->stop();
        disconnect(timer, 0, 0, 0);
        delete timer;
    }
}
void gGraph::Trigger(int ms)
{
    if (timer->isActive()) { timer->stop(); }

    timer->setSingleShot(true);
    timer->start(ms);
}
void gGraph::Timeout()
{
    deselect();
    m_graphview->timedRedraw(0);
}

void gGraph::deselect()
{
    for (auto & layer : m_layers) {
        layer->deselect();
    }
}
bool gGraph::isSelected()
{
    bool res = false;

    for (const auto & layer : m_layers) {
        res = layer->isSelected();

        if (res) { break; }
    }

    return res;
}

bool gGraph::isEmpty()
{
    bool empty = true;

    for (const auto & layer : m_layers) {
        if (!layer->isEmpty()) {
            empty = false;
            break;
        }
    }

    return empty;
}

float gGraph::printScaleX() { return m_graphview->printScaleX(); }
float gGraph::printScaleY() { return m_graphview->printScaleY(); }

//void gGraph::drawGLBuf()
//{

//    float linesize = 1;

//    if (m_printing) { linesize = 4; } //ceil(m_graphview->printScaleY());

//    for (int i = 0; i < m_layers.size(); i++) {
//        m_layers[i]->drawGLBuf(linesize);
//    }

//}

void gGraph::setDay(Day *day)
{
    // Don't update for snapshots..
    if (m_snapshot) return;

    m_day = day;

    for (auto & layer : m_layers) {
        layer->SetDay(day);
    }

    rmin_y = rmax_y = 0;
    // This resets weight and bmi overview graphs to full date range when they are changed.
    // is it required ever?
    // ResetBounds();
}

void gGraph::setZoomY(ZoomyScaling zoomy) {
    m_zoomY = zoomy;
    dynamicScalingOn =false;
    timedRedraw(0);
}

void gGraph::mouseDoubleClickYAxis(QMouseEvent * ) {
    if ( isDynamicScalingEnabled() ) {
        dynamicScalingOn = !dynamicScalingOn;
    } else {
        dynamicScalingOn =false;
        if (m_zoomY == ZS_AUTO_FIT ) {
            m_zoomY = ZS_DEFAULT;
        } else if (m_zoomY == ZS_DEFAULT) {
            m_zoomY = ZS_AUTO_FIT ;
        }
    }
    timedRedraw(0);
}

void gGraph::renderText(QString text, int x, int y, float angle, QColor color, QFont *font, bool antialias)
{
    m_graphview->AddTextQue(text, x, y, angle, color, font, antialias);
}

void gGraph::renderText(QString text, QRectF rect, quint32 flags, float angle, QColor color, QFont *font, bool antialias)
{
    m_graphview->AddTextQue(text, rect, flags, angle, color, font, antialias);
}


void gGraph::paint(QPainter &painter, const QRegion &region)
{
    m_rect = region.boundingRect();
    int originX = m_rect.left();
    int originY = m_rect.top();
    int width = m_rect.width();
    int height = m_rect.height();

    int fw, font_height;
    GetTextExtent("Wg@", fw, font_height);

    if (m_margintop > 0) {
        m_margintop = font_height + (2*printScaleY());
    }

    //m_marginbottom=5;

    left = marginLeft()*printScaleX(), right = marginRight()*printScaleX(), top = marginTop(), bottom = marginBottom() * printScaleY();
    //int x;
    int y;

    if (m_showTitle) {
        int title_x, yh;

        painter.setFont(*mediumfont);
        QFontMetrics fm(*mediumfont);

        yh = fm.height();
        //GetTextExtent("Wy@",x,yh,mediumfont); // This gets a better consistent height. should be cached.
        y = yh;
        //x = fm.width(title());
        //GetTextExtent(title(),x,y,mediumfont);
        title_x = float(yh) ;

        QString & txt = title();
        graphView()->AddTextQue(txt, marginLeft() + title_x + 8*printScaleX(), originY + height / 2 - y / 2, 90, Qt::black, mediumfont);

        left += graphView()->titleWidth*printScaleX();
    } else { left = 0; }


    if (m_snapshot) {
        QLinearGradient linearGrad(QPointF(100, 100), QPointF(width / 2, 100));
        linearGrad.setColorAt(0, QColor(255, 150, 150,40));
        linearGrad.setColorAt(1, QColor(255,255,255,20));

        painter.fillRect(m_rect, QBrush(linearGrad));
        painter.setFont(*defaultfont);
        painter.setPen(QColor(0,0,0,255));

        QString t = name().section(";", -1);

        QString txt = QObject::tr("Snapshot %1").arg(t);
        QRectF rec = QRect(m_rect.left(),m_rect.top()+6*printScaleY(), m_rect.width(), 0);
        rec = painter.boundingRect(rec, Qt::AlignCenter, txt);

        painter.drawText(rec, Qt::AlignCenter, txt);
        m_margintop += rec.height();
        top = m_margintop;
    }


#ifdef DEBUG_LAYOUT
    QColor col = Qt::red;
    painter.setPen(col);
    painter.drawLine(0, originY, 0, originY + height);
    painter.drawLine(left, originY, left, originY + height);
#endif
    int tmp;

   // left = 0;

    for (const auto & layer : m_layers) {
        if (!layer->visible()) { continue; }

        tmp = layer->minimumHeight();// * m_graphview->printScaleY();

        if (layer->position() == LayerTop) { top += tmp; }
        if (layer->position() == LayerBottom) { bottom += tmp * printScaleY(); }
    }


    for (const auto & layer : m_layers) {
        if (!layer->visible()) { continue; }

        tmp = layer->minimumWidth();
        tmp *= m_graphview->printScaleX();
        tmp *= m_graphview->devicePixelRatio();

        if (layer->position() == LayerLeft) {
            QRect rect(originX + left, originY + top, tmp, height - top - bottom);
            layer->m_rect = rect;
          //  layer->paint(painter, *this, QRegion(rect));
            left += tmp;
#ifdef DEBUG_LAYOUT
            QColor col = Qt::red;
            painter.setPen(col);
            painter.drawLine(originX + left - 1, originY, originX + left - 1, originY + height);
#endif
        }

        if (layer->position() == LayerRight) {
            right += tmp;
            QRect rect(originX + width - right, originY + top, tmp, height - top - bottom);
            layer->m_rect = rect;
            //layer->paint(painter, *this, QRegion(rect));
#ifdef DEBUG_LAYOUT
            QColor col = Qt::red;
            painter.setPen(col);
            painter.drawLine(originX + width - right, originY, originX + width - right, originY + height);
#endif
        }
    }

    bottom = marginBottom() * printScaleY();
    top = marginTop();

    for (const auto & layer : m_layers) {
        if (!layer->visible()) { continue; }

        tmp = layer->minimumHeight();

        if (layer->position() == LayerTop) {
            QRect rect(originX + left, originY + top, width - left - right, tmp);
            layer->m_rect = rect;
            layer->paint(painter, *this, QRegion(rect));
            top += tmp;
        }

        if (layer->position() == LayerBottom) {
            bottom += tmp * printScaleY();
            QRect rect(originX + left, originY + height - bottom, width - left - right, tmp);
            layer->m_rect = rect;
            layer->paint(painter, *this, QRegion(rect));
        }
    }

    if (isPinned()) {
        // Fill the background on pinned graphs
        painter.fillRect(originX + left, originY + top, width - right, height - bottom - top, QBrush(QColor(Qt::white)));
    }

    for (const auto & layer : m_layers) {
        if (!layer->visible()) { continue; }

        if (layer->position() == LayerCenter) {
            QRect rect(originX + left, originY + top, width - left - right, height - top - bottom);
            layer->m_rect = rect;
            layer->paint(painter, *this, QRegion(rect));
        }
    }

    // Draw anything like the YAxis labels afterwards, in case the graph scale was updated during draw
    for (const auto & layer : m_layers) {
        if (!layer->visible()) { continue; }
        if ((layer->position() == LayerLeft) || (layer->position() == LayerRight)) {
            layer->paint(painter, *this, QRegion(layer->m_rect));
        }
    }

    if (m_selection.width() > 0 && m_selecting_area) {
        QColor col(128, 128, 255, 128);
        painter.fillRect(originX + m_selection.x(), originY + top, m_selection.width(), height - bottom - top,QBrush(col));
//        quads()->add(originX + m_selection.x(), originY + top,
//                     originX + m_selection.x() + m_selection.width(), originY + top, col.rgba());
//        quads()->add(originX + m_selection.x() + m_selection.width(), originY + height - bottom,
//                     originX + m_selection.x(), originY + height - bottom, col.rgba());
    }

    if (isPinned() && !printing()) {
        painter.drawPixmap(-5, originY-10, m_graphview->pin_icon);
    }

}

QPixmap gGraph::renderPixmap(int w, int h, bool printing)
{

    QFont *_defaultfont = defaultfont;
    QFont *_mediumfont = mediumfont;
    QFont *_bigfont = bigfont;

    QFont fa = *defaultfont;
    QFont fb = *mediumfont;
    QFont fc = *bigfont;


    m_printing = printing;

    if (printing) {
        fa.setPixelSize(28);
        fb.setPixelSize(32);
        fc.setPixelSize(70);
        graphView()->setPrintScaleX(2.5f);
        graphView()->setPrintScaleY(2.2f);
    } else {
        graphView()->setPrintScaleX(1.0f);
        graphView()->setPrintScaleY(1.0f);
    }

    defaultfont = &fa;
    mediumfont = &fb;
    bigfont = &fc;

    QPixmap pm(w,h);


    bool pixcaching = AppSetting->usePixmapCaching();
    graphView()->setUsePixmapCache(false);
    AppSetting->setUsePixmapCaching(false);
    QPainter painter(&pm);
    painter.fillRect(0,0,w,h,QBrush(QColor(Qt::white)));
    QRegion region(0,0,w,h);
    paint(painter, region);
    DrawTextQue(painter);
    painter.end();

    graphView()->setUsePixmapCache(pixcaching);
    AppSetting->setUsePixmapCaching(pixcaching);
    graphView()->setPrintScaleX(1);
    graphView()->setPrintScaleY(1);


    defaultfont = _defaultfont;
    mediumfont = _mediumfont;
    bigfont = _bigfont;
    m_printing = false;

    return pm;
}

// Sets a new Min & Max X dates for clipping data (refresh done by caller)
void gGraph::SetXBounds(qint64 minx, qint64 maxx)
{
    invalidate_xAxisImage = true;
    min_x = minx;
    max_x = maxx;

    //repaint();
    //m_graphview->redraw();
}

int gGraph::flipY(int y)
{
    return m_graphview->height() - y;
}

void gGraph::ResetBounds()
{
    if (m_snapshot) return;
    invalidate_xAxisImage = true;
    min_x = MinX();
    max_x = MaxX();
    min_y = MinY();
    max_y = MaxY();
}

void gGraph::ToolTip(QString text, int x, int y, ToolTipAlignment align, int timeout)
{
    if (timeout <= 0) {
        timeout = AppSetting->tooltipTimeout();
    }

    m_graphview->m_tooltip->display(text, x, y, align, timeout);
}

bool gGraph::isDynamicScalingEnabled() {
    return ((m_lineChart_layer!=nullptr) && AppSetting->allowYAxisScaling() );
}

QString gGraph::unitsTooltip() {
    if (isDynamicScalingEnabled()) {
        if(dynamicScalingOn) {
            if (zoomY() == ZS_AUTO_FIT ) {
                return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to AUTO-FIT Scaling"));
            } else if (zoomY() == ZS_DEFAULT ) {
                return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to DEFAULT Scaling"));
            } else {
                return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to OVERRIDE Scaling"));
            }
        } else {
            return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: For Dynamic Scaling"));
        }
    } else {
        if (zoomY() == ZS_AUTO_FIT ) {
            return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Select DEFAULT Scaling"));
        } else if (zoomY() == ZS_DEFAULT ) {
            return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Select AUTO-FIT Scaling"));
        }
    }
    return m_units;
}

void gGraph::dynamicScaling(EventDataType &miny, EventDataType &maxy) {
    // Have new Dynamic mode;
    miny = m_lineChart_layer->actualMinY();
    maxy = m_lineChart_layer->actualMaxY();
    EventDataType diff= (maxy-miny);
    maxy += diff*0.08;      // more space at top for event ticks.
    miny -= diff*0.04;
    if (m_saved_minY!=m_lineChart_layer->actualMinY() || m_saved_maxY!=m_lineChart_layer->actualMaxY()  ) {
        // DEBUGF O(m_name) Q(m_saved_minY) QQ("==>",m_lineChart_layer->actualMinY() ) QQ("==>",miny) Q(m_saved_maxY) QQ("==>",m_lineChart_layer->actualMaxY()  )  QQ("==>",maxy);
        m_saved_minY=m_lineChart_layer->actualMinY();
        m_saved_maxY=m_lineChart_layer->actualMaxY();
    }
}

// YAxis Autoscaling code
void gGraph::roundY(EventDataType &miny, EventDataType &maxy)
{
    if (dynamicScalingOn) {
        dynamicScaling(miny, maxy) ;
        if (maxy > miny) return;
    };
    if (zoomY() == ZS_OVERRIDE) {    // Have override mode
        // set min and max to override values.
        miny = rec_miny;
        maxy = rec_maxy;
        if (maxy > miny) return; // Not Autoscaling
    } else if (zoomY() ==ZS_DEFAULT) {  // Have Default mode
        // set min and max to physical Min / max values.
        miny = physMinY();
        maxy = physMaxY();
        if (maxy > miny) return; // Not Autoscaling
    }
    miny = MinY();
    maxy = MaxY();
    int m, t;
    bool ymin_good = false, ymax_good = false;

    // Have no minx/miny reference, have to create one
    if (maxy == miny) {
        m = ceil(maxy / 2.0);
        t = m * 2;

        if (maxy == t) {
            t += 2;
        }

        if (!ymax_good) {
            maxy = t;
        }

        m = floor(miny / 2.0);
        t = m * 2;

        if (miny == t) {
            t -= 2;
        }

        if (miny >= 0 && t < 0) {
            t = 0;
        }

        if (!ymin_good) {
            miny = t;
        }


        if (miny < 0) {
            EventDataType tmp = qMax(qAbs(miny), qAbs(maxy));
            maxy = tmp;
            miny = -tmp;
        }

        return;
    }

    if (maxy >= 400) {
        m = ceil(maxy / 50.0);
        t = m * 50;

        if (!ymax_good) {
            maxy = t;
        }

        m = floor(miny / 50.0);

        if (!ymin_good) {
            miny = m * 50;
        }
    } else if (maxy >= 30) {
        m = ceil(maxy / 5.0);
        t = m * 5;

        if (!ymax_good) {
            maxy = t;
        }

        m = floor(miny / 5.0);

        if (!ymin_good) {
            miny = m * 5;
        }
    } else {
        if (maxy == miny && maxy == 0) {
            maxy = 0.5;
        } else {
            //maxy*=4.0;
            //miny*=4.0;
            if (!ymax_good) {
                maxy = ceil(maxy);
            }

            if (!ymin_good) {
                miny = floor(miny);
            }

            //maxy/=4.0;
            //miny/=4.0;
        }
    }

    // Make the range symmetrical if there are both positive and negative values.
    if (miny < 0 && maxy > 0) {
        EventDataType tmp = qMax(qAbs(miny), qAbs(maxy));
        maxy = tmp;
        miny = -tmp;
    }


    //if (m_enforceMinY) { miny=f_miny; }
    //if (m_enforceMaxY) { maxy=f_maxy; }
}

void gGraph::AddLayer(Layer *l, LayerPosition position, short width, short height, short order, bool movable, short x, short y)
{
    l->setLayout(position, width, height, order);
    l->setMovable(movable);
    l->setPos(x, y);
    l->addref();
    m_layers.push_back(l);

    if (l->layerType()==LT_LineChart) {
        m_lineChart_layer = dynamic_cast<gLineChart *>(l);
    }

}

void gGraph::dataChanged()
{
    for (auto & layer : m_layers) {
        layer->dataChanged();
    }
}

void gGraph::redraw()
{
    m_graphview->redraw();
}
void gGraph::timedRedraw(int ms)
{
    m_graphview->timedRedraw(ms);
}

double gGraph::currentTime() const
{
    return m_graphview->currentTime();
}
double gGraph::screenToTime(int xpos)
{
    double w = m_rect.width() - left - right;
    double xx = m_blockzoom ? rmax_x - rmin_x : max_x - min_x;
    double xmult = xx / w;
    double x = xpos - m_rect.left() - left;
    double res = xmult * x;
    res += m_blockzoom ? rmin_x : min_x;
    return res;
}

void gGraph::mouseMoveEvent(QMouseEvent *event)
{
//    qDebug() << m_title << "Move" << event->pos() << m_graphview->pointClicked();
    if (m_rect.width() == 0) return;
    int y = event->y();
    int x = event->x();

    //bool doredraw = false;

    timedRedraw(0);


    for (const auto & layer : m_layers) {
        if (layer->m_rect.contains(x, y))
            if (layer->mouseMoveEvent(event, this)) {
                return;
            }
    }

    y -= m_rect.top();
    x -= m_rect.left();

    int x2 = m_graphview->pointClicked().x() - m_rect.left();

    int w = m_rect.width() - left - right;

    double xx;
    double xmult;

    {
        xmult = (m_blockzoom ? double(rmax_x - rmin_x) : double(max_x - min_x)) / double(w);

        double  a = x;

        if (a < left) a = left;
        if (a > left+w) a = left+w;

        a -= left;
        a *= xmult;
        a += m_blockzoom ? rmin_x : min_x;

        m_currentTime = a;
        m_graphview->setCurrentTime(a);
    }

    if (m_graphview->m_selected_graph == this) {  // Left Mouse button dragging
        if (event->buttons() & Qt::LeftButton) {

            //qDebug() << m_title << "Moved" << x << y << left << right << top << bottom << m_width << h;
            int a1 = MIN(x, x2);
            int a2 = MAX(x, x2);

            if (a1 < left) { a1 = left; }

            if (a2 > left + w) { a2 = left + w; }

            m_selecting_area = true;
            m_selection = QRect(a1 - 1, 0, a2 - a1, m_rect.height());
            double w2 = m_rect.width() - right - left;

            if (m_blockzoom) {
                xmult = (rmax_x - rmin_x) / w2;
            } else {
                xmult = (max_x - min_x) / w2;
            }

            qint64 a = double(a2 - a1) * xmult;
            m_selectedDuration = a;
            float d = double(a) / 86400000.0;
            int h = a / 3600000;
            int m = (a / 60000) % 60;
            int s = (a / 1000) % 60;
            int ms(a % 1000);

            if (d > 1) {
                m_selDurString = tr("%1 days").arg(floor(d));
            } else {

                m_selDurString.sprintf("%02i:%02i:%02i:%03i", h, m, s, ms);
            }

            ToolTipAlignment align = x >= x2 ? TT_AlignLeft : TT_AlignRight;
            int offset = (x >= x2) ? 20 : - 20;
            ToolTip(m_selDurString, m_rect.left() + x + offset, m_rect.top() + y + 20, align);

            //doredraw = true;
        } else if (event->buttons() & Qt::RightButton) {    // Right Mouse button dragging
            m_graphview->setPointClicked(event->pos());
            x -= left;
            x2 -= left;

            if (!m_blockzoom) {
                xx = max_x - min_x;
                xmult = xx / double(w);
                qint64 j1 = xmult * x;
                qint64 j2 = xmult * x2;
                qint64 jj = j2 - j1;
                min_x += jj;
                max_x += jj;

                if (min_x < rmin_x) {
                    min_x = rmin_x;
                    max_x = rmin_x + xx;
                }

                if (max_x > rmax_x) {
                    max_x = rmax_x;
                    min_x = rmax_x - xx;
                }

                m_graphview->SetXBounds(min_x, max_x, m_group, false);
              //  doredraw = true;
            } else {
                qint64 qq = rmax_x - rmin_x;
                xx = max_x - min_x;

                if (xx == qq) { xx = 1800000; }

                xmult = qq / double(w);
                qint64 j1 = (xmult * x);
                min_x = rmin_x + j1 - (xx / 2);
                max_x = min_x + xx;

                if (min_x < rmin_x) {
                    min_x = rmin_x;
                    max_x = rmin_x + xx;
                }

                if (max_x > rmax_x) {
                    max_x = rmax_x;
                    min_x = rmax_x - xx;
                }

                m_graphview->SetXBounds(min_x, max_x, m_group, false);
                //doredraw = true;
            }
        }
    }

    //if (!nolayer) { // no mouse button
//    if (doredraw) {
//        m_graphview->timedRedraw(0);
//    }

    //}
    //if (x>left+m_marginleft && x<m_lastbounds.width()-(right+m_marginright) && y>top+m_margintop && y<m_lastbounds.height()-(bottom+m_marginbottom)) { // main area
    //        x-=left+m_marginleft;
    //        y-=top+m_margintop;
    //        //qDebug() << m_title << "Moved" << x << y << left << right << top << bottom << m_width << m_height;
    //    }
}

bool gGraph::selectingArea() { return m_selecting_area || m_graphview->metaSelect(); }

void gGraph::mousePressEvent(QMouseEvent *event)
{
    int y = event->pos().y();
    int x = event->pos().x();

//    qDebug() << m_title << "gGraph mousePressEvent, x=" << x << " y=" << y;

    for (const auto & layer : m_layers) {
        if (layer->m_rect.contains(x, y))
            if (layer->mousePressEvent(event, this)) {
                return;
            }
    }

    /*
    int w=m_lastbounds.width()-(right+m_marginright);
    //int h=m_lastbounds.height()-(bottom+m_marginbottom);
    //int x2,y2;
    double xx=max_x-min_x;
    //double xmult=xx/w;
    if (x>left+m_marginleft && x<m_lastbounds.width()-(right+m_marginright) && y>top+m_margintop && y<m_lastbounds.height()-(bottom+m_marginbottom)) { // main area
        x-=left+m_marginleft;
        y-=top+m_margintop;
    }*/
    //qDebug() << m_title << "Clicked" << x << y << left << right << top << bottom << m_width << m_height;
}

void gGraph::mouseReleaseEvent(QMouseEvent *event)
{

    int y = event->pos().y();
    int x = event->pos().x();
//    qDebug() << m_title << "gGraph mouseReleaseEvent at x,y"  << x << y;

    for (const auto & layer : m_layers) {
        if (layer->m_rect.contains(x, y))
            if (layer->mouseReleaseEvent(event, this)) {
                return;
            }
    }

    x -= m_rect.left();
    y -= m_rect.top();

    int w = m_rect.width() - left - right; //(m_marginleft+left+right+m_marginright);
    int h = m_rect.height() - bottom; //+m_marginbottom);

    int x2 = m_graphview->pointClicked().x() - m_rect.left();
    //int y2 = m_graphview->pointClicked().y() - m_rect.top();

    m_selDurString = QString();

//    qDebug() << m_title << "Released" << min_x << max_x << x << y << x2 << left << right << top << bottom << m_width << m_height;

    if (m_selecting_area) {
        m_selecting_area = false;
        m_selection.setWidth(0);

        // Shift-drag only measures a range and should not zoom in.
        if ((event->modifiers() & Qt::ShiftModifier) != 0) {
            return;
        }

        if (m_graphview->horizTravel() > mouse_movement_threshold) {
            x -= left; //+m_marginleft;
            //y -= top; //+m_margintop;
            x2 -= left; //+m_marginleft;
            //y2 -= top; //+m_margintop;

            if (x < 0) { x = 0; }

            if (x2 < 0) { x2 = 0; }

            if (x > w) { x = w; }

            if (x2 > w) { x2 = w; }

            double xx;
            double xmult;

            if (!m_blockzoom) {
                xx = max_x - min_x;
                xmult = xx / double(w);
                qint64 j1 = min_x + xmult * x;
                qint64 j2 = min_x + xmult * x2;
                qint64 a1 = MIN(j1, j2)
                            qint64 a2 = MAX(j1, j2)

                                        //if (a1<rmin_x) a1=rmin_x;
                if (a2 > rmax_x) { a2 = rmax_x; }

                if (a1 == a2)  // Don't zoom into a block if range is zero
                    return;

                if (a1 <= rmin_x && a2 <= rmin_x) {
                    //qDebug() << "Foo??";
                } else {
//                    qDebug() << m_title << "!m_blockzoom (960), a1, a2" << a1 << a2;
                    if (a2 - a1 < zoom_hard_limit) { a2 = a1 + zoom_hard_limit; }

                    m_graphview->SetXBounds(a1, a2, m_group);
                }
            } else {
                xx = rmax_x - rmin_x;
                xmult = xx / double(w);
                qint64 j1 = rmin_x + xmult * x;
                qint64 j2 = rmin_x + xmult * x2;
                qint64 a1 = MIN(j1, j2)
                qint64 a2 = MAX(j1, j2)

                //if (a1<rmin_x) a1=rmin_x;
                if (a2 > rmax_x) { a2 = rmax_x; }

                if (a1 <= rmin_x && a2 <= rmin_x) {
                    qDebug() << "Foo2??";
                } else  {
//                     qDebug() << m_title << "m_blockzoom (979), a1, a2" << a1 << a2;
                    if (a2 - a1 < zoom_hard_limit) { a2 = a1 + zoom_hard_limit; }
                    m_graphview->SetXBounds(a1, a2, m_group);
                }
            }

            return;
        } else { m_graphview->redraw(); }
    }

    if ((m_graphview->horizTravel() < mouse_movement_threshold) && (x > left && x < w + left
            && y > top && y < h)) {
        // normal click in main area
        if (!m_blockzoom) {
            double zoom;

            if (event->button() & Qt::RightButton) {
                zoom = 1.33;

                if (event->modifiers() & Qt::ControlModifier) { zoom *= 1.5; }

                ZoomX(zoom, x); // Zoom out
                return;
            } else if (event->button() & Qt::LeftButton) {
                zoom = 0.75;

                if (event->modifiers() & Qt::ControlModifier) { zoom /= 1.5; }

                ZoomX(zoom, x); // zoom in.
                return;
            }
        } else {
            x -= left;
            //y -= top;
            //w-=m_marginleft+left;
            double qq = rmax_x - rmin_x;
            double xmult;

            double xx = max_x - min_x;
            //if (xx==qq) xx=1800000;

            xmult = qq / double(w);

            if ((xx == qq) || (x == m_lastx23)) {
                double zoom = 1;

                if (event->button() & Qt::RightButton) {
                    zoom = 1.33;

                    if (event->modifiers() & Qt::ControlModifier) { zoom *= 1.5; }
                } else if (event->button() & Qt::LeftButton) {
                    zoom = 0.75;

                    if (event->modifiers() & Qt::ControlModifier) { zoom /= 1.5; }
                }

                xx *= zoom;

                if (xx < qq / zoom_hard_limit) { xx = qq / zoom_hard_limit; }

                if (xx > qq) { xx = qq; }
            }

            double j1 = xmult * x;
            min_x = rmin_x + j1 - (xx / 2.0);
            max_x = min_x + xx;

            if (min_x < rmin_x) {
                min_x = rmin_x;
                max_x = rmin_x + xx;
            } else if (max_x > rmax_x) {
                max_x = rmax_x;
                min_x = rmax_x - xx;
            }

            m_graphview->SetXBounds(min_x, max_x, m_group);
            m_lastx23 = x;
        }
    }

    //m_graphview->redraw();
}


void gGraph::wheelEvent(QWheelEvent *event)
{
    qDebug() << m_title << "Wheel" << event->x() << event->y() << event->delta();
    //int y=event->pos().y();
    if (event->orientation() == Qt::Horizontal) {

        return;
    }

    int x = event->pos().x() - m_graphview->titleWidth; //(left+m_marginleft);

    if (event->delta() > 0) {
        ZoomX(0.75, x);
    } else {
        ZoomX(1.5, x);
    }

    int y = event->pos().y();
    x = event->pos().x();

    for (const auto & layer : m_layers) {
        if (layer->m_rect.contains(x, y)) {
            layer->wheelEvent(event, this);
        }
    }

}
void gGraph::mouseDoubleClickEvent(QMouseEvent *event)
{
    //mousePressEvent(event);
    //mouseReleaseEvent(event);
    int y = event->pos().y();
    int x = event->pos().x();

    for (const auto & layer : m_layers) {
        if (layer->m_rect.contains(x, y)) {
            layer->mouseDoubleClickEvent(event, this);
        }
    }
}
void gGraph::keyPressEvent(QKeyEvent *event)
{
    for (const auto & layer : m_layers) {
        layer->keyPressEvent(event, this);
    }

    //qDebug() << m_title << "Key Pressed.. implement me" << event->key();
}

void gGraph::keyReleaseEvent(QKeyEvent *event)
{
    if (!m_graphview) return;

    if (m_graphview->selectionInProgress() && m_graphview->metaSelect()) {
        if (!(event->modifiers() & Qt::AltModifier)) {

        }
    }
}


void gGraph::ZoomX(double mult, int origin_px)
{

//    qDebug() << "gGraph::ZoomX width, left, right" << m_rect.width() << left << right;
    int width = m_rect.width() - left - right; //(m_marginleft+left+right+m_marginright);

    if (origin_px == 0) { origin_px = (width / 2); }
    else { origin_px -= left; }

    if (origin_px < 0) { origin_px = 0; }

    if (origin_px > width) { origin_px = width; }


    // Okay, I want it to zoom in centered on the mouse click area..
    // Find X graph position of mouse click
    // find current zoom width
    // apply zoom
    // center on point found in step 1.

    qint64 min = min_x;
    qint64 max = max_x;

    double hardspan = rmax_x - rmin_x;
    double span = max - min;
    double ww = double(origin_px) / double(width);
    double origin = ww * span;
    //double center=0.5*span;
    //double dist=(origin-center);

    double q = span * mult;

    if (q > hardspan) { q = hardspan; }

    if (q < hardspan / zoom_hard_limit) { q = hardspan / zoom_hard_limit; }

    min = min + origin - (q * ww);
    max = min + q;

    if (min < rmin_x) {
        min = rmin_x;
        max = min + q;
    }

    if (max > rmax_x) {
        max = rmax_x;
        min = max - q;
    }

    //extern const int max_history;

    m_graphview->SetXBounds(min, max, m_group);
    //updateSelectionTime(max-min);
}

void gGraph::DrawTextQue(QPainter &painter)
{
    AppSetting->usePixmapCaching() ? m_graphview->DrawTextQueCached(painter) : m_graphview->DrawTextQue(painter);
}

// margin recalcs..
void gGraph::resize(int width, int height)
{
    invalidate_xAxisImage = true;
    invalidate_yAxisImage = true;

    Q_UNUSED(width);
    Q_UNUSED(height);
    //m_height=height;
    //m_width=width;
}

qint64 gGraph::MinX()
{
    qint64 val = 0, tmp;

    for (const auto & layer : m_layers) {
        if (layer->isEmpty()) {
            continue;
        }

        tmp = layer->Minx();

        if (!tmp) {
            continue;
        }

        if (!val || tmp < val) {
            val = tmp;
        }
    }

    if (val) { rmin_x = val; }

    return val;
}
qint64 gGraph::MaxX()
{
    //bool first=true;
    qint64 val = 0, tmp;

    for (const auto & layer : m_layers) {
        if (layer->isEmpty()) {
            continue;
        }

        tmp = layer->Maxx();

        //if (!tmp) continue;
        if (!val || (tmp > val)) {
            val = tmp;
        }
    }

    if (val) { rmax_x = val; }

    return val;
}

EventDataType gGraph::MinY()
{
    bool first = true;
    EventDataType val = 0, tmp;

    if (m_enforceMinY) {
        return rmin_y = f_miny;
    }

    for (const auto & layer : m_layers) {
        if (layer->isEmpty() || (layer->layerType() == LT_Other)) {
            continue;
        }

        tmp = layer->Miny();

//        if (tmp == 0 && tmp == (*l)->Maxy()) {
//            continue;
//        }

        if (first) {
            val = tmp;
            first = false;
        } else {
            if (tmp < val) {
                val = tmp;
            }
        }
    }

    return rmin_y = val;
//    return rmin_y = val * 0.9;
}
EventDataType gGraph::MaxY()
{
    bool first = true;
    EventDataType val = 0, tmp;

    if (m_enforceMaxY) {
        return rmax_y = f_maxy;
    }

    for (const auto & layer : m_layers) {
        if (layer->isEmpty() || (layer->layerType() == LT_Other)) {
            continue;
        }

        tmp = layer->Maxy();
//        if (tmp == 0 && layer->Miny() == 0) {
//            continue;
//        }

        if (first) {
            val = tmp;
            first = false;
        } else {
            if (tmp > val) {
                val = tmp;
            }
        }
    }

    return rmax_y = val;
}

EventDataType gGraph::physMinY()
{
    bool first = true;
    EventDataType val = 0, tmp;

    //if (m_enforceMinY) return rmin_y=f_miny;

    for (const auto & layer : m_layers) {
        if (layer->isEmpty()) {
            continue;
        }

        tmp = layer->physMiny();
        if (tmp == 0 && layer->physMaxy() == 0) {
            continue;
        }

        if (first) {
            val = tmp;
            first = false;
        } else {
            if (tmp < val) {
                val = tmp;
            }
        }
    }

    return rphysmin_y = val;
}
EventDataType gGraph::physMaxY()
{
    bool first = true;
    EventDataType val = 0, tmp;

    // if (m_enforceMaxY) return rmax_y=f_maxy;

    for (const auto & layer : m_layers) {
        if (layer->isEmpty()) {
            continue;
        }

        tmp = layer->physMaxy();
        if (tmp == 0 && layer->physMiny() == 0) {
            continue;
        }

        if (first) {
            val = tmp;
            first = false;
        } else {
            if (tmp > val) {
                val = tmp;
            }
        }
    }

    return rphysmax_y = val;
}

void gGraph::SetMinX(qint64 v)
{
    rmin_x = min_x = v;
}

void gGraph::SetMaxX(qint64 v)
{
    rmax_x = max_x = v;
}

void gGraph::SetMinY(EventDataType v)
{
    rmin_y = min_y = v;
}

void gGraph::SetMaxY(EventDataType v)
{
    rmax_y = max_y = v;
}

Layer *gGraph::getLineChart()
{
    if (m_lineChart_layer) return m_lineChart_layer;
    for (auto & layer : m_layers) {
        Layer *tmp = dynamic_cast<gLineChart *>(layer);
        if (tmp) m_lineChart_layer = tmp;
    }
    return nullptr;
}

int gGraph::minHeight()
{
    int minheight = m_min_height;

//    int top = 0;
//    int center = 0;
//    int bottom = 0;
    for (const auto & layer : m_layers) {
        int mh = layer->minimumHeight();
        mh += m_margintop + m_marginbottom;
        if (mh > minheight) minheight = mh;
    }
    // layers need to set their own too..
    return minheight;
}

int GetXHeight(QFont *font)
{
    QFontMetrics fm(*font);
    return fm.xHeight();
}

void gGraph::dumpInfo() {
    for (const auto & layer : m_layers) {
        if (!layer->visible()) { continue; }

        if (layer->position() == LayerCenter) {
            gLineChart *lc = dynamic_cast<gLineChart *>(layer);
            if (lc != nullptr) {
                QString text = lc->getMetaString(currentTime());
                if (!text.isEmpty()) {
                    mainwin->log(text);
                }
            }
        }
    }
}