/*
 *   NetstatDlg.cpp - Dialog for the netstat command
 * 
 *   part of knu: KDE network utilities
 *
 *   Copyright (C) 1999  John Corey
 *
 *   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.
 *
 *   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, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <kconfig.h>
#include <klocale.h>
#include <qbuttongroup.h>
#include <qheader.h>
#include <qmessagebox.h>

#include "NetstatDlg.h"
#include "NetstatDlg.moc"

// This is the unit used to separate widgets
#define SEPARATION 10

/*
 * Constructor
 */
NetstatDlg::NetstatDlg(QString commandName, 
		 QWidget* parent, const char* name)
  : CommandDlg(commandName, parent, name)
{
  KConfig *kc = kapp->config();

  strcpy(part_line, PART_DEFAULT);
  layout1 = new QBoxLayout(commandBinOK, 
			   QBoxLayout::TopToBottom, SEPARATION);
  CHECK_PTR(layout1);
  
  layout2 = new QBoxLayout(QBoxLayout::LeftToRight, SEPARATION);
  CHECK_PTR(layout2);
  layout1->addLayout(layout2, 0);
  
  // Frame for options
  frame1 = new QFrame(commandBinOK, "frame_1");
  CHECK_PTR(frame1);
  frame1->setFrameStyle(QFrame::Box | QFrame::Sunken);
  layout1->addWidget(frame1, 0);
  
  // Make the layout of CommandDlg
  // TODO: Find a good way to remove the QLineEdit since this command takes no input
  // -  inherit the Go button's clicked slot?
  layout2->addWidget(commandLbl1);
//  commandArgs->setText("This will be replaced...");
  commandArgs->setEnabled(FALSE);
  layout2->addWidget(commandArgs);
  layout2->addSpacing(2*SEPARATION);
  commandGoBtn->setEnabled(TRUE);
  isGoBtnEnabled = TRUE;
  layout2->addWidget(commandGoBtn);
  layout2->addWidget(commandStopBtn);
  
  layout3 = new QBoxLayout(frame1, QBoxLayout::LeftToRight, SEPARATION/2);
  CHECK_PTR(layout3);
  
  // Create QListView
  commandTextArea->hide();
  lv = new QListView(commandBinOK, "qlistview");
  CHECK_PTR(lv);
  if (style() == WindowsStyle) {
     lv->setFrameStyle(QFrame::WinPanel | QFrame::Sunken);
  } else {
     lv->setFrameStyle(QFrame::Panel | QFrame::Sunken);
  }
  lv->addColumn(i18n("Proto"));
  lv->addColumn(i18n("Local Address"), 7*fontMetrics().width("111."));
  lv->addColumn(i18n("Port"), fontMetrics().width("00000  "));
  lv->setColumnAlignment(2, AlignRight);
  lv->addColumn(i18n("Remote Address"), 7*fontMetrics().width("111."));
  lv->addColumn(i18n("Port"), fontMetrics().width("00000  "));
  lv->setColumnAlignment(4, AlignRight);
  lv->addColumn(i18n("State"), fontMetrics().width("ESTABLISHED "));
  lv->setAllColumnsShowFocus(TRUE);
  lv->setSorting(0);
  
  memset(lines, 0, 1024 * sizeof(void *));
  numberOfLines = 0;

  QHeader *h = lv->header();
  h->setResizeEnabled(TRUE);
//  h->setResizeEnabled, TRUE, 1);
  h->setMovingEnabled(FALSE);
  h->setClickEnabled(TRUE);

#if QT_VERSION >= 210
  setMinimumWidth(lv->sizeHint().width()+2*QApplication::style().scrollBarExtent().width()+5);
#else
  setMinimumWidth(lv->sizeHint().width()+2*SEPARATION+10);
#endif
  layout1->addWidget(lv, 10);

  // Layout of options
  layout3->addStretch(10);

  netstatCb3 = new QCheckBox(i18n("Mas&queraded connections"), frame1, "cb_2");
  netstatCb3->setChecked(FALSE);
  netstatCb3->adjustSize();
  netstatCb3->setFixedSize(netstatCb3->width(), 2*fontMetrics().height());
  layout3->addWidget(netstatCb3, 0);
  layout3->addStretch(10);

  netstatCb1 = new QCheckBox(i18n("Make host &name resolution"), frame1, "cb_1");
  netstatCb1->setChecked(FALSE);
  netstatCb1->adjustSize();
  netstatCb1->setFixedSize(netstatCb1->width(), 2*fontMetrics().height());
  layout3->addWidget(netstatCb1, 0);
  layout3->addStretch(10);
  
  netstatCb2 = new QCheckBox(i18n("Filter out &Localhost"), frame1, "cb_2");
  netstatCb2->setChecked(TRUE);
  netstatCb2->adjustSize();
  netstatCb2->setFixedSize(netstatCb2->width(), 2*fontMetrics().height());
  layout3->addWidget(netstatCb2, 0);
  layout3->addStretch(10);

  layout3->activate();
  
//  layout1->addWidget(commandTextArea, 10);
  layout1->activate();

  /*
   * Look at the command binary to see which widget to display
   */
  kc->setGroup(configGroupName);
  if (!kc->hasKey("path")) {
    // It's the first execution, 
    // so we have to search for the pathname
    kc->writeEntry("path", commandName);
    checkBinaryAndDisplayWidget();
    if (commandFound) {
      // All is OK : we can enable this tab.
      if (!kc->hasKey("enable")) {
	kc->writeEntry("enable", 1);
      }
    }
  } else {
    checkBinaryAndDisplayWidget();
  }
  
  // Commit changes in configfile (if needed)
  kc->sync();
}

/*
 * Destructor
 , fontMetrics().width("ESTABLISHED")*/
NetstatDlg::~NetstatDlg()
{
}

/**
 * build the command line from widgets
 */
bool
NetstatDlg::buildCommandLine(QString)
{
  QString s;
  KConfig *kc = kapp->config();
  
  kc->setGroup(configGroupName);
  s = kc->readEntry("path");
  if (s.isNull()) {
    return FALSE;
  } else {
    //debug("getExecutable = %s", (const char *)s);
    childProcess.clearArguments();
    childProcess.setExecutable(s);

    // Add arguments
    s = (kc->readEntry("arguments")).simplifyWhiteSpace();
    
    if (!s.isEmpty()) {
      while (s.contains(' ', FALSE) != 0) {
	int pos = s.find(' ', 0, FALSE);
	childProcess << s.left(pos);
	s = s.remove(0, pos+1);
      }
      childProcess << s;
    }
    

    if (!netstatCb1->isChecked()) {  // Make host name resolution
      childProcess << "-n";
    }
    if (netstatCb3->isChecked()) {  // Masqueraded connections
      childProcess << "-M";
    }
//    childProcess << (const char *)args;
    return TRUE;
  }
}

/**
 * Read the output of the command on a socket
 *
 * This is called by the main select loop in Qt (QSocketNotifier)
 */
void
NetstatDlg::slotCmdStdout(KProcess *, char *buffer, int buflen)
{
  char *p, *q, *line, buffer2[1025];
  
  if (buflen > 0) {
    memcpy(buffer2, buffer, buflen);
    if (buffer2[buflen] != 0)
       buffer2[buflen] = 0;
  }
  //  debug("stdout> %s", (char *)*receivedLine);

  // Split incoming data by line
  p = buffer2;
  while ( (p != 0) && (*p != 0) ) {
    char newline[1025];

    line = p;
    q = p;
    while ((*q != '\n') && (*q != 0)) { q++; }
    int part = 0;
    if (*q == 0) {  // End of buffer, no \n
//      debug(QString("This line is not complete - >%1<").arg(p));
      if (!strcmp(part_line, PART_DEFAULT)) strcpy(part_line, p);
      else strcat(part_line, p);
      part = 1;
    }
    *q = 0;
    if (!part) q++;
    p = q;

    if ( ( (*p == 0) && part ) ||  // end of input, with a partial line remaining
       (!strncmp(line, "Active", 6)) || (!strncmp(line, "Proto", 5)) || // for netstat
       (!strncmp(line, "IP mas", 6)) || (!strncmp(line, "prot", 4)) )   // for netstat -M
       continue;

    if (strcmp(part_line, PART_DEFAULT)) {  // An old part_line exists
       strcpy(newline, part_line);
       strcat(newline, line);
       strcpy(part_line, PART_DEFAULT);
       line = newline;
    }

    processLineOfOutput(QString(line));
  } // while
}

/**
 * clear the output (slot)
 */
void NetstatDlg::clearOutput() {
   int i;
   // Remove current lines in the QListView if needed
   for (i=numberOfLines-1; i>=0; i--) {
      delete lines[i];
      lines[i] = 0;
   }
   numberOfLines = 0;
}

void NetstatDlg::slotProcessDead(KProcess *p) {
   CommandDlg::slotProcessDead(p);
   if (strcmp(part_line, PART_DEFAULT)) {  // one line remaining
      processLineOfOutput(QString(part_line));
   }
}

void NetstatDlg::processLineOfOutput(QString line) {
    QString dummy;
    QString _proto;
    QString _local_addr, _local_port;
    QString _remote_addr, _remote_port;
    QString _state;

    if (netstatCb3->isChecked()) {
      _proto = line.left(line.find(" ")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(" ")).simplifyWhiteSpace();
//      debug(QString("MASQ - proto: %1").arg(_proto));

      dummy = line.left(line.find(" ")).simplifyWhiteSpace();  // Expire time
      line = line.right(line.length() - line.find(" ")).simplifyWhiteSpace();

      _local_addr = line.left(line.find(" ")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(" ") - 1).simplifyWhiteSpace();
//      debug(QString("MASQ - local addr: %1").arg(_local_addr));

      _remote_addr = line.left(line.find(" ")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(" ") - 1).simplifyWhiteSpace();
//      debug(QString("MASQ - remote addr: %1").arg(_remote_addr));

      _local_port = line.left(line.find(" ")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(" ") - 1).simplifyWhiteSpace();
//      debug(QString("MASQ - local port: %1").arg(_local_port));

      dummy = line.left(line.find(" ")).simplifyWhiteSpace();  // ->
      line = line.right(line.length() - line.find(" ")).simplifyWhiteSpace();

      _remote_port = line.left(line.find(" ")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(" ") - 1).simplifyWhiteSpace();
//      debug(QString("MASQ - remote port: %1").arg(_remote_port));

      _state = line.left(line.find(" ")).simplifyWhiteSpace();
      _state = _state.right(_state.length()-1);
      _state = _state.left(_state.length()-1);
//      debug(QString("MASQ - state: %1").arg(_state));

      // Change the heading for the last column
      lv->setColumnText(5, "Masq Port");
    } else {
      _proto = line.left(line.find(" ")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(" ")).simplifyWhiteSpace();
//      debug(QString("proto: %1").arg(_proto));

      dummy = line.left(line.find(" ")).simplifyWhiteSpace();  // Recv-Q
      line = line.right(line.length() - line.find(" ")).simplifyWhiteSpace();
      dummy = line.left(line.find(" ")).simplifyWhiteSpace();  // Send-Q
      line = line.right(line.length() - line.find(" ")).simplifyWhiteSpace();

      _local_addr = line.left(line.find(":")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(":") - 1).simplifyWhiteSpace();
      _local_port = line.left(line.find(" ")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(" ")).simplifyWhiteSpace();
//      debug(QString("local addr: %1:%2").arg(_local_addr).arg(_local_port));

      _remote_addr = line.left(line.find(":")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(":") - 1).simplifyWhiteSpace();
      _remote_port = line.left(line.find(" ")).simplifyWhiteSpace();
      line = line.right(line.length() - line.find(" ")).simplifyWhiteSpace();
//      debug(QString("remote addr: %1:%2").arg(_remote_addr).arg(_remote_port));
      _state = line.simplifyWhiteSpace();
//      debug(QString("state: %1\n").arg(_state));

      // Change the heading for the last column
      lv->setColumnText(5, "State");
    }

    bool displayIt = true;
    if (netstatCb2->isChecked())
      if ( (_local_addr == "localhost") || (_local_addr == "127.0.0.1") )
         displayIt = false;

    if (displayIt) {
      int i = numberOfLines;
      lines[i] = new QListViewItem(lv);
      CHECK_PTR(lines[i]);
      lines[i]->setText(0, _proto);
      lines[i]->setText(1, _local_addr);
      lines[i]->setText(2, _local_port);
      lines[i]->setText(3, _remote_addr);
      lines[i]->setText(4, _remote_port);
      lines[i]->setText(5, _state);
      numberOfLines++;
    }
}

/*
 * this is called from the Go button
 */
void
NetstatDlg::slotLauchCommand()
{
  clearOutput();

  //QString str;
  QString args;
 
  if (childProcess.isRunning()) {
    return;
  }
 
  args = commandArgs->text();
  //CB args = commandArgs->currentText();//CB
//  if ((strlen(args) == 0) && !emptyTextAllowed) {
    // nothing to do (this should not be possible)
//  } else {
 
    // Check the input
    if (!checkInput(&args)) {
      //warning("input not valid");
      commandArgs->selectAll();
      KApplication::beep();
      return;
    }
 
    // Install the "Stop" button, and hide "Go!"
    commandGoBtn->setEnabled(FALSE);
    commandStopBtn->setEnabled(TRUE);
 
    // Install waitCursor
    installWaitCursor();
 
    // separate commands with CR/LF
    if (commandTextArea->numLines() > 1) {
      int line;
      line = QMAX(commandTextArea->numLines()-2, 0);
      if (commandTextArea->textLine(line) != QString::null) {
        commandTextArea->append("");
      }
    }
 
    //  Process creation
    if (!buildCommandLine(args)) {
      QString errorString;
      debug("buildCommandLine = FALSE");

      // removeAmpersand() from CommandDlg.cpp
      QString s2(this->name());
      s2.replace(QRegExp("&"), "");
      // Same message in MtrDlg.cpp
      errorString = i18n("\nYou have a problem in the\n"
                            "configuration file of '%1'.\n"
                            "In the [%2] group,\nI can't "
                            "find a valid \"path=\" entry.\n\n"
                            "Please use Edit->Preferences... menu\n"
                            "to configure it again.\n")
                          .arg(kapp->name())
                          .arg(s2);
      QMessageBox::warning(this, i18n("Error in pathname"),
                           errorString, i18n("OK"));
      slotProcessDead(NULL);
      return;
    }
 
    connect(&childProcess, SIGNAL(processExited(KProcess *)),
            SLOT(slotProcessDead(KProcess *)));
 
    connect(&childProcess, SIGNAL(receivedStdout(KProcess *, char *, int)),
            this, SLOT(slotCmdStdout(KProcess *, char *, int)));
    connect(&childProcess, SIGNAL(receivedStderr(KProcess *, char *, int)),
            this, SLOT(slotCmdStdout(KProcess *, char *, int)));
 
    // Test pour mtr: pb stdin?
//    if (!childProcess.start(KProcess::NotifyOnExit, KProcess::AllOutput)) {
    if (!childProcess.start(KProcess::NotifyOnExit, KProcess::All)) {
      // Process not started
      debug("Process not started");
      slotProcessDead(NULL);
      return;
    }
//  }
}

/* ******************************************************************** */

/**
 * make a new config object
 *
 * @param parent parent widget
 * @param name name of the widget
 */
NetstatCfgDlg::NetstatCfgDlg(const char *tcs,
		       QWidget *parent, const char *name)
  : CommandCfgDlg(tcs, parent, name)
{
}

/*
 * Destructor
 */
NetstatCfgDlg::~NetstatCfgDlg()
{
}


/**
 * make a new config widget
 *
 * @param parent parent widget
 * @param makeLayouts name of the widget
 */
QWidget *
NetstatCfgDlg::makeWidget(QWidget *parent, bool makeLayouts)
{
#define SET_ADJUSTED_FIXED_SIZE(_wdgt) \
                  _wdgt->setFixedSize(_wdgt->sizeHint())

  //debug("NetstatCfgDlg::makeWidget");
  (void)CommandCfgDlg::makeWidget(parent, FALSE);

  // Widgets
  cfgBG = new QButtonGroup(cfgWidget);
  CHECK_PTR(cfgBG);

#if 0
  cfgPingBtn = new QRadioButton(i18n("hos&t"), cfgBG);
  CHECK_PTR(cfgPingBtn);
  SET_ADJUSTED_FIXED_SIZE(cfgPingBtn);

  cfgNslookupBtn = new QRadioButton(i18n("ns&lookup"), cfgBG);
  CHECK_PTR(cfgNslookupBtn);
  SET_ADJUSTED_FIXED_SIZE(cfgNslookupBtn);
#endif

  if (makeLayouts) {
    cfgLayoutTB = new QBoxLayout(cfgWidget, QBoxLayout::TopToBottom, 10);
    CHECK_PTR(cfgLayoutTB);
    
    if (cfgWarning != 0) {
      cfgLayoutTB->addLayout(cfgWarningLayout);
      cfgWarningLayout->addStretch(10);
      cfgWarningLayout->addWidget(cfgWarningPm, 0);
      cfgWarningLayout->addWidget(cfgWarningLbl, 0);
      cfgWarningLayout->addStretch(10);
    }
    cfgLayoutTB->addWidget(cfgBinGB);
    
    cfgLayoutGB = new QGridLayout(cfgBinGB, 3, 3, 10);
    CHECK_PTR(cfgLayoutGB);
    
    cfgLayoutGB->addRowSpacing(0, 0);
    cfgLayoutGB->addWidget(cfgBinNameLbl, 1, 0, AlignRight|AlignVCenter);
    cfgLayoutGB->addWidget(cfgBinNameLE, 1, 1);
    cfgLayoutGB->addWidget(cfgBinNameBrowse, 1, 2);
    cfgLayoutGB->addWidget(cfgBinArgLbl, 2, 0, AlignRight|AlignVCenter);
    cfgLayoutGB->addWidget(cfgBinArgLE, 2, 1);
    cfgLayoutGB->setColStretch(0, 0);
    cfgLayoutGB->setColStretch(1, 10);
    cfgLayoutGB->activate();

#if 0
    // Our widget
    cfgLayout2 = new QBoxLayout(cfgBG, QBoxLayout::LeftToRight, 10);
    CHECK_PTR(cfgLayout2);
    
    cfgLayout2->addStretch(10);
    cfgLayout2->addWidget(cfgPingBtn);
    cfgLayout2->addStretch(10);
    cfgLayout2->addWidget(cfgNslookupBtn);
    cfgLayout2->addStretch(10);
    cfgLayout2->activate();
#endif

    cfgLayoutTB->addWidget(cfgBG);

    cfgLayoutTB->addStretch(10);

    cfgWidget->adjustSize();
    cfgLayoutTB->activate();
    cfgWidget->adjustSize();
    cfgWidget->setMinimumSize(cfgWidget->size());
    
    cfgLayoutTB->activate();
  }
  readConfig();
  return (cfgWidget);
#undef SET_ADJUSTED_FIXED_SIZE
}

/**
 * delete the config widget
 */
void
NetstatCfgDlg::deleteConfigWidget()
{
  //  debug("PingCfgDlg::deleteCondigWidget");
  
  delete cfgLayoutTB;
  delete cfgLayoutGB;
//  delete cfgLayout2;
  delete cfgBG;
//  delete cfgPingBtn;
//  delete cfgNslookupBtn;
}

/**
 * commit changes to the configfile
 * 
 * @return if the change have been done
 */
bool
NetstatCfgDlg::commitChanges()
{ 
#if 0
  KConfig *kc = kapp->config();

  (void)CommandCfgDlg::commitChanges();
if (cfgNslookupBtn->isChecked()) {
    kc->writeEntry("binaryType", "nslookup");
  } else {
    kc->writeEntry("binaryType", "ping");
  }
#endif
  return(TRUE);
}

/**
 * cancel changes to the configfile
 */
void
NetstatCfgDlg::cancelChanges()
{
  // Nothing to do...
}

/**
 * read the configfile
 */
void
NetstatCfgDlg::readConfig()
{
  QString s;
  KConfig *kc = kapp->config();

  kc->setGroup(configGroupName);

#if 0
  if (kc->hasKey("binaryType")) {
    s = kc->readEntry("binaryType");
    if (!stricmp(s, "nslookup")) {
      cfgNslookupBtn->setChecked(TRUE);
    } else {
      cfgPingBtn->setChecked(TRUE);
    }
  } else {
    cfgPingBtn->setChecked(TRUE);
  }
#endif
}

