/*
  This file is part of KHelpcenter.

  Copyright (C) 2002 Cornelius Schumacher <schumacher@kde.org>

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public
  License version 2 as published by the Free Software Foundation.

  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; see the file COPYING.  If not, write to
  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  Boston, MA 02110-1301, USA.
*/

#include "kcmhelpcenter.h"

#include "htmlsearchconfig.h"
#include "docmetainfo.h"
#include "prefs.h"
#include "searchhandler.h"
#include "searchengine.h"

#include <tdeconfig.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tdeglobal.h>
#include <tdeaboutdata.h>
#include <kdialog.h>
#include <tdestandarddirs.h>
#include <tdeprocess.h>
#include <tdeapplication.h>
#include <dcopclient.h>
#include <tdetempfile.h>
#include <kurlrequester.h>
#include <tdemessagebox.h>
#include <tdelistview.h>
#include <klineedit.h>
#include <tqlayout.h>
#include <tqpushbutton.h>
#include <tqdir.h>
#include <tqtabwidget.h>
#include <tqprogressbar.h>
#include <tqfile.h>
#include <tqlabel.h>
#include <tqvbox.h>
#include <tqtextedit.h>
#include <tqregexp.h>

#include <unistd.h>
#include <sys/types.h>

using namespace KHC;

IndexDirDialog::IndexDirDialog( TQWidget *parent )
  : KDialogBase( parent, 0, true, i18n("Change Index Folder"), Ok | Cancel )
{
  TQFrame *topFrame = makeMainWidget();

  TQBoxLayout *urlLayout = new TQHBoxLayout( topFrame );

  TQLabel *label = new TQLabel( i18n("Index folder:"), topFrame );
  urlLayout->addWidget( label );

  mIndexUrlRequester = new KURLRequester( topFrame );
  mIndexUrlRequester->setMode( KFile::Directory | KFile::ExistingOnly |
                               KFile::LocalOnly );
  urlLayout->addWidget( mIndexUrlRequester );

  mIndexUrlRequester->setURL( Prefs::indexDirectory() );
  connect(mIndexUrlRequester->lineEdit(),TQ_SIGNAL(textChanged ( const TQString & )), this, TQ_SLOT(slotUrlChanged( const TQString &)));
  slotUrlChanged( mIndexUrlRequester->lineEdit()->text() );
}

void IndexDirDialog::slotUrlChanged( const TQString &_url )
{
  enableButtonOK( !_url.isEmpty() );
}
  

void IndexDirDialog::slotOk()
{
  Prefs::setIndexDirectory( mIndexUrlRequester->url() );
  accept();
}


IndexProgressDialog::IndexProgressDialog( TQWidget *parent )
  : KDialog( parent, "IndexProgressDialog", true ),
    mFinished( true )
{
  setCaption( i18n("Build Search Indices") );

  TQBoxLayout *topLayout = new TQVBoxLayout( this );
  topLayout->setMargin( marginHint() );
  topLayout->setSpacing( spacingHint() );

  mLabel = new TQLabel( this );
  mLabel->setAlignment( AlignHCenter );
  topLayout->addWidget( mLabel );

  mProgressBar = new TQProgressBar( this );
  topLayout->addWidget( mProgressBar );

  mLogLabel = new TQLabel( i18n("Index creation log:"), this );
  topLayout->addWidget( mLogLabel );

  mLogView = new TQTextEdit( this );
  mLogView->setTextFormat( LogText );
  mLogView->setMinimumHeight( 200 );
  topLayout->addWidget( mLogView, 1 );

  TQBoxLayout *buttonLayout = new TQHBoxLayout( topLayout );

  buttonLayout->addStretch( 1 );

  mDetailsButton = new TQPushButton( this );
  connect( mDetailsButton, TQ_SIGNAL( clicked() ), TQ_SLOT( toggleDetails() ) );
  buttonLayout->addWidget( mDetailsButton );

  hideDetails();

  mEndButton = new TQPushButton( this );
  connect( mEndButton, TQ_SIGNAL( clicked() ), TQ_SLOT( slotEnd() ) );
  buttonLayout->addWidget( mEndButton );

  setFinished( false );
}

IndexProgressDialog::~IndexProgressDialog()
{
  if ( !mLogView->isHidden() ) {
    TDEConfig *cfg = TDEGlobal::config();
    cfg->setGroup( "indexprogressdialog" );
    cfg->writeEntry( "size", size() );
  }
}

void IndexProgressDialog::setTotalSteps( int steps )
{
  mProgressBar->setTotalSteps( steps );
  mProgressBar->setProgress( 0 );
  setFinished( false );
  mLogView->clear();
}

void IndexProgressDialog::advanceProgress()
{
  mProgressBar->setProgress( mProgressBar->progress() + 1 );
}

void IndexProgressDialog::setLabelText( const TQString &text )
{
  mLabel->setText( text );
}

void IndexProgressDialog::setMinimumLabelWidth( int width )
{
  mLabel->setMinimumWidth( width );
}

void IndexProgressDialog::setFinished( bool finished )
{
  if ( finished == mFinished ) return;

  mFinished = finished;

  if ( mFinished ) {
    mEndButton->setText( i18n("Close") );
    mLabel->setText( i18n("Index creation finished.") );
  } else {
    mEndButton->setText( i18n("Stop") );
  }
}

void IndexProgressDialog::appendLog( const TQString &text )
{
  mLogView->append( text );
}

void IndexProgressDialog::slotEnd()
{
  if ( mFinished ) {
    emit closed();
    accept();
  } else {
    emit cancelled();
    reject();
  }
}

void IndexProgressDialog::toggleDetails()
{
  TDEConfig *cfg = TDEGlobal::config();
  cfg->setGroup( "indexprogressdialog" );
  if ( mLogView->isHidden() ) {
    mLogLabel->show();
    mLogView->show();
    mDetailsButton->setText( i18n("Details <<") );
    TQSize size = cfg->readSizeEntry( "size" );
    if ( !size.isEmpty() ) resize( size );
  } else {
    cfg->writeEntry( "size", size() );
    hideDetails();
  }
}

void IndexProgressDialog::hideDetails()
{
  mLogLabel->hide();
  mLogView->hide();
  mDetailsButton->setText( i18n("Details >>") );
  layout()->activate();
  adjustSize();
}


KCMHelpCenter::KCMHelpCenter( KHC::SearchEngine *engine, TQWidget *parent,
  const char *name)
  : DCOPObject( "kcmhelpcenter" ),
    KDialogBase( parent, name, false, i18n("Build Search Index"),
      Ok | Cancel, Ok, true ),
    mEngine( engine ), mProgressDialog( 0 ), mCurrentEntry( 0 ), mCmdFile( 0 ),
    mProcess( 0 ), mIsClosing( false ), mRunAsRoot( false )
{
  TQWidget *widget = makeMainWidget();

  setupMainWidget( widget );

  setButtonOK( i18n("Build Index") );

  mConfig = TDEGlobal::config();

  DocMetaInfo::self()->scanMetaInfo();

  load();

  bool success = tdeApp->dcopClient()->connectDCOPSignal( "khc_indexbuilder",
      0, "buildIndexProgress()", "kcmhelpcenter",
      "slotIndexProgress()", false );
  if ( !success ) kdError() << "connect DCOP signal failed" << endl;

  success = tdeApp->dcopClient()->connectDCOPSignal( "khc_indexbuilder",
      0, "buildIndexError(TQString)", "kcmhelpcenter",
      "slotIndexError(TQString)", false );
  if ( !success ) kdError() << "connect DCOP signal failed" << endl;

  resize( configDialogSize( "IndexDialog" ) );
}

KCMHelpCenter::~KCMHelpCenter()
{
  saveDialogSize( "IndexDialog" );
}

void KCMHelpCenter::setupMainWidget( TQWidget *parent )
{
  TQVBoxLayout *topLayout = new TQVBoxLayout( parent );
  topLayout->setSpacing( KDialog::spacingHint() );

  TQString helpText =
    i18n("To be able to search a document, there needs to exist a search\n"
         "index. The status column of the list below shows, if an index\n"
         "for a document exists.\n") +
    i18n("To create an index check the box in the list and press the\n"
         "\"Build Index\" button.\n");

  TQLabel *label = new TQLabel( helpText, parent );
  topLayout->addWidget( label );

  mListView = new TDEListView( parent );
  mListView->setFullWidth( true );
  mListView->addColumn( i18n("Search Scope") );
  mListView->addColumn( i18n("Status") );
  mListView->setColumnAlignment( 1, AlignCenter );
  topLayout->addWidget( mListView );
  connect( mListView, TQ_SIGNAL( clicked( TQListViewItem * ) ),
    TQ_SLOT( checkSelection() ) );

  TQBoxLayout *urlLayout = new TQHBoxLayout( topLayout );

  TQLabel *urlLabel = new TQLabel( i18n("Index folder:"), parent );
  urlLayout->addWidget( urlLabel );

  mIndexDirLabel = new TQLabel( parent );
  urlLayout->addWidget( mIndexDirLabel, 1 );

  TQPushButton *button = new TQPushButton( i18n("Change..."), parent );
  connect( button, TQ_SIGNAL( clicked() ), TQ_SLOT( showIndexDirDialog() ) );
  urlLayout->addWidget( button );

  TQBoxLayout *buttonLayout = new TQHBoxLayout( topLayout );

  buttonLayout->addStretch( 1 );
}

void KCMHelpCenter::defaults()
{
}

bool KCMHelpCenter::save()
{
  kdDebug(1401) << "KCMHelpCenter::save()" << endl;

  if ( !TQFile::exists( Prefs::indexDirectory() ) ) {
    KMessageBox::sorry( this,
      i18n("<qt>The folder <b>%1</b> does not exist; unable to create index.</qt>")
      .arg( Prefs::indexDirectory() ) );
    return false;
  } else {
    return buildIndex();
  }

  return true;
}

void KCMHelpCenter::load()
{
  findWriteableIndexDir();
  mIndexDirLabel->setText( Prefs::indexDirectory() );

  mListView->clear();

  DocEntry::List entries = DocMetaInfo::self()->docEntries();
  DocEntry::List::ConstIterator it;
  for( it = entries.begin(); it != entries.end(); ++it ) {
//    kdDebug(1401) << "Entry: " << (*it)->name() << " Indexer: '"
//              << (*it)->indexer() << "'" << endl;
    if ( mEngine->canSearch( *it ) && mEngine->needsIndex( *it ) ) {
      ScopeItem *item = new ScopeItem( mListView, *it );
      item->setOn( (*it)->searchEnabled() );
    }
  }

  updateStatus();
}

void KCMHelpCenter::updateStatus()
{
  TQListViewItemIterator it( mListView );
  while ( it.current() != 0 ) {
    ScopeItem *item = static_cast<ScopeItem *>( it.current() );
    TQString status;
    if ( item->entry()->indexExists( Prefs::indexDirectory() ) ) {
      status = i18n("OK");
      item->setOn( false );
    } else {
      status = i18n("Missing");
    }
    item->setText( 1, status );

    ++it;
  }

  checkSelection();
}

bool KCMHelpCenter::buildIndex()
{
  kdDebug(1401) << "Build Index" << endl;

  kdDebug() << "IndexPath: '" << Prefs::indexDirectory() << "'" << endl;

  if ( mProcess ) {
    kdError() << "Error: Index Process still running." << endl;
    return false;
  }

  mIndexQueue.clear();

  TQFontMetrics fm( font() );
  int maxWidth = 0;

  mCmdFile = new KTempFile;
  mCmdFile->setAutoDelete( true );
  TQTextStream *ts = mCmdFile->textStream();
  if ( !ts ) {
    kdError() << "Error opening command file." << endl;
    deleteCmdFile();
    return false;
  } else {
    kdDebug() << "Writing to file '" << mCmdFile->name() << "'" << endl;
  }

  bool hasError = false;

  TQListViewItemIterator it( mListView );
  while ( it.current() != 0 ) {
    ScopeItem *item = static_cast<ScopeItem *>( it.current() );
    if ( item->isOn() ) {
      DocEntry *entry = item->entry();

      TQString docText = i18n("Document '%1' (%2):\n")
        .arg( entry->identifier() )
        .arg( entry->name() );
      if ( entry->documentType().isEmpty() ) {
        KMessageBox::sorry( this, docText +
          i18n("No document type.") );
        hasError = true;
      } else {
        SearchHandler *handler = mEngine->handler( entry->documentType() );
        if ( !handler ) {
          KMessageBox::sorry( this, docText +
            i18n("No search handler available for document type '%1'.")
            .arg( entry->documentType() ) );
          hasError = true;
        } else {
          TQString indexer = handler->indexCommand( entry->identifier() );
          if ( indexer.isEmpty() ) {
            KMessageBox::sorry( this, docText +
              i18n("No indexing command specified for document type '%1'.")
              .arg( entry->documentType() ) );
            hasError = true;
          } else {
            indexer.replace( TQRegExp( "%i" ), entry->identifier() );
            indexer.replace( TQRegExp( "%d" ), Prefs::indexDirectory() );
            indexer.replace( TQRegExp( "%p" ), entry->url() );
            kdDebug() << "INDEXER: " << indexer << endl;
            *ts << indexer << endl;

            int width = fm.width( entry->name() );
            if ( width > maxWidth ) maxWidth = width;

            mIndexQueue.append( entry );
          }
        }
      }
    }
    ++it;
  }

  mCmdFile->close();

  if ( mIndexQueue.isEmpty() ) {
    deleteCmdFile();
    return !hasError;
  }

  mCurrentEntry = mIndexQueue.begin();
  TQString name = (*mCurrentEntry)->name();

  if ( !mProgressDialog ) {
    mProgressDialog = new IndexProgressDialog( this );
    connect( mProgressDialog, TQ_SIGNAL( cancelled() ),
             TQ_SLOT( cancelBuildIndex() ) );
    connect( mProgressDialog, TQ_SIGNAL( closed() ),
             TQ_SLOT( slotProgressClosed() ) );
  }
  mProgressDialog->setLabelText( name );
  mProgressDialog->setTotalSteps( mIndexQueue.count() );
  mProgressDialog->setMinimumLabelWidth( maxWidth );
  mProgressDialog->show();

  startIndexProcess();

  return true;
}

void KCMHelpCenter::startIndexProcess()
{
  kdDebug() << "KCMHelpCenter::startIndexProcess()" << endl;

  mProcess = new TDEProcess;

  if ( mRunAsRoot ) {
    *mProcess << "tdesu" << "--nonewdcop";
    kdDebug() << "Run as root" << endl;
  }

  *mProcess << locate("exe", "khc_indexbuilder");
  *mProcess << mCmdFile->name();
  *mProcess << Prefs::indexDirectory();

  connect( mProcess, TQ_SIGNAL( processExited( TDEProcess * ) ),
           TQ_SLOT( slotIndexFinished( TDEProcess * ) ) );
  connect( mProcess, TQ_SIGNAL( receivedStdout( TDEProcess *, char *, int ) ),
           TQ_SLOT( slotReceivedStdout(TDEProcess *, char *, int ) ) );
  connect( mProcess, TQ_SIGNAL( receivedStderr( TDEProcess *, char *, int ) ),
           TQ_SLOT( slotReceivedStderr( TDEProcess *, char *, int ) ) );

  if ( !mProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput ) ) {
    kdError() << "KCMHelpcenter::startIndexProcess(): Failed to start process."
      << endl;
  }
}

void KCMHelpCenter::cancelBuildIndex()
{
  kdDebug() << "cancelBuildIndex()" << endl;

  deleteProcess();
  deleteCmdFile();
  mIndexQueue.clear();

  if ( mIsClosing ) {
    mIsClosing = false;
  }
}

void KCMHelpCenter::slotIndexFinished( TDEProcess *proc )
{
  kdDebug() << "KCMHelpCenter::slotIndexFinished()" << endl;

  if ( proc == 0 ) {
    kdWarning() << "Process null." << endl;
    return;
  }

  if ( proc != mProcess ) {
    kdError() << "Unexpected Process finished." << endl;
    return;
  }

  if ( mProcess->normalExit() && mProcess->exitStatus() == 2 ) {
    if ( mRunAsRoot ) {
      kdError() << "Insufficient permissions." << endl;
    } else {
      kdDebug() << "Insufficient permissions. Trying again as root." << endl;
      mRunAsRoot = true;
      deleteProcess();
      startIndexProcess();
      return;
    }
  } else if ( !mProcess->normalExit() || mProcess->exitStatus() != 0 ) {
    kdDebug() << "TDEProcess reported an error." << endl;
    KMessageBox::error( this, i18n("Failed to build index.") );
  } else {
    mConfig->setGroup( "Search" );
    mConfig->writeEntry( "IndexExists", true );
    emit searchIndexUpdated();
  }

  deleteProcess();
  deleteCmdFile();

  mCurrentEntry = 0;

  if ( mProgressDialog ) {
    mProgressDialog->setFinished( true );
  }

  mStdOut = TQString();
  mStdErr = TQString();

  if ( mIsClosing ) {
    if ( !mProgressDialog->isVisible() ) {
      mIsClosing = false;
      accept();
    }
  }
}

void KCMHelpCenter::deleteProcess()
{
  delete mProcess;
  mProcess = 0;
}

void KCMHelpCenter::deleteCmdFile()
{
  delete mCmdFile;
  mCmdFile = 0;
}

void KCMHelpCenter::slotIndexProgress()
{
  if( !mProcess )
    return;

  kdDebug() << "KCMHelpCenter::slotIndexProgress()" << endl;

  updateStatus();

  advanceProgress();
}

void KCMHelpCenter::slotIndexError( const TQString &str )
{
  if( !mProcess )
    return;

  kdDebug() << "KCMHelpCenter::slotIndexError()" << endl;

  KMessageBox::sorry( this, i18n("Error executing indexing build command:\n%1")
    .arg( str ) );

  if ( mProgressDialog ) {
    mProgressDialog->appendLog( "<i>" + str + "</i>" );
  }

  advanceProgress();
}

void KCMHelpCenter::advanceProgress()
{
  if ( mProgressDialog && mProgressDialog->isVisible() ) {
    mProgressDialog->advanceProgress();
    mCurrentEntry++;
    if ( mCurrentEntry != mIndexQueue.end() ) {
      TQString name = (*mCurrentEntry)->name();
      mProgressDialog->setLabelText( name );
    }
  }
}

void KCMHelpCenter::slotReceivedStdout( TDEProcess *, char *buffer, int buflen )
{
  TQString text = TQString::fromLocal8Bit( buffer, buflen );
  int pos = text.findRev( '\n' );
  if ( pos < 0 ) {
    mStdOut.append( text );
  } else {
    if ( mProgressDialog ) {
      mProgressDialog->appendLog( mStdOut + text.left( pos ) );
      mStdOut = text.mid( pos + 1 );
    }
  }
}

void KCMHelpCenter::slotReceivedStderr( TDEProcess *, char *buffer, int buflen )
{
  TQString text = TQString::fromLocal8Bit( buffer, buflen );
  int pos = text.findRev( '\n' );
  if ( pos < 0 ) {
    mStdErr.append( text );
  } else {
    if ( mProgressDialog ) {
      mProgressDialog->appendLog( "<i>" + mStdErr + text.left( pos ) +
                                  "</i>");
      mStdErr = text.mid( pos + 1 );
    }
  }
}

void KCMHelpCenter::slotOk()
{
  if ( buildIndex() ) {
    if ( !mProcess ) accept();
    else mIsClosing = true;
  }
}

void KCMHelpCenter::slotProgressClosed()
{
  kdDebug() << "KCMHelpCenter::slotProgressClosed()" << endl;

  if ( mIsClosing ) accept();
}

void KCMHelpCenter::showIndexDirDialog()
{
  IndexDirDialog dlg( this );
  if ( dlg.exec() == TQDialog::Accepted ) {
    load();
  }
}

void KCMHelpCenter::checkSelection()
{
  int count = 0;

  TQListViewItemIterator it( mListView );
  while ( it.current() != 0 ) {
    ScopeItem *item = static_cast<ScopeItem *>( it.current() );
    if ( item->isOn() ) {
      ++count;
    }
    ++it;
  }
  
  enableButtonOK( count != 0 );
}

void KCMHelpCenter::findWriteableIndexDir()
{
  TQFileInfo currentDir( Prefs::indexDirectory() );
  if ( !currentDir.isWritable() )
    Prefs::setIndexDirectory( TDEGlobal::dirs()->saveLocation("data", "khelpcenter/index/") );
}
#include "kcmhelpcenter.moc"
