/***************************************************************************
                          kbandwidthview.cpp  -  description
                             -------------------
    copyright            : (C) 2007 by Hans Dembinski
    email                : kbandwidth@5th-heaven.org
    original author      : Niko Sommer
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "kbandwidthview.h"
#include "preferencesdialog.h"
#include <klocale.h>
#include <kconfig.h>

#include <qpainter.h>
#include <qtooltip.h>
#include <qfile.h>
#include <qstring.h>
#include <qfont.h>
#include <qimage.h>
#include <qpointarray.h>
#include <qbitmap.h>
#include <qdatetime.h>

#include <fstream>
#include <iostream>
#include <cmath>
using namespace std;

KBandwidthView::KBandwidthView( KConfig* ksCfg,  QWidget* parent, const char* name, WFlags fl ) :
  QWidget( parent, name, fl ), QToolTip(this),

  previous(), bandwidth(),

  orientation(eHorizontal),

  limits(),
  forceInterface(),
  divisor(1024),
  updateInterval(1000),
  colorNormalIncoming(104,145, 66),
  colorNormalOutgoing(255,129, 70),
  colorArrowIncoming (colorNormalIncoming),
  colorArrowOutgoing (colorNormalOutgoing),
  arrowTransparency  (40),
  colorText          (  0,  0,  0),
  showText(false),
  showArrows(true),
  flagLearn(true),
  arrowWidth(0),
  textWidth(0)
{
  if ( !name ) setName( "kbandwidthview" );

  setMinimumSize( QSize( 10, 10 ) );
  languageChange();
  clearWState( WState_Polished );
  /////////////////////////////////

  kConfig = ksCfg;

  updateConfig();
}

int KBandwidthView::setHorizontal(const int& height) {
//   if (height == this->height()) return width(); // nothing to be done

  orientation = eHorizontal;
  textWidth  = static_cast<int>(height*2.5);
  arrowWidth = static_cast<int>(height/4);
  const int a = ( showArrows ) ? arrowWidth : 0;
  const int t = ( showText   ) ? textWidth  : 0;
  setFixedWidth (height + a + t);
  setFixedHeight(height);
  generateArrowPixmaps();
  return height + a + t;
}

int KBandwidthView::setVertical  (const int& width ) {
//   if (width == this->width()) return height(); // nothing to be done

  orientation = eVertical;
  qDebug("vertical mode not implemented yet");
  setFixedWidth(width);
  setFixedHeight(width);
  generateArrowPixmaps();
  return width;
}

void KBandwidthView::updateConfig() {
  kConfig->setGroup("General");

  updateInterval = kConfig->readNumEntry("updateInterval",updateInterval);
  killTimer(timerId);
  timerId = startTimer(updateInterval);

  forceInterface = kConfig->readEntry         ("forceInterface", forceInterface);
  divisor        = kConfig->readNumEntry      ("divisor"       , divisor       );
  showArrows     = kConfig->readBoolEntry     ("showArrows"    , showArrows    );
  showText       = kConfig->readBoolEntry     ("showText"      , showText      );
  flagLearn      = kConfig->readBoolEntry     ("learn"         , flagLearn     );
  bandwidth.interface = kConfig->readEntry    ("lastInterface" , bandwidth.interface);

  QStringList ifaces = listInterfaces();
  for( QStringList::const_iterator it = ifaces.begin();
    it != ifaces.end(); ++it )
  {
    const QString& iface = *it;
    Bandwidth& bw  = limits[iface];
    bw.incoming =
      kConfig->readNumEntry(iface+".incoming",bw.incoming);
    bw.outgoing =
      kConfig->readNumEntry(iface+".outgoing",bw.outgoing);
    bw.interface = iface;
  }

  ///////////////////
  kConfig->setGroup("Colors");

  colorNormalIncoming = kConfig->readColorEntry("normalIncoming", &colorNormalIncoming);
  colorNormalOutgoing = kConfig->readColorEntry("normalOutgoing", &colorNormalOutgoing);
  colorArrowIncoming  = kConfig->readColorEntry("arrowIncoming" , &colorArrowIncoming );
  colorArrowOutgoing  = kConfig->readColorEntry("arrowOutgoing" , &colorArrowOutgoing );

  arrowTransparency   = kConfig->readNumEntry  ("arrowTransparency", arrowTransparency);

  colorText           = kConfig->readColorEntry("text"          , &colorText          );

  ///////////////////
  readInterfaceData(previous);
}

void KBandwidthView::writeConfig()
{
//////////////////
  kConfig->setGroup("General");

  kConfig->writeEntry("forceInterface", forceInterface       );
  kConfig->writeEntry("showText"      , showText             );
  kConfig->writeEntry("showArrows"    , showArrows           );
  kConfig->writeEntry("updateInterval", updateInterval       );

  for (QMap<QString,Bandwidth>::iterator it=limits.begin(); it!=limits.end(); ++it) {
    kConfig->writeEntry(it.key()+".incoming", it.data().incoming);
    kConfig->writeEntry(it.key()+".outgoing", it.data().outgoing);
  }

  kConfig->writeEntry("learn",flagLearn);
  kConfig->writeEntry("divisor",divisor);

//////////////////
  kConfig->setGroup("Colors");

  kConfig->writeEntry("normalIncoming",      colorNormalIncoming);
  kConfig->writeEntry("normalOutgoing",      colorNormalOutgoing);

  kConfig->writeEntry("arrowIncoming",       colorArrowIncoming );
  kConfig->writeEntry("arrowOutgoing",       colorArrowOutgoing );

  kConfig->writeEntry("arrowTransparency",   arrowTransparency  );

  kConfig->writeEntry("text",                colorText          );

//////////////////
  kConfig->sync();
  emit configChanged();
}

void KBandwidthView::generateArrowPixmaps() {
  if (showArrows and height() > 0 and arrowWidth > 2) {
    const int h(height()/2-3), w(arrowWidth);

    const int transparency =
      static_cast<int>(arrowTransparency*2.55);

    // incoming off
    QImage image(w, h, 32);
    image.fill(0);
    image.setAlphaBuffer(true);
    QRgb col = qRgba(
      colorArrowIncoming.red(),
      colorArrowIncoming.green(),
      colorArrowIncoming.blue(),
      transparency);
    for (int iy = 0; iy < h; ++iy)
      for (int ix = 0; ix < w; ++ix)
      if ( ( iy <  h/2 and ix <= iy )
        or ( iy >= h/2 and ix < h-iy )
        and image.valid(ix,iy) )
        image.setPixel(ix,iy,col);
    arrowIncoming = image;

    // incoming on
    image.create(w, h, 32);
    image.fill(0);
    image.setAlphaBuffer(true);
    col = qRgba(
      colorArrowIncoming.red(),
      colorArrowIncoming.green(),
      colorArrowIncoming.blue(),
      255);
    for (int iy = 0; iy < h; ++iy)
      for (int ix = 0; ix < w; ++ix)
      if ( ( iy <  h/2 and ix <= iy )
        or ( iy >= h/2 and ix < h-iy )
        and image.valid(ix,iy) )
        image.setPixel(ix,iy,col);
    arrowIncomingOn = image;

    // outgoing off
    image.create(w, h, 32);
    image.fill(0);
    image.setAlphaBuffer(true);
    col = qRgba(
      colorArrowOutgoing.red(),
      colorArrowOutgoing.green(),
      colorArrowOutgoing.blue(),
      transparency);
    for (int iy = 0; iy < h; ++iy)
      for (int ix = 0; ix < w-1; ++ix)
      if ( ( iy <  h/2 and ix >= h/2 - iy  )
        or ( iy >= h/2 and ix >= iy  - h/2 )
        and image.valid(ix,iy) )
        image.setPixel(ix,iy,col);
    arrowOutgoing = image;

    // outgoing on
    image.create(w, h, 32);
    image.fill(0);
    image.setAlphaBuffer(true);
    col = qRgba(
      colorArrowOutgoing.red(),
      colorArrowOutgoing.green(),
      colorArrowOutgoing.blue(),
      255);
    for (int iy = 0; iy < h; ++iy)
      for (int ix = 0; ix < w-1; ++ix)
      if ( ( iy <  h/2 and ix >= h/2 - iy  )
        or ( iy >= h/2 and ix >= iy  - h/2 )
        and image.valid(ix,iy) )
        image.setPixel(ix,iy,col);
    arrowOutgoingOn = image;
  }
}

KBandwidthView::~KBandwidthView()
{
}

void KBandwidthView::languageChange()
{
  setCaption( tr2i18n( "F" ) );
}

void KBandwidthView::timerEvent(QTimerEvent* te)
{
  if(te->timerId() == timerId)
  {
    calculateBandwidth();

    const QString& iface = bandwidth.interface;

    kConfig->setGroup("General");

    if (iface != kConfig->readEntry("lastInterface", "")) {
      kConfig->writeEntry("lastInterface", iface);
      kConfig->sync();
    }

    if (flagLearn) {
      if (bandwidth.incoming > limits[iface].incoming)
      {
//         cerr << "limit1 " << bandwidth.incoming << " " << limits[iface].incoming << endl;
        limits[iface].incoming = bandwidth.incoming;
        kConfig->writeEntry(iface+".incoming",bandwidth.incoming);
        kConfig->sync();
//         cerr << "limit2 " << bandwidth.incoming << " " << limits[iface].incoming << endl;
      }

      if (bandwidth.outgoing > limits[iface].outgoing)
      {
        limits[iface].outgoing = bandwidth.outgoing;
        kConfig->writeEntry(iface+".outgoing",bandwidth.outgoing);
        kConfig->sync();
      }
    }

    repaint(true);
  }
}

// Show a tool-tip with some status information.
void KBandwidthView::maybeTip(const QPoint& p)
{
  if (!rect().contains(p)) return;
  QString unit;
  switch (divisor) {
    case 128     : unit = "kBit/s"; break;
    case 131072  : unit = "MBit/s"; break;
    case 1024    : unit = "kB/s"  ; break;
    case 1048576 : unit = "MB/s"  ; break;
  }

  QString str = i18n("Bandwidth<br>In %1 %2<br>Out %3 %4<br>Maximums<br>In %5 %6<br>Out %7 %8")
                  .arg(bandwidth.incoming/divisor).arg(unit)
                  .arg(bandwidth.outgoing/divisor).arg(unit)
                  .arg(limits[bandwidth.interface].incoming/divisor).arg(unit)
                  .arg(limits[bandwidth.interface].outgoing/divisor).arg(unit);

  tip(rect(), str);
}

void KBandwidthView::paintEvent(QPaintEvent * pe)
{
  QPainter p;
  p.begin(this);

  float fracIncoming = ( limits[bandwidth.interface].incoming != 0 ) ?
    static_cast<float>(bandwidth.incoming)/limits[bandwidth.interface].incoming : 0.;
  float fracOutgoing = ( limits[bandwidth.interface].outgoing != 0 ) ?
    static_cast<float>(bandwidth.outgoing)/limits[bandwidth.interface].outgoing : 0.;

  if (fracIncoming > 1. ) fracIncoming = 1.;
  if (fracOutgoing > 1. ) fracOutgoing = 1.;

  int h(height()/2), w(width()), a(arrowWidth), t(textWidth); // in pixel

  // draw arrows
  if (showArrows and arrowWidth > 0) {
    if ( fracIncoming > 0. ) p.drawPixmap(1, 2  , arrowIncomingOn );
    else                     p.drawPixmap(1, 2  , arrowIncoming          );

    if ( fracOutgoing > 0. ) p.drawPixmap(1, 1+h, arrowOutgoingOn );
    else                     p.drawPixmap(1, 1+h, arrowOutgoing          );
  } else a = 0;

  // draw the text
  if (showText and textWidth > 0) {
    p.setPen( QPen(colorText) );
    QFont f = this->QWidget::font();
    f.setPixelSize(h-2);
    p.setFont( f );

    QString unit;
    switch (divisor) {
      case 128     : unit = "kBit/s"; break;
      case 131072  : unit = "MBit/s"; break;
      case 1024    : unit = "kB/s"  ; break;
      case 1048576 : unit = "MB/s"  ; break;
    }

    QString text;
    text = QString("%1 %2")
              .arg(bandwidth.incoming/divisor)
              .arg(unit);
    p.drawText( w-t+1,   2, t-2, h-4, AlignVCenter|AlignRight, text.ascii() );

    text = QString("%1 %2")
              .arg(bandwidth.outgoing/divisor)
              .arg(unit);
    p.drawText( w-t+1, h+2, t-2, h-4, AlignVCenter|AlignRight, text.ascii() );
  } else t = 0;

  // draw the bars
  p.setPen ( colorNormalIncoming );
  p.setBrush ( colorNormalIncoming );
  p.drawRect( a+3,   3, static_cast<int>( (w-a-t-6)*fracIncoming), h-6 );

  p.setPen ( colorNormalOutgoing );
  p.setBrush ( colorNormalOutgoing );
  p.drawRect( a+3, h+3, static_cast<int>( (w-a-t-6)*fracOutgoing), h-6 );

  //draw border around both bars
  p.setBrush( NoBrush );

  p.setPen( colorNormalIncoming);
  p.drawRect( a+1,   1, w-t-a-2, h-2);
  p.setPen( colorNormalOutgoing);
  p.drawRect( a+1, h+1, w-t-a-2, h-2);

  p.end();
}

void KBandwidthView::readInterfaceData(StateData& state)
{
  QFile nsf(QString("/proc/net/dev"));

  if(!nsf.open(IO_ReadOnly | IO_Translate))
    fprintf(stderr,"unable to open /proc/net/dev\n");

  state.time = QTime::currentTime();

  QTextStream stream(&nsf);
  ushort i = 0;
  while( !stream.atEnd() ) {
    QString line = stream.readLine();
    ++i;
    if (i<3) continue; // throw away header, consisting of first two lines
    // ex:  eth1:86136425   65106    2    2    0     0          0
    //       0  3897238   41154    0    0    0     0       0          0

    QString interface, values;
    interface = line.section(":",0,0).stripWhiteSpace();

    // skip the loop back device for readout
    if ( interface == "lo" ) continue;

    values    = line.section(":",1,1);

    QTextIStream istream(&values);
    int b; // dummy
    unsigned long incoming, outgoing;
    istream >> incoming >> b >> b >> b >> b >> b >> b >> b >> outgoing;
    if (incoming > 0) state[interface].incoming = incoming;
    if (outgoing > 0) state[interface].outgoing = outgoing;
  }
  nsf.close();
}

QStringList KBandwidthView::listInterfaces()
{
  QFile nsf(QString("/proc/net/dev"));

  if(!nsf.open(IO_ReadOnly | IO_Translate))
    fprintf(stderr,"unable to open /proc/net/dev\n");

  QTextStream stream(&nsf);
  ushort i = 0;
  QStringList list;
  while( !stream.atEnd() ) {
    QString line = stream.readLine();
    ++i;
    if (i<3) continue; // throw away header, consisting of first two lines
    // ex:  eth1:86136425   65106    2    2    0     0          0
    //       0  3897238   41154    0    0    0     0       0          0

    const QString interface = line.section(":",0,0).stripWhiteSpace();
    list.append(interface);
  }
  nsf.close();
  return list;
}

void KBandwidthView::calculateBandwidth()
{
  bandwidth.incoming = 0; bandwidth.outgoing = 0;

  StateData now;
  readInterfaceData(now);

  if (forceInterface.length()>0) bandwidth.interface = forceInterface;

  // use interface with biggest change or forced one
  for (QMap<QString, Bandwidth>::iterator it = now.begin();
    it != now.end(); ++it)
  {
    if ( not previous.contains(it.key()) or
         now[it.key()].incoming < previous[it.key()].incoming or
         now[it.key()].outgoing < previous[it.key()].outgoing or
         previous[it.key()].incoming == 0 or
         previous[it.key()].outgoing == 0 )
    continue;

    unsigned long temp_incoming
      = now[it.key()].incoming - previous[it.key()].incoming;
    unsigned long temp_outgoing
      = now[it.key()].outgoing - previous[it.key()].outgoing;

    const bool conditionA = forceInterface.length() > 0 and it.key() == forceInterface;
    const bool conditionB = forceInterface.length() == 0 and
      temp_incoming + temp_outgoing > bandwidth.incoming + bandwidth.outgoing;

    if ( conditionA or conditionB )
    {
      bandwidth.interface = it.key();
      bandwidth.incoming  = temp_incoming;
      bandwidth.outgoing  = temp_outgoing;
    }
  }

  const double deltaTime = previous.time.msecsTo(now.time)/1000.;

//   cerr << "\'" << forceInterface.utf8() << "\'" << endl;
//   cerr << bandwidth.incoming << " " << bandwidth.outgoing << " " << bandwidth.interface.utf8() << " " << deltaTime << endl;

  bandwidth.incoming
    = static_cast<unsigned long>(bandwidth.incoming/deltaTime);
  bandwidth.outgoing
    = static_cast<unsigned long>(bandwidth.outgoing/deltaTime);

//   cerr << "calc " << bandwidth.incoming << " " << bandwidth.outgoing << endl;

  previous = now;  // save current state
}
