/*
 Constructs a font object that uses the application's default font.
 * This file is part of the source of kcollectd, a viewer for
 * rrd-databases created by collectd
 *
 * Copyright (C) 2008 M G Berberich
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include <time.h>

#include <cmath>
#include <vector>

#include <QFontDatabase>
#include <QFrame>
#include <QIcon>
#include <QMenu>
#include <QPainter>
#include <QPolygon>
#include <QRect>

#include <KLocalizedString>

#include "graph.h"
#include "misc.h"
#include "rrd_interface.h"
#include "timeaxis.h"

#define I18N_NOOP(text) text

// some magic numbers

// colors
static const double colortable[][3] = {
    {0, 255, 0},   {255, 0, 255}, {0, 128, 255}, {255, 128, 0},
    {0, 255, 191}, {64, 0, 255},  {255, 0, 64},  {191, 255, 0}};

// magnification for the header-font
static const double header_font_mag = 1.2;

// distance between elements
const int marg = 2;

inline double norm(const QPointF &a) {
  return sqrt(a.x() * a.x() + a.y() * a.y());
}

/**
 *
 */
Graph::Graph(QWidget *parent)
    : QFrame(parent), data_is_valid(false), start(time(0) - 3600 * 24),
      span(3600 * 24), step(1), dragging(false),
      font(QFontDatabase::systemFont(QFontDatabase::GeneralFont)),
      small_font(
          QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)),
      color_major(140, 115, 60), color_minor(80, 65, 34),
      color_graph_bg(0, 0, 0),
      // color_major(255, 180, 180), color_minor(220, 220, 220),
      // color_graph_bg(255, 255, 255),
      // color_minmax(180, 255, 180, 200), color_line(0, 170, 0),
      autoUpdateTimer(-1) {
  setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
  setMinimumWidth(300);
  setMinimumHeight(150);
  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  setAcceptDrops(true);

  // setup color-tables
  for (int i = 0; i < 8; ++i) {
    color_line[i].setRgb(colortable[i][0], colortable[i][1], colortable[i][2]);
    color_minmax[i].setRgb(0.5 * colortable[i][0], 0.5 * colortable[i][1],
                           0.5 * colortable[i][2], 160);
  }

  // idiotic case-differentiation on point or pixelsize necessary :(
  if (header_font.pixelSize() == -1) {
    header_font.setPointSizeF(header_font_mag * header_font.pointSizeF());
  } else {
    header_font.setPixelSize(header_font_mag * header_font.pixelSize());
  }

  struct timezone tz;
  struct timeval tv;
  gettimeofday(&tv, &tz);
  tz_off = tz.tz_minuteswest * 60;
}

/**
 * get average, min and max data
 *
 * set start end to the values get_rrd_data returns
 * don't change span, because it can shrink
 */
bool Graph::fetchAllData() {
  if (data_is_valid)
    return (true);

  if (empty())
    return (false);

  for (graph_list::iterator i = begin(); i != end(); ++i) {
    for (GraphInfo::iterator j = i->begin(); j != i->end(); ++j) {
      const std::string file(j->rrd.toUtf8().data());
      const std::string ds(j->ds.toUtf8().data());

      data_start = start;
      data_end = start + span;
      step = 1;
      get_rrd_data(file, ds, &data_start, &data_end, &step, "MIN",
                   &j->min_data);

      data_start = start;
      data_end = start + span;
      step = 1;
      get_rrd_data(file, ds, &data_start, &data_end, &step, "MAX",
                   &j->max_data);

      data_start = start;
      data_end = start + span;
      step = 1;
      get_rrd_data(file, ds, &data_start, &data_end, &step, "AVERAGE",
                   &j->avg_data);
    }
  }
  data_is_valid = true;

  return (true);
}

/**
 *
 */
void Graph::clear() {
  glist.clear();
  data_is_valid = false;
  layout();
  update();
}

int Graph::calcLegendHeights(int box_size, int width) {
  const QFontMetrics &fontmetric = fontMetrics();

  int total_legend_height = 0;
  for (graph_list::iterator i = begin(); i != end(); ++i) {

    if (i->empty()) {
      i->legend_lines(0);
      continue;
    }

    std::vector<int> label_width;
    for (GraphInfo::const_iterator gi = i->begin(); gi != i->end(); ++gi)
      label_width.push_back(fontmetric.horizontalAdvance(gi->label) + box_size + marg);

    const int n = label_width.size();
    int r;
    for (r = 1; r < n; ++r) {
      const int col = (n + r - 1) / r;
      int total_width = 0, i = 0;
      for (int j = 0; j < col; ++j) {
        int max = 0;
        for (int k = 0; k < r && i < n; ++i, ++k) {
          if (max < label_width[i])
            max = label_width[i];
        }
        total_width += max;
      }
      total_width += 4 * marg * (col - 1);
      if (total_width <= width)
        break;
    }
    i->legend_lines(r);
    total_legend_height += r * fontmetric.lineSpacing();
  }
  return total_legend_height;
}

void Graph::drawLegend(QPainter &paint, int left, int y, int box_size,
                       const GraphInfo &ginfo) {
  const QFontMetrics &fontmetric = fontMetrics();

  int n = 0, cy = y, cx = left, max_width = 0;
  int lines = ginfo.legend_lines();
  if (lines) {
    for (GraphInfo::const_iterator i = ginfo.begin(); i != ginfo.end(); ++i) {
      paint.drawRect(cx, cy - box_size, box_size - 1, box_size - 1);
      paint.fillRect(cx + 1, cy - box_size + 1, box_size - 2, box_size - 2,
                     color_line[n % 8]);
      paint.drawText(cx + box_size + marg, cy, i->label);

      int w = box_size + marg + fontmetric.horizontalAdvance(i->label);
      if (w > max_width)
        max_width = w;
      cy += fontmetric.lineSpacing();
      ++n;
      if (n % lines == 0) {
        cx += max_width + 4 * marg;
        max_width = 0;
        cy = y;
      }
    }
  }
}

void Graph::drawHeader(QPainter &paint) {
  paint.save();
  paint.setFont(header_font);

  const QFontMetrics &fontmetric = paint.fontMetrics();

  QString format;
  time_t time_span = data_end - data_start;
  if (time_span > 3600 * 24 * 356)
    format = i18n("%Y-%m");
  else if (time_span > 3600 * 24 * 31)
    format = i18n("%A %Y-%m-%d");
  else if (time_span > 3600 * 24)
    format = i18n("%A %Y-%m-%d %H:%M");
  else
    format = i18n("%A %Y-%m-%d %H:%M:%S");

  QString buffer_from = Qstrftime(format.toLatin1(), localtime(&data_start));
  QString buffer_to = Qstrftime(format.toLatin1(), localtime(&data_end));
  QString label = i18n("from %1 to %2", buffer_from, buffer_to);
  int x = (contentsRect().left() + contentsRect().right()) / 2 -
          fontmetric.horizontalAdvance(label) / 2;
  int y = fontmetric.ascent() + marg;
  paint.drawText(x, y, label);

  paint.restore();
}

void Graph::drawFooter(QPainter &paint, int left, int right) {
  paint.save();
  paint.setFont(header_font);

  const QFontMetrics &fontmetric = paint.fontMetrics();

  QString format;
  time_t time_span = data_end - data_start;
  if (time_span > 3600 * 24 * 356)
    format = i18n("%Y-%m");
  else if (time_span > 3600 * 24 * 31)
    format = i18n("%A %Y-%m-%d");
  else if (time_span > 3600 * 24)
    format = i18n("%A %Y-%m-%d %H:%M");
  else
    format = i18n("%A %Y-%m-%d %H:%M:%S");

  QString buffer_from = Qstrftime(format.toLatin1(), localtime(&data_start));
  QString buffer_to = Qstrftime(format.toLatin1(), localtime(&data_end));
  QString label = i18n("from %1 to %2", buffer_from, buffer_to);

  fontmetric.horizontalAdvance(label);
  paint.drawText((left + right) / 2 - fontmetric.horizontalAdvance(label) / 2, label_y2,
                 label);

  paint.restore();
}

void Graph::drawXLines(QPainter &paint, const QRect &rect, time_iterator i,
                       QColor color) {
  if (!i.valid())
    return;

  // setting up linear mappings
  const linMap xmap(data_start, rect.left(), data_end, rect.right());

  // if lines are to close draw nothing
  if (i.interval() * xmap.m() < 3)
    return;

  // draw lines
  paint.save();
  paint.setPen(color);
  for (; *i <= data_end; ++i) {
    int x = xmap(*i);
    paint.drawLine(x, rect.top(), x, rect.bottom());
  }
  paint.restore();
}

void Graph::drawXLabel(QPainter &paint, int y, int left, int right,
                       time_iterator i, QString format, bool center) {
  if (!i.valid())
    return;
  paint.save();
  paint.setFont(small_font);

  // setting up linear mappings
  const linMap xmap(data_start, left, data_end, right);

  // draw labels
  if (center)
    --i;
  for (; *i <= data_end; ++i) {
    // special handling for localtime/mktime on DST
    time_t t = center ? *i + i.interval() / 2 : *i;
    tm tm;
    localtime_r(&t, &tm);
    QString label = Qstrftime(i18n(format.toLatin1()).toLatin1(), &tm);
    if (!label.isNull()) {
      const int width = paint.fontMetrics().horizontalAdvance(label);
      int x = center ? xmap(*i + i.interval() / 2) - width / 2
                     : xmap(*i) - width / 2;

      if (x > left && x + width < right)
        paint.drawText(x, y, label);
    }
  }
  paint.restore();
}

void Graph::findXGrid(int width, QString &format, bool &center,
                      time_iterator &minor_x, time_iterator &major_x,
                      time_iterator &label_x) {
  const time_t min = 60;
  const time_t hour = 3600;
  const time_t day = 24 * hour;
  const time_t week = 7 * day;
  const time_t month = 31 * day;
  const time_t year = 365 * day;

  enum bla { align_tzalign, align_week, align_month };
  struct {
    time_t maxspan;
    time_t major;
    time_t minor;
    const char *format;
    bool center;
    bla align;
  } axis_params[] = {
      {day, 1 * min, 10, "%H:%M", false, align_tzalign},
      {day, 2 * min, 30, "%H:%M", false, align_tzalign},
      {day, 5 * min, min, "%H:%M", false, align_tzalign},
      {day, 10 * min, min, "%H:%M", false, align_tzalign},
      {day, 30 * min, 10 * min, "%H:%M", false, align_tzalign},
      {day, hour, 10 * min, "%H:%M", false, align_tzalign},
      {day, 2 * hour, 30 * min, "%H:%M", false, align_tzalign},
      {day, 3 * hour, hour, "%H:%M", false, align_tzalign},
      {day, 6 * hour, hour, "%H:%M", false, align_tzalign},
      {day, 12 * hour, 3 * hour, "%H:%M", false, align_tzalign},
      {week, 12 * hour, 3 * hour, "%a %H:%M", false, align_tzalign},
      {week, day, 3 * hour, "%a", true, align_tzalign},
      {week, 2 * day, 6 * hour, "%a", true, align_tzalign},
      {month, day, 6 * hour, "%a %d", true, align_tzalign},
      {month, day, 6 * hour, "%d", true, align_tzalign},
      {year, week, day, I18N_NOOP("week %V"), true, align_week},
      {year, month, day, "%b", true, align_month},
      {0, 0, 0, 0, true, align_tzalign},
  };

  const QFontMetrics fontmetric(font);
  const time_t time_span = data_end - data_start;
  const time_t now = time(0);

  for (int i = 0; axis_params[i].maxspan; ++i) {
    if (time_span < axis_params[i].maxspan) {
      QString label = Qstrftime(axis_params[i].format, localtime(&now));
      if (!label.isNull()) {
        const int textwidth =
            fontmetric.horizontalAdvance(label) * time_span / axis_params[i].major * 3 / 2;
        if (textwidth < width) {
          switch (axis_params[i].align) {
          case align_tzalign:
            minor_x.set(data_start, axis_params[i].minor);
            major_x.set(data_start, axis_params[i].major);
            label_x.set(data_start, axis_params[i].major);
            format = axis_params[i].format;
            center = axis_params[i].center;
            break;
          case align_week:
            minor_x.set(data_start, day);
            major_x.set(data_start, 1, time_iterator::weeks);
            label_x.set(data_start, 1, time_iterator::weeks);
            format = axis_params[i].format;
            center = axis_params[i].center;
            break;
          case align_month:
            minor_x.set(data_start, axis_params[i].minor);
            major_x.set(data_start, 1, time_iterator::month);
            label_x.set(data_start, 1, time_iterator::month);
            format = axis_params[i].format;
            center = axis_params[i].center;
            break;
          }
          return;
        }
      }
    }
  }
  QString label = Qstrftime("%Y", localtime(&now));
  if (!label.isNull()) {
    const int textwidth = fontmetric.horizontalAdvance(label) * 3 / 2;
    // fixed-point calculation with 16 bit fraction.
    int num = (time_span * textwidth * 16) / (year * width);
    if (num < 16) {
      minor_x.set(data_start, 1, time_iterator::month);
      major_x.set(data_start, 1, time_iterator::years);
      label_x.set(data_start, 1, time_iterator::years);
      format = "%Y";
      center = true;
    } else {
      minor_x.set(data_start, 1, time_iterator::years);
      major_x.set(data_start, (num + 15) / 16, time_iterator::years);
      label_x.set(data_start, (num + 15) / 16, time_iterator::years);
      format = "%Y";
      center = false;
    }
  }
}

void Graph::drawYLabel(QPainter &paint, const QRect &rect, const Range &y_range,
                       double base) {
  // setting up linear mappings
  const linMap ymap(y_range.min(), rect.bottom(), y_range.max(), rect.top());

  const QFontMetrics fontmetric(font);

  // SI Unit for nice display
  std::string SI;
  double mag;
  si_char(y_range.max(), SI, mag);

  // make sure labels don not overlap
  const int fontheight = fontmetric.height();

  // if we cannot even fit the text, just abort instead of crashing
  if (rect.height() < fontheight)
    return;

  while (rect.height() < fontheight * (y_range.max() - y_range.min()) / base)
    if (rect.height() > fontheight * (y_range.max() - y_range.min()) / base / 2)
      base *= 2;
    else if (rect.height() >
             fontheight * (y_range.max() - y_range.min()) / base / 5)
      base *= 5;
    else
      base *= 10;

  // draw labels
  double min = ceil(y_range.min() / base) * base;
  double max = floor(y_range.max() / base) * base;
  for (double i = min; i <= max; i += base) {
    const std::string label = si_number(i, 6, SI, mag);
    const int x = rect.left() - fontmetric.horizontalAdvance(label.c_str()) - 4;
    paint.drawText(x, ymap(i) + fontmetric.ascent() / 2, label.c_str());
  }
}

void Graph::drawYLines(QPainter &paint, const QRect &rect, const Range &y_range,
                       double base, QColor color) {
  // setting up linear mappings
  const linMap ymap(y_range.min(), rect.bottom(), y_range.max(), rect.top());

  const QFontMetrics fontmetric(font);

  // adjust linespacing
  if (base * -ymap.m() < 5.0) {
    if (2.0 * base * -ymap.m() > 5.0)
      base = base * 2.0;
    else
      base = base * 5.0;
  }

  // and return if insufficient
  if (base * -ymap.m() < 5)
    return;

  // draw lines
  paint.save();
  paint.setPen(color);
  double min = ceil(y_range.min() / base) * base;
  double max = y_range.max();
  for (double i = min; i < max; i += base) {
    paint.drawLine(rect.left(), ymap(i), rect.right(), ymap(i));
  }
  paint.restore();
}

/**
 * draw the graph itself
 */
void Graph::drawGraph(QPainter &paint, const QRect &rect,
                      const GraphInfo &ginfo, double min, double max) {
  const linMap ymap(min, rect.bottom(), max, rect.top());
  // define once use many
  QPolygon points;

  paint.save();
  // paint.setRenderHint(QPainter::Antialiasing);

  // draw all min/max backshadows
  int color_nr = 0;
  for (GraphInfo::const_iterator gi = ginfo.begin(); gi != ginfo.end(); ++gi) {
    const std::vector<double> &min_data = gi->min_data;
    const std::vector<double> &max_data = gi->max_data;

    if (min_data.empty() || max_data.empty())
      continue;
    const int size = gi->min_data.size();

    // setting up linear mappings
    const linMap xmap(0, rect.left(), size - 1, rect.right());

    if (!min_data.empty() && !max_data.empty()) {
      paint.setPen(Qt::NoPen);
      paint.setBrush(QBrush(color_minmax[color_nr++ % 8]));
      for (int i = 0; i < size; ++i) {
        while (i < size && (std::isnan(min_data[i]) || std::isnan(max_data[i])))
          ++i;
        int l = i;
        while (i < size && !std::isnan(min_data[i]) && !std::isnan(max_data[i]))
          ++i;
        const int asize = i - l;
        points.resize(asize * 2);
        int k;
        for (k = 0; k < asize; ++k, ++l) {
          points.setPoint(k, xmap(l), ymap(min_data[l]));
        }
        --l;
        for (; k < 2 * asize; ++k, --l) {
          points.setPoint(k, xmap(l), ymap(max_data[l]));
        }
        paint.drawPolygon(points);
      }
    }
  }

  // draw all averages
  color_nr = 0;
  for (GraphInfo::const_iterator gi = ginfo.begin(); gi != ginfo.end(); ++gi) {
    const std::vector<double> &avg_data = gi->avg_data;

    if (avg_data.empty())
      continue;
    const int size = avg_data.size();

    // setting up linear mappings
    const linMap xmap(0, rect.left(), size - 1, rect.right());

    // draw ing
    if (!avg_data.empty()) {
      paint.setPen(color_line[color_nr++ % 8]);
      for (int i = 0; i < size; ++i) {
        while (i < size && std::isnan(avg_data[i]))
          ++i;
        int l = i;
        while (i < size && !std::isnan(avg_data[i]))
          ++i;
        const int asize = i - l;
        points.resize(asize);
        for (int k = 0; k < asize; ++k, ++l) {
          points.setPoint(k, xmap(l), ymap(avg_data[l]));
        }
        paint.drawPolyline(points);
      }
    }
  }
  paint.restore();
}

/**
 * draw the widgets contents.
 *
 * this covers a grid, the graph istself, x- and y-label and a header
 */
void Graph::drawAll() {
  const int numgraphs = glist.size();

  if (numgraphs) {
    if (!data_is_valid)
      fetchAllData();

    // clear
    QPainter paint(&offscreen);
    paint.setFont(font);
    paint.eraseRect(0, 0, contentsRect().width(), contentsRect().height());
    // paint.fillRect(0, 0, contentsRect().width(), contentsRect().height(),
    // QColor(245, 245, 245));

    // margin calculations
    // place for labels at the left and two line labels below
    const QFontMetrics &fontmetric = paint.fontMetrics();
    const QFontMetrics smallmetric = QFontMetrics(small_font);
    const QFontMetrics headermetric = QFontMetrics(header_font);

    time_iterator minor_x, major_x, label_x;
    QString format_x;
    bool center_x;
    findXGrid(graph_rect.width(), format_x, center_x, minor_x, major_x,
              label_x);
    drawHeader(paint);

    int n = 0;
    for (graph_list::iterator i = begin(); i != end(); ++n, ++i) {
      const int top = i->top() - contentsRect().top();
      const int bottom = i->bottom() - contentsRect().top();

      // y-scaling
      double base;
      Range y_range = i->minmax_adj(&base);
      if (!y_range.isValid())
        continue;

      // geometry
      const int xlabel_base = bottom + marg + smallmetric.ascent();
      const int legend_base = top + graph_height + fontmetric.ascent();

      // panel area
      QRect panelrect(graph_rect.left(), top, graph_rect.width(), bottom - top);

      // graph-background
      paint.fillRect(panelrect, color_graph_bg);

      // draw minor, major, graph
      drawXLabel(paint, xlabel_base, graph_rect.left(), graph_rect.right(),
                 label_x, format_x, center_x);
      drawXLines(paint, panelrect, minor_x, color_minor);
      drawYLines(paint, panelrect, y_range, base / 10, color_minor);
      drawXLines(paint, panelrect, major_x, color_major);
      drawYLines(paint, panelrect, y_range, base, color_major);
      drawYLabel(paint, panelrect, y_range, base);
      drawGraph(paint, panelrect, *i, y_range.min(), y_range.max());
      drawLegend(paint, marg, legend_base, box_size, *i);
    }
    paint.end();
    // copy to screen
    QPainter(this).drawPixmap(contentsRect(), offscreen);
  } else {
    QPainter paint(this);
    paint.eraseRect(contentsRect());
    const QString label(i18n("Drag a data source from tree (at left) to view it here."));
    const int labelwidth = paint.fontMetrics().horizontalAdvance(label);
    paint.drawText((width() - labelwidth) / 2, height() / 2, label);
    paint.end();
  }
}

void Graph::layout() {
  const int numgraphs = glist.size();
  if (!numgraphs)
    return;

  // resize offscreen-map to widget-size
  offscreen = QPixmap(contentsRect().width(), contentsRect().height());

  QPainter paint(&offscreen);
  paint.setFont(font);

  // margin calculations
  // place for labels at the left and two line labels below
  const QFontMetrics fontmetric = QFontMetrics(font);
  const QFontMetrics smallmetric = QFontMetrics(small_font);
  const QFontMetrics headermetric = QFontMetrics(header_font);

  const int labelwidth = fontmetric.boundingRect("888.888 M").width();
  box_size = fontmetric.ascent() - 2;

  // area for graphs (including legends)
  graph_rect.setRect(labelwidth + marg, headermetric.height() + 2 * marg,
                     offscreen.width() - labelwidth - marg,
                     offscreen.height() - headermetric.height() - 2 * marg);

  const int total_legend_height =
      calcLegendHeights(box_size, offscreen.width() - 2 * marg);
  graph_height =
      (graph_rect.height() - total_legend_height - marg * (2 * numgraphs - 1)) /
      numgraphs;

  int top = graph_rect.top();
  for (graph_list::iterator i = begin(); i != end(); ++i) {
    int bottom = top + graph_height - marg - smallmetric.lineSpacing();
    i->top(top + contentsRect().top());
    i->bottom(bottom + contentsRect().top());
    top +=
        graph_height + i->legend_lines() * fontmetric.lineSpacing() + 2 * marg;
  }
}

/**
 * Qt (re)paint event
 */
void Graph::paintEvent(QPaintEvent *e) {
  QFrame::paintEvent(e);
  drawAll();
}

void Graph::resizeEvent(QResizeEvent * /*event*/) { layout(); }

Graph::graph_list::iterator Graph::graphAt(const QPoint &pos) {
  for (graph_list::iterator i = begin(); i != end(); ++i) {
    if (i->top() < pos.y() && i->bottom() > pos.y())
      return i;
  }
  return end();
}

Graph::graph_list::const_iterator Graph::graphAt(const QPoint &pos) const {
  for (graph_list::const_iterator i = begin(); i != end(); ++i) {
    if (i->top() < pos.y() && i->bottom() > pos.y())
      return i;
  }
  return end();
}

/**
 * switch auto-update on or off
 *
 * Auto-update does a update every 10 sec.
 */
void Graph::autoUpdate(bool active) {
  if (active == true) {
    if (autoUpdateTimer == -1) {
      autoUpdateTimer = startTimer(10000);
      timer_diff = 0.99 * span;
      start = time(0) - timer_diff;
      data_is_valid = false;
      update();
    }
  } else {
    if (autoUpdateTimer != -1) {
      killTimer(autoUpdateTimer);
      autoUpdateTimer = -1;
    }
  }
}

/**
 *
 */
void Graph::removeGraph() {
  const int numgraphs = glist.size();
  if (!numgraphs)
    return;

  graph_list::iterator target = graphAt(QPoint(0, origin_y));
  if (target != end()) {
    glist.erase(target);
    changed(true);
  }
  layout();
  update();
}

/**
 *
 */
void Graph::splitGraph() {
  add();
  layout();
  update();
}

/**
 * Qt mouse-press-event
 */
void Graph::mousePressEvent(QMouseEvent *e) {
  origin_x = qRound(e->position().x());
  origin_y = qRound(e->position().y());

  origin_start = data_start;
  origin_end = data_end;

  // context-menu
  if (e->button() == Qt::RightButton) {
    // map for delete-datasource-options
    typedef std::map<QAction *, GraphInfo::iterator> actionmap;
    actionmap acts;

    // context-menu
    QMenu menu(this);

    graph_list::iterator s_graph = graphAt(e->position().toPoint());

    menu.addAction(QIcon("list-add"), i18n("add new subgraph"), this,
                   SLOT(splitGraph()));

    if (s_graph != end()) {
      menu.addAction(QIcon("edit-delete"), i18n("delete this subgraph"), this,
                     SLOT(removeGraph()));
      menu.addSeparator();

      // generate entries to remove datasources
      for (GraphInfo::iterator i = s_graph->begin(); i != s_graph->end(); ++i) {
        QAction *T =
            menu.addAction(QIcon("list-remove"), i18n("remove ") + i->label);
        acts[T] = i;
      }
    }

    QAction *action = menu.exec(e->globalPosition().toPoint());

    actionmap::iterator result = acts.find(action);
    if (result != acts.end()) {
      s_graph->erase(result->second);
      layout();
      update();
    }
  }
}

/**
 * Qt mouse-event
 *
 * handle dragging of mouse in graph
 */
void Graph::mouseMoveEvent(QMouseEvent *e) {
  if (e->buttons() == Qt::LeftButton) {
    if (autoUpdateTimer != -1)
      return;

    int x = qRound(e->position().x());
    int y = qRound(e->position().y());

    if ((x < graph_rect.left()) || (x >= graph_rect.right()))
      return;
    if ((y < 0) || (y >= height()))
      return;

    int offset =
        (x - origin_x) * (origin_end - origin_start) / graph_rect.width();

    start = origin_start - offset;
    const time_t now = time(0);
    if (start + span > now + span * 2 / 3)
      start = now - span / 3;

    if (autoUpdateTimer != -1)
      timer_diff = time(0) - start;

    data_is_valid = false;
    update();
  } else if (e->buttons() == Qt::MiddleButton) {
    dragging = true;
    update();
  } else {
    e->ignore();
  }
}

/**
 *
 */
void Graph::wheelEvent(QWheelEvent *e) {
  int degreesScrolled = e->angleDelta().y();

  if (degreesScrolled)
    zoom(degreesScrolled / 120.0);
}

/**
 *
 */
void Graph::timerEvent(QTimerEvent * /*event*/) {
  data_is_valid = false;
  start = time(0) - timer_diff;
  update();
}

/**
 *
 */

void Graph::dragEnterEvent(QDragEnterEvent *event) {
  const GraphMimeData *mimeData =
      qobject_cast<const GraphMimeData *>(event->mimeData());

  if (mimeData)
    event->acceptProposedAction();
  else
    event->ignore();
}

/**
 *hermione•load•load•longterm
 */
void Graph::dragMoveEvent(QDragMoveEvent *event) {
  const GraphMimeData *mimeData =
      qobject_cast<const GraphMimeData *>(event->mimeData());
  if (!mimeData)
    return;

  if (glist.size() == 0) {
    event->accept(contentsRect());
  } else {
    graph_list::iterator i = graphAt(event->position().toPoint());
    if (i != end()) {
      QRect g;
      g.setCoords(contentsRect().left(), i->top(), contentsRect().right(),
                  i->bottom());
      event->accept(g);
    } else {
      event->ignore();
    }
  }
}

/**
 *
 */
void Graph::dropEvent(QDropEvent *event) {
  const GraphMimeData *mimeData =
      qobject_cast<const GraphMimeData *>(event->mimeData());
  if (!mimeData)
    return;

  event->acceptProposedAction();
  const int numgraphs = glist.size();
  if (numgraphs) {
    graph_list::iterator target = graphAt(event->position().toPoint());
    if (target != end()) {
      target->add(mimeData->rrd(), mimeData->ds(), mimeData->label());
    } else {
      add(mimeData->rrd(), mimeData->ds(), mimeData->label());
    }
  } else {
    add(mimeData->rrd(), mimeData->ds(), mimeData->label());
  }
  data_is_valid = false;
  layout();
  update();
}

/**
 * set the graph to display the last new_span seconds
 */
void Graph::last(time_t new_span) {
  span = new_span;

  if (autoUpdateTimer != -1) {
    timer_diff = 0.99 * span;
    start = time(0) - timer_diff;
  } else {
    start = time(0) - 0.99 * span;
  }
  data_is_valid = false;
  update();
}

/**
 * zoom graph with factor
 */
void Graph::zoom(double clicks) {
  double factor = exp(-0.23104906027008765 * clicks);

  // don't zoom to wide
  if (factor < 1 && span * factor < width())
    return;

  time_t time_center = data_end - span / 2;
  if (time_center < 0)
    return;
  span *= factor;
  start = time_center - (span / 2);

  const time_t now = time(0);
  if (start + span > now + span * 2 / 3)
    start = now - span / 3;

  if (autoUpdateTimer != -1) {
    timer_diff = 0.99 * span;
    start = time(0) - timer_diff;
  }

  data_is_valid = false;
  update();
}

/**
 * returns range for y-values
 */
Range GraphInfo::minmax() {
  Range r;
  for (const_iterator i = begin(); i != end(); ++i) {
    Range a = ds_minmax(i->avg_data, i->min_data, i->max_data);
    if (a.isValid()) {
      if (r.isValid())
        r = range_max(r, a);
      else
        r = a;
    }
  }
  return r;
}

/**
 * returns adjusted range for y-values
 */
Range GraphInfo::minmax_adj(double *base) {
  Range y = minmax();
  return range_adj(y, base);
}
