// -*- C++ -*-

//
//  klpq
//
//  Copyright (C) 1997 Christoph Neerfeld
//  email:  Christoph.Neerfeld@home.ivm.de or chris@kde.org
//
//  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.
//

extern "C" {
#include <unistd.h>
};

#include <qcheckbox.h>
#include <qcombobox.h>
#include <qframe.h>
#include <qgroupbox.h>
#include <qlabel.h>
#include <qlistbox.h>
#include <qpushbutton.h>
#include <qkeycode.h>
#include <qcollection.h>
#include <qfileinfo.h>
#include <qdir.h>
#include <qcursor.h>
#include <qevent.h>
#include <qdragobject.h>
#include <qlayout.h>
#include <qaccel.h>

#include <klistview.h>
#include <kglobal.h>
#include <kdebug.h>
#include <kprocess.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kconfig.h>
#include <kmenubar.h>
#include <kstdaccel.h>
#include <kio/netaccess.h>

#include "klpq.h"
#include "klpq.moc"
#include "queueview.h"
#include "klpqspooler.h"
#include "FirstStart.h"

Klpq::Klpq( const char *name )
  : KMainWindow( 0, name )
{
  update_delay = 0; // means no auto update at all
  KConfig *config = KGlobal::config();
  config->setGroup( QString::fromLatin1("klpq") );
  if( config->hasKey( QString::fromLatin1("updateDelay") ) )
    update_delay = config->readNumEntry( QString::fromLatin1("updateDelay") );

  conf_auto = new ConfAutoUpdate( topLevelWidget(), "update" );
  conf_auto->setFrequency(update_delay);

  conf_spooler = new SpoolerConfig( topLevelWidget(), "spooler" );

  f_main = new QFrame( this, "Frame_1" );

  f_top = new QGroupBox(i18n("Printer Configuration"), f_main, "Frame_2" );

  QVBoxLayout *vlayout = new QVBoxLayout(f_top, KDialog::marginHint(),
					 KDialog::spacingHint());
  vlayout->addSpacing(f_top->fontMetrics().lineSpacing());

  QHBoxLayout *hlayout = new QHBoxLayout;
  vlayout->addLayout(hlayout);

  l_printer = new QLabel( f_top, "Label_1" );
  l_printer->setText( i18n("Printer name:") );
  hlayout->addWidget( l_printer, 0 );

  cb_printer = new QComboBox( f_top, "ComboBox_1" );
  hlayout->addWidget( cb_printer, 3, AlignLeft );

  c_queuing = new QCheckBox( f_top, "CheckBox_1" );
  c_queuing->setText( i18n("Queuing") );
  hlayout->addWidget( c_queuing, 1 );

  c_printing = new QCheckBox( f_top, "CheckBox_2" );
  c_printing->setText( i18n("Printing") );
  hlayout->addWidget( c_printing, 1 );

  lb_list = new KListView(f_main);
  lb_list->setSelectionMode( QListView::Extended );

  lb_list->addColumn(i18n("Rank"), 75);
  lb_list->setColumnAlignment( 0, AlignRight );
  lb_list->addColumn(i18n("Owner"), 150);
  lb_list->addColumn(i18n("Job"), 75);
  lb_list->setColumnAlignment( 2, AlignRight );
  lb_list->addColumn(i18n("Files"), 150);
  lb_list->addColumn(i18n("Size"), 75);
  lb_list->setColumnAlignment( 4, AlignRight );

  //  connect( lb_list, SIGNAL(rightButtonClicked()), this, SLOT(popupMenu()) );

  top2bottom = new QVBoxLayout( f_main, KDialog::marginHint(),
				KDialog::spacingHint());
  top2bottom->addWidget( f_top );
  top2bottom->addWidget( lb_list, 1 );

  hlayout = new QHBoxLayout;
  top2bottom->addLayout(hlayout);

  vlayout = new QVBoxLayout;
  hlayout->addLayout(vlayout);

  b_remove = new QPushButton( f_main, "PushButton_3" );
  b_remove->setText( i18n("Remove") );
  vlayout->addWidget(b_remove);

  b_make_top = new QPushButton( f_main, "PushButton_4" );
  b_make_top->setText( i18n("Make Top") );
  vlayout->addWidget(b_make_top);

  lb_status = new QListBox( f_main, "ListBox_2" );
  lb_status->setMinimumSize( 312, 44 );
  hlayout->addWidget(lb_status);

  vlayout = new QVBoxLayout;
  hlayout->addLayout(vlayout);

  b_auto = new QPushButton( f_main, "PushButton_6" );
  b_auto->setText( i18n("Auto") );
  b_auto->setToggleButton( TRUE );
  vlayout->addWidget(b_auto);

  b_update = new QPushButton( f_main, "PushButton_1" );
  b_update->setText( i18n("Update") );
  vlayout->addWidget( b_update );

  QPopupMenu *file = new QPopupMenu;
  file->insertItem(i18n("&Quit"), qApp, SLOT(quit()), KStdAccel::key(KStdAccel::Quit) );

  QPopupMenu *conf_menu = new QPopupMenu;
  conf_menu->insertItem(i18n("&Update frequency..."),
			this, SLOT(configureAuto()));
  conf_menu->insertItem(i18n("&Spooler..."), this,
			SLOT(configureSpooler()) );

  menuBar()->insertItem( i18n("&File"), file );
  menuBar()->insertItem( i18n("&Options"), conf_menu );
  menuBar()->insertSeparator();
  menuBar()->insertItem( i18n("&Help"),	helpMenu( ) );

  connect( cb_printer, SIGNAL(activated(int)), SLOT(printerSelect(int)));
  connect( c_queuing, SIGNAL(clicked()), SLOT(queuing()) );
  connect( c_printing, SIGNAL(clicked()), SLOT(printing()) );
  connect( b_update, SIGNAL(clicked()), SLOT(update()) );
  connect( b_remove, SIGNAL(clicked()), SLOT(remove()) );
  connect( b_make_top, SIGNAL(clicked()), SLOT(makeTop()) );
  connect( b_auto, SIGNAL(clicked()), SLOT(setAuto()) );

  popup_menu = new QPopupMenu;
  popup_menu->insertItem( i18n("Remove"), this, SLOT(remove()) );
  popup_menu->insertItem( i18n("Make Top"), this, SLOT(makeTop()) );

  int key_id;
  accel = new QAccel(this);
  accel->connectItem( accel->insertItem( Key_U ), this, SLOT(update()) );
  accel->connectItem( accel->insertItem( Key_Delete ), this, SLOT(remove()) );
  accel->connectItem( accel->insertItem( Key_T ), this, SLOT(makeTop()) );
  key_id = accel->insertItem( Key_A );
  accel->connectItem( key_id, b_auto, SLOT(toggle()) );
  accel->connectItem( key_id, this, SLOT(setAuto()) );
  accel->connectItem( accel->insertItem( Key_Left ), this, SLOT(prevPrinter()) );
  accel->connectItem( accel->insertItem( Key_Right ), this, SLOT(nextPrinter()) );

  setCentralWidget(f_main);

  // set up subprocess
  FirstStart spooler_dialog;
  QString temp = config->readEntry( QString::fromLatin1("Spooler") );
  if ( temp == QString::fromLatin1("BSD") )
    spooler = new SpoolerBsd;
  else if( temp == QString::fromLatin1("PPR") )
    spooler = new SpoolerPpr;
  else if ( temp == QString::fromLatin1("LPRNG") )
    spooler = new SpoolerLprng;
  else
    {
      spooler_dialog.exec();
      temp = spooler_dialog.getSpooler();
      config->writeEntry(QString::fromLatin1("Spooler"), temp);
      if( temp == QString::fromLatin1("PPR") )
	spooler = new SpoolerPpr;
      else if ( temp == QString::fromLatin1("LPRNG") )
	spooler = new SpoolerLprng;
      else
	spooler = new SpoolerBsd;
    }
  lpc = new KProcess;
  lpc_com = NotRunning;
  connect( lpc, SIGNAL(receivedStdout(KProcess *, char *, int)),
	   this, SLOT(recvLpc(KProcess *, char *, int)) );
  connect( lpc, SIGNAL(processExited(KProcess *)),
	   this, SLOT(exitedLpc(KProcess *)) );

  lpq = new KProcess;
  lpq_running = FALSE;
  connect( lpq, SIGNAL(receivedStdout(KProcess *, char *, int)),
	   this, SLOT(recvLpq(KProcess *, char *, int)) );
  connect( lpq, SIGNAL(processExited(KProcess *)),
	   this, SLOT(exitedLpq(KProcess *)) );

  // activate DnD
  setAcceptDrops(true);

  int width, height;
  config->setGroup(QString::fromLatin1("klpq"));
  width = config->readNumEntry(QString::fromLatin1("Width"), sizeHint().width());
  height = config->readNumEntry(QString::fromLatin1("Height"), sizeHint().height());
  resize( width, height );

  b_auto->setOn( config->readBoolEntry(QString::fromLatin1("autoOn"), TRUE) );
  setAuto();
}

Klpq::~Klpq()
{
  delete conf_auto;
  delete conf_spooler;

  delete lpc;
  delete lpq;

  KConfig &config = *KGlobal::config();
  config.setGroup(QString::fromLatin1("klpq"));
  config.writeEntry(QString::fromLatin1("Width"), width());
  config.writeEntry(QString::fromLatin1("Height"), height());
  config.writeEntry(QString::fromLatin1("updateDelay"), update_delay);
  config.writeEntry(QString::fromLatin1("autoOn"), b_auto->isOn() );
  config.writeEntry(QString::fromLatin1("lastQueue"),
		    cb_printer->currentText() );
  config.sync();
}

void Klpq::setLastPrinterCurrent()
{
  if( cb_printer->count() )
    cb_printer->setCurrentItem( cb_printer->count() - 1 );
}

void Klpq::update()
{
  if( !isEnabled() )
    return;
  lpq_in_buffer = QString::null;
  lpq->clearArguments();

  spooler->updateCommand( lpq, cb_printer->text( cb_printer->currentItem() ) );
  if( (lpq_running = lpq->start( KProcess::NotifyOnExit, KProcess::Stdout )) )
    disable();
}

void Klpq::queuing()
{
  if( !isEnabled() )
    {
      if( c_queuing->isChecked() )
	c_queuing->setChecked(FALSE);
      else
	c_queuing->setChecked(TRUE);
      return;
    }
  lpc->clearArguments();
  spooler->setQueuingCommand( lpc, cb_printer->text( cb_printer->currentItem() ),
			      c_queuing->isChecked() );
  if( lpc->start( KProcess::NotifyOnExit, KProcess::Stdout ) )
    {
      lpc_com = Queuing;
      disable();
    }
}

void Klpq::printing()
{
  if( !isEnabled() )
    {
      if( c_printing->isChecked() )
	c_printing->setChecked(FALSE);
      else
	c_printing->setChecked(TRUE);
      return;
    }
  lpc->clearArguments();
  spooler->setPrintingCommand( lpc, cb_printer->text( cb_printer->currentItem() ),
			       c_printing->isChecked() );
  if( lpc->start( KProcess::NotifyOnExit, KProcess::Stdout ) )
    {
      lpc_com = Printing;
      disable();
    }
}

void Klpq::remove()
{
  QList<int> list;

  QListViewItem *it = lb_list->firstChild();
  for (; it; it = it->itemBelow() )
    if ( it->isSelected() )
      list.append( new int( ((MyLVI *)it)->jobId() ) );

  if (list.isEmpty()) return;

  QString ask;
  if (list.count() == 1)
    ask = i18n("Do you really want to remove job No %1?")
    .arg(KGlobal::locale()->formatNumber(*list.first(), 0));
  else
    ask = i18n("Do you really want to remove the marked jobs?");

  if( KMessageBox::questionYesNo(this, ask) == KMessageBox::Yes )
    {
      for ( QListIterator<int> it(list); it.current(); ++it )
	{
	  KProcess proc;
	  QStringList list = spooler->removeCommand
	    ( cb_printer->text( cb_printer->currentItem() ),
	      QString::number( *it.current() ) );
	  for ( QStringList::Iterator it = list.begin();
		it != list.end();
		++it )
	    proc << *it;

	  proc.start(KProcess::Block);
	}

      update();
    }
}

void Klpq::makeTop()
{
  QList<int> list;

  QListViewItem *it = lb_list->firstChild();
  for (; it; it = it->itemBelow() )
    if ( it->isSelected() )
      list.append( new int( ((MyLVI *)it)->jobId() ) );

  if (list.isEmpty()) return;

  QString ask;
  if (list.count() == 1)
    ask = i18n("Do you really want to move job No %1\nto top of queue?")
    .arg(KGlobal::locale()->formatNumber(*list.first(), 0));
  else
    ask = i18n("Do you really want to move the marked\njobs to top of queue?");

  if( KMessageBox::questionYesNo(this, ask) == KMessageBox::Yes)
    {
      lpc->clearArguments();
      spooler->makeTopCommand( lpc, cb_printer->text(cb_printer->currentItem()), list );

      if( lpc->start( KProcess::NotifyOnExit, KProcess::Stdout ) )
	{
	  lpc_com = MakeTop;
	  disable();
	}
    }
}

void Klpq::setAuto()
{
  if( update_delay == 0 )
    {
      b_auto->setOn( FALSE );
      b_auto->setEnabled(FALSE);
      return;
    } else
      b_auto->setEnabled(TRUE);

  if( b_auto->isOn() )
    {
      if( cb_printer->count() )
	update();
      QTimer::singleShot(update_delay * 1000, this, SLOT(setAuto()) );
    }
}


void Klpq::prevPrinter()
{
  if( !isEnabled() )
    return;
  int cur = cb_printer->currentItem();
  if( cur == 0 )
    return;
  cb_printer->setCurrentItem( cur - 1 );
  update();
}

void Klpq::nextPrinter()
{
  if( !isEnabled() )
    return;
  int cur = cb_printer->currentItem();
  if( ++cur == cb_printer->count() )
    return;
  cb_printer->setCurrentItem( cur );
  update();
}

void Klpq::configureAuto()
{
  if( conf_auto->exec() ) {
    update_delay = conf_auto->getFrequency();
    setAuto();
  } else
    conf_auto->setFrequency(update_delay);
}

void Klpq::configureSpooler()
{
  KConfig &config = *KGlobal::config();
  config.setGroup(QString::fromLatin1("klpq"));
  conf_spooler->initConfig( config.readEntry(QString::fromLatin1("Spooler"),
					     QString::fromLatin1("BSD")) );
  if( conf_spooler->exec() )
  {
    QString spoolerName = conf_spooler->saveConfig();
    delete spooler;
    if( spoolerName == QString::fromLatin1("BSD") )
      spooler = new SpoolerBsd;
    else if( spoolerName == QString::fromLatin1("PPR") )
      spooler = new SpoolerPpr;
    else if ( spoolerName == QString::fromLatin1("LPRNG") )
      spooler = new SpoolerLprng;
  }
}

void Klpq::enable()
{
  QApplication::restoreOverrideCursor();
}

void Klpq::disable()
{
  QApplication::setOverrideCursor( waitCursor );
}

void Klpq::updateList()
{
  QString temp;
  lb_list->clear();
  lb_status->clear();
  spooler->parseUpdate( lb_list, lb_status, lpq_in_buffer );

  int jobs = lb_list->childCount();
  if( jobs != 0 )
    lb_list->setSelected( lb_list->firstChild(), TRUE );

  b_remove->setEnabled( jobs != 0 );
  b_make_top->setEnabled( jobs != 0 );

  // --- exec lpc
  if( lpc_com == NotRunning )
    {
      lpc->clearArguments();
      spooler->statusCommand( lpc, cb_printer->text( cb_printer->currentItem() ) );
      if( lpc->start( KProcess::NotifyOnExit, KProcess::Stdout ) )
	lpc_com = Status;
    }
}

void Klpq::recvLpq( KProcess *, char *buffer, int len )
{
  if( !lpq_running )
    {
      kdDebug() << "lpq not running" << endl;
      return;
    }
  int i;
  for( i = 0; i < len; i++ )
    {
      lpq_in_buffer += *(buffer+i);
    }
}

void Klpq::recvLpc( KProcess *, char *buffer, int len )
{
  unsigned char c;
  int j = 0;
  QString temp;
  switch(lpc_com) {
  case NotRunning:
    kdDebug() << "lpc not running" << endl;
    return;
  case Status:
    spooler->parseStatus( buffer, len, c_queuing, c_printing );
    break;
  case Queuing:
    if( spooler->parseQueuing( buffer, len ) < 0 )
      KMessageBox::error( this, i18n("This is a privileged command\n"
				     "and you do not have permission to execute it."),
				i18n("Permission denied"));
    break;
  case Printing:
    if( spooler->parsePrinting( buffer, len) < 0 )
      KMessageBox::error( this, i18n("This is a privileged command\n"
				     "and you do not have permission to execute it."),
				i18n("Permission denied"));
    break;
  case MakeTop:
    while( (c = *(buffer+j)) && j < len )
      {
	j++;
	if ( c >= 128) break;
	if ( c != '\n' )
	  temp += c;
	else
	  {
	    if(temp.contains(QString::fromLatin1("Privileged")) ||
	       temp.contains(QString::fromLatin1("permission")) )
	      {
		KMessageBox::error( this,
				      i18n("This is a privileged command\n"
					   "and you do not have permission to execute it."),
				      i18n("Permission denied"));
		return;
	      }
	    temp = "";
	  }
      }
    break;
  };
}

void Klpq::exitedLpc(KProcess *)
{
  if( lpc_com == Status )
    {
      lpc_com = NotRunning;
      enable();
      return;
    }
  lpc_com = NotRunning;
  enable();
  update();
}


void Klpq::dragEnterEvent(QDragEnterEvent* event)
{
  event->accept(QUriDrag::canDecode(event));
}


void Klpq::dropEvent(QDropEvent* event)
{
  QStrList urls;

  if(!QUriDrag::decode(event, urls))
    return;

  bool err = false;
  for ( QStrListIterator it(urls); it.current(); ++it )
  {
    if (!printURL(*it, cb_printer->text( cb_printer->currentItem() )))
      err = true;
    update();
  }

  if (err) messageNotFound();
}

void Klpq::messageNotFound()
{
    KMessageBox::error(0, i18n("<qt>Could not print some of the files. "
				  "Please make sure you have access to all "
				  "files and that they are not "
				  "directories.</qt>"));
}

bool Klpq::printURL(const KURL &url, const QString &printer)
{
  QString target;
  KIO::NetAccess::download(url, target);

  KProcess proc;
  proc << QString::fromLatin1("lpr");
  if (!printer.isEmpty())
  {
    proc << QString::fromLatin1("-P");
    proc << printer;
  }
  proc << target;
  proc.start(KProcess::Block);

  KIO::NetAccess::removeTempFile(target);

  return proc.normalExit() && proc.exitStatus() == 0;
}

void Klpq::popupMenu()
{
  popup_menu->popup(QCursor::pos());
}
