/*
 *  This file is part of the TDE Help Center
 *
 *  Copyright (C) 1999 Matthias Elter (me@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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */


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

#include <tqdir.h>
#include <tqfile.h>
#include <tqpixmap.h>
#include <tqstring.h>
#include <tqlabel.h>
#include <tqheader.h>
#include <tqdom.h>
#include <tqtextstream.h>
#include <tqregexp.h>
#include <tqlayout.h>
#include <tqlineedit.h>
#include <tqpushbutton.h>
#include <tqtooltip.h>

#include <tdeaction.h>
#include <tdeapplication.h>
#include <tdesimpleconfig.h>
#include <tdestandarddirs.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <tdelistview.h>
#include <tdemessagebox.h>
#include <kiconloader.h>
#include <tdeprocio.h>
#include <kcharsets.h>
#include <kdialog.h>
#include <tdedesktopfile.h>
#include <kprotocolinfo.h>
#include <kservicegroup.h>

#include "navigatoritem.h"
#include "navigatorappitem.h"
#include "searchwidget.h"
#include "searchengine.h"
#include "docmetainfo.h"
#include "docentrytraverser.h"
#include "glossary.h"
#include "toc.h"
#include "view.h"
#include "infotree.h"
#include "mainwindow.h"
#include "plugintraverser.h"
#include "scrollkeepertreebuilder.h"
#include "kcmhelpcenter.h"
#include "formatter.h"
#include "history.h"
#include "prefs.h"

#include "navigator.h"

using namespace KHC;

Navigator::Navigator( View *view, TQWidget *parent, const char *name )
   : TQWidget( parent, name ), mIndexDialog( 0 ),
     mView( view ), mSelected( false )
{
    TDEConfig *config = tdeApp->config();
    config->setGroup("General");
    mShowMissingDocs = config->readBoolEntry("ShowMissingDocs",false);

    mSearchEngine = new SearchEngine( view );
    connect( mSearchEngine, TQ_SIGNAL( searchFinished() ),
             TQ_SLOT( slotSearchFinished() ) );

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

    TQBoxLayout *topLayout = new TQVBoxLayout( this );

    mSearchFrame = new TQFrame( this );
    topLayout->addWidget( mSearchFrame );

    TQBoxLayout *searchLayout = new TQHBoxLayout( mSearchFrame );
    searchLayout->setSpacing( KDialog::spacingHint() );
    searchLayout->setMargin( 6 );

    TQPushButton *clearButton = new TQPushButton( mSearchFrame );
    clearButton->setIconSet( TDEApplication::reverseLayout() ?
      SmallIconSet( "clear_left" ) : SmallIconSet("locationbar_erase") );
    searchLayout->addWidget( clearButton );
    connect( clearButton, TQ_SIGNAL( clicked() ), TQ_SLOT( clearSearch() ) );
    TQToolTip::add( clearButton, i18n("Clear search") );

    mSearchEdit = new TQLineEdit( mSearchFrame );
    searchLayout->addWidget( mSearchEdit );
    connect( mSearchEdit, TQ_SIGNAL( returnPressed() ), TQ_SLOT( slotSearch() ) );
    connect( mSearchEdit, TQ_SIGNAL( textChanged( const TQString & ) ),
             TQ_SLOT( checkSearchButton() ) );

    mSearchButton = new TQPushButton( i18n("&Search"), mSearchFrame );
    searchLayout->addWidget( mSearchButton );
    connect( mSearchButton, TQ_SIGNAL( clicked() ), TQ_SLOT( slotSearch() ) );

    clearButton->setFixedHeight( mSearchButton->height() );

    mTabWidget = new TQTabWidget( this );
    topLayout->addWidget( mTabWidget );

    setupContentsTab();
    setupGlossaryTab();
    setupSearchTab();

    insertPlugins();

    if ( !mSearchEngine->initSearchHandlers() ) {
      hideSearch();
    } else {
      mSearchWidget->updateScopeList();
      mSearchWidget->readConfig( TDEGlobal::config() );
    }

    connect( mTabWidget, TQ_SIGNAL( currentChanged( TQWidget * ) ),
             TQ_SLOT( slotTabChanged( TQWidget * ) ) );
}

Navigator::~Navigator()
{
  delete mSearchEngine;
}

SearchEngine *Navigator::searchEngine() const
{
  return mSearchEngine;
}

Formatter *Navigator::formatter() const
{
  return mView->formatter();
}

bool Navigator::showMissingDocs() const
{
  return mShowMissingDocs;
}

void Navigator::setupContentsTab()
{
    mContentsTree = new TDEListView( mTabWidget );
    mContentsTree->setFrameStyle(TQFrame::Panel | TQFrame::Sunken);
    mContentsTree->addColumn(TQString::null);
    mContentsTree->setAllColumnsShowFocus(true);
    mContentsTree->header()->hide();
    mContentsTree->setRootIsDecorated(false);
    mContentsTree->setSorting(-1, false);

    connect(mContentsTree, TQ_SIGNAL(clicked(TQListViewItem*)),
            TQ_SLOT(slotItemSelected(TQListViewItem*)));
    connect(mContentsTree, TQ_SIGNAL(returnPressed(TQListViewItem*)),
           TQ_SLOT(slotItemSelected(TQListViewItem*)));
    mTabWidget->addTab(mContentsTree, i18n("&Contents"));
}

void Navigator::setupSearchTab()
{
    mSearchWidget = new SearchWidget( mSearchEngine, mTabWidget );
    connect( mSearchWidget, TQ_SIGNAL( searchResult( const TQString & ) ),
             TQ_SLOT( slotShowSearchResult( const TQString & ) ) );
    connect( mSearchWidget, TQ_SIGNAL( scopeCountChanged( int ) ),
             TQ_SLOT( checkSearchButton() ) );
    connect( mSearchWidget, TQ_SIGNAL( showIndexDialog() ),
      TQ_SLOT( showIndexDialog() ) );

    mTabWidget->addTab( mSearchWidget, i18n("Search Options"));
}

void Navigator::setupGlossaryTab()
{
    mGlossaryTree = new Glossary( mTabWidget );
    connect( mGlossaryTree, TQ_SIGNAL( entrySelected( const GlossaryEntry & ) ),
             this, TQ_SIGNAL( glossSelected( const GlossaryEntry & ) ) );
    mTabWidget->addTab( mGlossaryTree, i18n( "G&lossary" ) );
}

void Navigator::insertPlugins()
{
  PluginTraverser t( this, mContentsTree );
  DocMetaInfo::self()->traverseEntries( &t );

#if 0
  kdDebug( 1400 ) << "<docmetainfo>" << endl;
  DocEntry::List entries = DocMetaInfo::self()->docEntries();
  DocEntry::List::ConstIterator it;
  for( it = entries.begin(); it != entries.end(); ++it ) {
    (*it)->dump();
  }
  kdDebug( 1400 ) << "</docmetainfo>" << endl;
#endif
}

void Navigator::insertParentAppDocs( const TQString &name, NavigatorItem *topItem )
{
  kdDebug(1400) << "Requested plugin documents for ID " << name << endl;

  KServiceGroup::Ptr grp = KServiceGroup::childGroup( name );
  if ( !grp )
    return;

  KServiceGroup::List entries = grp->entries();
  KServiceGroup::List::ConstIterator it = entries.begin();
  KServiceGroup::List::ConstIterator end = entries.end();
  for ( ; it != end; ++it ) {
    TQString desktopFile = ( *it )->entryPath();
    if ( TQDir::isRelativePath( desktopFile ) ) {
        desktopFile = locate( "apps", desktopFile );
    }
    createItemFromDesktopFile( topItem, desktopFile );
  }
  topItem->sortChildItems( 0, true /* ascending */ );
}

void Navigator::insertIOSlaveDocs( const TQString &name, NavigatorItem *topItem )
{
  kdDebug(1400) << "Requested IOSlave documents for ID " << name << endl;

  TQStringList list = KProtocolInfo::protocols();
  list.sort();

  NavigatorItem *prevItem = 0;
  for ( TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it )
  {
    TQString docPath = KProtocolInfo::docPath(*it);
    if ( !docPath.isNull() )
    {
      // First parameter is ignored if second is an absolute path
      KURL url(KURL("help:/"), docPath);
      TQString icon = KProtocolInfo::icon(*it);
      if ( icon.isEmpty() ) icon = "text-x-generic-template";
      DocEntry *entry = new DocEntry( *it, url.url(), icon );
      NavigatorItem *item = new NavigatorItem( entry, topItem, prevItem );
      prevItem = item;
      item->setAutoDeleteDocEntry( true );
    }
  }
}

void Navigator::insertAppletDocs( NavigatorItem *topItem )
{
  TQDir appletDir( locate( "data", TQString::fromLatin1( "kicker/applets/" ) ) );
  appletDir.setNameFilter( TQString::fromLatin1( "*.desktop" ) );

  TQStringList files = appletDir.entryList( TQDir::Files | TQDir::Readable );
  TQStringList::ConstIterator it = files.begin();
  TQStringList::ConstIterator end = files.end();
  for ( ; it != end; ++it ) {
    createItemFromDesktopFile( topItem, appletDir.absPath() + "/" + *it );
  }
  topItem->sortChildItems( 0, true /* ascending */ );
}

void Navigator::createItemFromDesktopFile( NavigatorItem *topItem,
                                           const TQString &file )
{
    TDEDesktopFile desktopFile( file );
    TQString docPath = desktopFile.readDocPath();
    if ( !docPath.isNull() ) {
      // First parameter is ignored if second is an absolute path
      KURL url(KURL("help:/"), docPath);
      TQString icon = desktopFile.readIcon();
      if ( icon.isEmpty() ) icon = "text-x-generic-template";
      DocEntry *entry = new DocEntry( desktopFile.readName(), url.url(), icon );
      NavigatorItem *item = new NavigatorItem( entry, topItem );
      item->setAutoDeleteDocEntry( true );
    }
}

void Navigator::insertInfoDocs( NavigatorItem *topItem )
{
  InfoTree *infoTree = new InfoTree( this );
  infoTree->build( topItem );
}

NavigatorItem *Navigator::insertScrollKeeperDocs( NavigatorItem *topItem,
                                                  NavigatorItem *after )
{
  ScrollKeeperTreeBuilder *builder = new ScrollKeeperTreeBuilder( this );
  return builder->build( topItem, after );
}

void Navigator::selectItem( const KURL &url )
{
  kdDebug() << "Navigator::selectItem(): " << url.url() << endl;

  if ( url.url() == "khelpcenter:home" ) {
    clearSelection();
    return;
  }

  // help:/foo&anchor=bar gets redirected to help:/foo#bar
  // Make sure that we match both the original URL as well as
  // its counterpart.
  KURL alternativeURL = url;
  if (url.hasRef())
  {
     alternativeURL.setQuery("anchor="+url.ref());
     alternativeURL.setRef(TQString::null);
  }

  // If the navigator already has the given URL selected, do nothing.
  NavigatorItem *item;
  item = static_cast<NavigatorItem *>( mContentsTree->selectedItem() );
  if ( item && mSelected ) {
    KURL currentURL ( item->entry()->url() );
    if ( (currentURL == url) || (currentURL == alternativeURL) ) {
      kdDebug() << "URL already shown." << endl;
      return;
    }
  }

  // First, populate the NavigatorAppItems if we don't want the home page
  if ( url != homeURL() ) {
    for ( TQListViewItem *item = mContentsTree->firstChild(); item;
          item = item->nextSibling() ) {
      NavigatorAppItem *appItem = dynamic_cast<NavigatorAppItem *>( item );
      if ( appItem ) appItem->populate( true /* recursive */ );
      for ( TQListViewItem *subitem = item->firstChild(); subitem;
	    subitem = subitem->nextSibling() ) {
	appItem = dynamic_cast<NavigatorAppItem *>( subitem );
	if ( appItem ) appItem->populate( true /* recursive */ );
      }
    }
  }

  TQListViewItemIterator it( mContentsTree );
  while ( it.current() ) {
    NavigatorItem *item = static_cast<NavigatorItem *>( it.current() );
    KURL itemUrl( item->entry()->url() );
    if ( (itemUrl == url) || (itemUrl == alternativeURL) ) {
      mContentsTree->setCurrentItem( item );
      // If the current item was not selected and remained unchanged it
      // needs to be explicitly selected
      mContentsTree->setSelected(item, true);
      item->setOpen( true );
      mContentsTree->ensureItemVisible( item );
      break;
    }
    ++it;
  }
  if ( !it.current() ) {
    clearSelection();
  } else {
    mSelected = true;
  }
}

void Navigator::clearSelection()
{
  mContentsTree->clearSelection();
  mSelected = false;
}

void Navigator::slotItemSelected( TQListViewItem *currentItem )
{
  if ( !currentItem ) return;

  mSelected = true;

  NavigatorItem *item = static_cast<NavigatorItem *>( currentItem );

  kdDebug(1400) << "Navigator::slotItemSelected(): " << item->entry()->name()
                << endl;

  if ( item->childCount() > 0 || item->isExpandable() )
    item->setOpen( !item->isOpen() );

  KURL url ( item->entry()->url() );

  if ( url.protocol() == "khelpcenter" ) {
      mView->closeURL();
      History::self().updateCurrentEntry( mView );
      History::self().createEntry();
      showOverview( item, url );
  } else {
    if ( url.protocol() == "help" ) {
      kdDebug( 1400 ) << "slotItemSelected(): Got help URL " << url.url()
                      << endl;
      if ( !item->toc() ) {
        TOC *tocTree = item->createTOC();
        kdDebug( 1400 ) << "slotItemSelected(): Trying to build TOC for "
                        << item->entry()->name() << endl;
        tocTree->setApplication( url.directory() );
        TQString doc = View::langLookup( url.path() );
        // Enforce the original .docbook version, in case langLookup returns a
        // cached version
        if ( !doc.isNull() ) {
          int pos = doc.find( ".html" );
          if ( pos >= 0 ) {
            doc.replace( pos, 5, ".docbook" );
          }
          kdDebug( 1400 ) << "slotItemSelected(): doc = " << doc << endl;

          tocTree->build( doc );
        }
      }
    }
    emit itemSelected( url.url() );
  }

  mLastUrl = url;
}

void Navigator::openInternalUrl( const KURL &url )
{
  if ( url.url() == "khelpcenter:home" ) {
    clearSelection();
    showOverview( 0, url );
    return;
  }

  selectItem( url );
  if ( !mSelected ) return;

  NavigatorItem *item =
    static_cast<NavigatorItem *>( mContentsTree->currentItem() );

  if ( item ) showOverview( item, url );
}

void Navigator::showOverview( NavigatorItem *item, const KURL &url )
{
  mView->beginInternal( url );

  TQString fileName = locate( "data", "khelpcenter/index.html.in" );
  if ( fileName.isEmpty() )
    return;

  TQFile file( fileName );

  if ( !file.open( IO_ReadOnly ) )
    return;

  TQTextStream stream( &file );
  TQString res = stream.read();

  TQString title,name,content;
  uint childCount;

  if ( item ) {
    title = item->entry()->name();
    name = item->entry()->name();

    TQString info = item->entry()->info();
    if ( !info.isEmpty() ) content = "<p>" + info + "</p>\n";

    childCount = item->childCount();
  } else {
    title = i18n("Start Page");
    name = i18n("The Trinity Help Center");

    childCount = mContentsTree->childCount();
  }

  if ( childCount > 0 ) {
    TQListViewItem *child;
    if ( item ) child = item->firstChild();
    else child = mContentsTree->firstChild();

    mDirLevel = 0;

    content += createChildrenList( child );
  }
  else
    content += "<p></p>";

  res = res.arg(title).arg(name).arg(content);

  mView->write( res );

  mView->end();
}

TQString Navigator::createChildrenList( TQListViewItem *child )
{
  ++mDirLevel;

  TQString t;

  t += "<ul>\n";

  while ( child ) {
    NavigatorItem *childItem = static_cast<NavigatorItem *>( child );

    DocEntry *e = childItem->entry();

    t += "<li><a href=\"" + e->url() + "\">";
    if ( e->isDirectory() ) t += "<b>";
    t += e->name();
    if ( e->isDirectory() ) t += "</b>";
    t += "</a>";

    if ( !e->info().isEmpty() ) {
      t += "<br>" + e->info();
    }

    t += "</li>\n";

    if ( childItem->childCount() > 0 && mDirLevel < 2 ) {
      t += createChildrenList( childItem->firstChild() );
    }

    child = child->nextSibling();
  }

  t += "</ul>\n";

  --mDirLevel;

  return t;
}

void Navigator::slotSearch()
{
  kdDebug(1400) << "Navigator::slotSearch()" << endl;

  if ( !checkSearchIndex() ) return;

  if ( mSearchEngine->isRunning() ) return;

  TQString words = mSearchEdit->text();
  TQString method = mSearchWidget->method();
  int pages = mSearchWidget->pages();
  TQString scope = mSearchWidget->scope();

  kdDebug(1400) << "Navigator::slotSearch() words: " << words << endl;
  kdDebug(1400) << "Navigator::slotSearch() scope: " << scope << endl;

  if ( words.isEmpty() || scope.isEmpty() ) return;

  // disable search Button during searches
  mSearchButton->setEnabled(false);
  TQApplication::setOverrideCursor(TQt::waitCursor);

  if ( !mSearchEngine->search( words, method, pages, scope ) ) {
    slotSearchFinished();
    KMessageBox::sorry( this, i18n("Unable to run search program.") );
  }
}

void Navigator::slotShowSearchResult( const TQString &url )
{
  TQString u = url;
  u.replace( "%k", mSearchEdit->text() );

  emit itemSelected( u );
}

void Navigator::slotSearchFinished()
{
  mSearchButton->setEnabled(true);
  TQApplication::restoreOverrideCursor();

  kdDebug( 1400 ) << "Search finished." << endl;
}

void Navigator::checkSearchButton()
{
  mSearchButton->setEnabled( !mSearchEdit->text().isEmpty() &&
    mSearchWidget->scopeCount() > 0 );
  mTabWidget->showPage( mSearchWidget );
}

void Navigator::hideSearch()
{
  mSearchFrame->hide();
  mTabWidget->removePage( mSearchWidget );
}

bool Navigator::checkSearchIndex()
{
  TDEConfig *cfg = TDEGlobal::config();
  cfg->setGroup( "Search" );
  if ( cfg->readBoolEntry( "IndexExists", false ) ) return true;

  if ( mIndexDialog && mIndexDialog->isShown() ) return true;

  TQString text = i18n( "A search index does not yet exist. Do you want "
                       "to create the index now?" );

  int result = KMessageBox::questionYesNo( this, text, TQString::null,
                                           i18n("Create"),
                                           i18n("Do Not Create"),
                                           "indexcreation" );
  if ( result == KMessageBox::Yes ) {
    showIndexDialog();
    return false;
  }

  return true;
}

void Navigator::slotTabChanged( TQWidget *wid )
{
  if ( wid == mSearchWidget ) checkSearchIndex();
}

void Navigator::slotSelectGlossEntry( const TQString &id )
{
  mGlossaryTree->slotSelectGlossEntry( id );
}

KURL Navigator::homeURL()
{
  if ( !mHomeUrl.isEmpty() ) return mHomeUrl;

  TDEConfig *cfg = TDEGlobal::config();
  // We have to reparse the configuration here in order to get a
  // language-specific StartUrl, e.g. "StartUrl[de]".
  cfg->reparseConfiguration();
  cfg->setGroup( "General" );
  mHomeUrl = cfg->readPathEntry( "StartUrl", "khelpcenter:home" );
  return mHomeUrl;
}

void Navigator::showIndexDialog()
{
  if ( !mIndexDialog ) {
    mIndexDialog = new KCMHelpCenter( mSearchEngine, this );
    connect( mIndexDialog, TQ_SIGNAL( searchIndexUpdated() ), mSearchWidget,
             TQ_SLOT( updateScopeList() ) );
  }
  mIndexDialog->show();
  mIndexDialog->raise();
}

void Navigator::readConfig()
{
  if ( Prefs::currentTab() == Prefs::Search ) {
    mTabWidget->showPage( mSearchWidget );
  } else if ( Prefs::currentTab() == Prefs::Glossary ) {
    mTabWidget->showPage( mGlossaryTree );
  } else {
    mTabWidget->showPage( mContentsTree );
  }
}

void Navigator::writeConfig()
{
  if ( mTabWidget->currentPage() == mSearchWidget ) {
    Prefs::setCurrentTab( Prefs::Search );
  } else if ( mTabWidget->currentPage() == mGlossaryTree ) {
    Prefs::setCurrentTab( Prefs::Glossary );
  } else {
    Prefs::setCurrentTab( Prefs::Content );
  }
}

void Navigator::clearSearch()
{
  mSearchEdit->setText( TQString::null );
}

#include "navigator.moc"
