/*
   Copyright (C) 2000, 2001, 2002 Dawit Alemayehu <adawit@kde.org>

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

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

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

#include <tqtimer.h>
#include <tqapplication.h>
#include <tqlabel.h>
#include <tqpushbutton.h>
#include <tqhbox.h>
#include <tqwhatsthis.h>
#include <tqiconview.h>
#include <tdelistview.h>

#include <kdebug.h>
#include <tdelocale.h>
#include <kinstance.h>

#include <tdeaction.h>
#include <tdepopupmenu.h>
#include <tdemessagebox.h>
#include <kiconloader.h>

#include <kdirlister.h>
#include <tdelistviewsearchline.h>
#include <kiconviewsearchline.h>
#include <konq_dirpart.h>
#include <konq_propsview.h>
#include <kstaticdeleter.h>
#include <kgenericfactory.h>
#include <tdeparts/browserextension.h>

#include "dirfilterplugin.h"

SessionManager* SessionManager::m_self = 0;
static KStaticDeleter<SessionManager> dirfiltersd;

SessionManager *SessionManager::self ()
{
  if (!m_self)
    m_self = dirfiltersd.setObject(m_self, new SessionManager);

  return m_self;
}

SessionManager::SessionManager()
{
  m_bSettingsLoaded = false;
  loadSettings ();
}

SessionManager::~SessionManager()
{
  saveSettings();
  m_self = 0;
}

TQString SessionManager::generateKey (const KURL& url) const
{
  TQString key;

  key = url.protocol ();
  key += ':';

  if (!url.host ().isEmpty ())
  {
    key += url.host ();
    key += ':';
  }

  key += url.path ();
  key += ':';
  key += TQString::number (m_pid);

  return key;
}

TQStringList SessionManager::restoreMimeFilters (const KURL& url) const
{
  TQString key(generateKey(url));
  return m_filters[key];
}

TQString SessionManager::restoreTypedFilter (const KURL& url) const
{
  TQString key(generateKey(url));
  return m_typedFilter[key];
}

void SessionManager::save (const KURL& url, const TQStringList& filters)
{
  TQString key = generateKey(url);
  m_filters[key] = filters;
}

void SessionManager::save (const KURL& url, const TQString& typedFilter)
{
  TQString key = generateKey(url);
  m_typedFilter[key] = typedFilter;
}

void SessionManager::saveSettings()
{
  TDEConfig cfg ("dirfilterrc", false, false);
  cfg.setGroup ("General");

  cfg.writeEntry ("ShowCount", showCount);
  cfg.writeEntry ("UseMultipleFilters", useMultipleFilters);
  cfg.sync();
}

void SessionManager::loadSettings()
{
  if (m_bSettingsLoaded)
    return;

  TDEConfig cfg ("dirfilterrc", false, false);
  cfg.setGroup ("General");

  showCount = cfg.readBoolEntry ("ShowCount", false);
  useMultipleFilters = cfg.readBoolEntry ("UseMultipleFilters", true);
  m_pid = getpid ();
  m_bSettingsLoaded = true;
}



DirFilterPlugin::DirFilterPlugin (TQObject* parent, const char* name,
                                  const TQStringList&)
                :KParts::Plugin (parent, name),
                 m_oldFilterString("")
{
  m_part = ::tqqt_cast<KonqDirPart*>(parent);

  if ( !m_part || !m_part->scrollWidget() )
    return;

  m_pFilterMenu = new TDEActionMenu (i18n("View F&ilter"), "filter",
                                   actionCollection(), "filterdir");
  m_pFilterMenu->setDelayed (false);
  m_pFilterMenu->setWhatsThis(i18n("Allow to filter the currently displayed items by filetype."));

  connect (m_pFilterMenu->popupMenu(), TQT_SIGNAL (aboutToShow()),
           TQT_SLOT (slotShowPopup()));

  connect (m_part, TQT_SIGNAL(itemRemoved(const KFileItem*)),
           TQT_SLOT( slotItemRemoved (const KFileItem*)));
  connect (m_part, TQT_SIGNAL(itemsAdded(const KFileItemList&)),
           TQT_SLOT (slotItemsAdded(const KFileItemList&)));
  connect (m_part, TQT_SIGNAL(itemsFilteredByMime(const KFileItemList&)),
           TQT_SLOT (slotItemsAdded(const KFileItemList&)));
  connect (m_part, TQT_SIGNAL(itemsRefresh(const KFileItemList&)),
           TQT_SLOT (slotItemsRefresh(const KFileItemList&)));
  connect (m_part, TQT_SIGNAL(aboutToOpenURL()), TQT_SLOT(slotOpenURL()));

  // add a searchline filter for konqis icons/list views
  TQHBox *hbox = new TQHBox(m_part->widget());
  hbox->hide();

  TDEAction *clear = new TDEAction(i18n("Clear Filter Field"),
                               TQApplication::reverseLayout() ? "clear_left" : "locationbar_erase",
                               0, 0, 0, actionCollection(), "clear_filter");

  clear->setWhatsThis(i18n("Clear filter field<p>Clears the content of the filter field."));

  if ( ::tqqt_cast<TDEListView*>(m_part->scrollWidget()) )
  {
    m_searchWidget = new TDEListViewSearchLine(hbox);
    static_cast<TDEListViewSearchLine*>(m_searchWidget)->setListView(static_cast<TDEListView*>(m_part->scrollWidget()));
  }
  else if ( ::tqqt_cast<TQIconView*>(m_part->scrollWidget()) )
  {
    m_searchWidget = new TDEIconViewSearchLine(hbox);
    static_cast<TDEIconViewSearchLine*>(m_searchWidget)->setIconView(static_cast<TQIconView*>(m_part->scrollWidget()));
  }
  else
  {
    m_searchWidget=NULL;
  }

  if ( m_searchWidget )
  {
    TQWhatsThis::add(m_searchWidget, i18n("Enter here a text which an item in the view must contain anywhere to be shown."));
    connect(clear, TQT_SIGNAL(activated()), m_searchWidget, TQT_SLOT(clear()));
    connect(m_searchWidget, TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(searchTextChanged(const TQString&)));
  }

  KWidgetAction *filterAction = new KWidgetAction(hbox, i18n("Filter Field"),
                                                  0, 0, 0, actionCollection(), "toolbar_filter_field");
  filterAction->setShortcutConfigurable(false);

// FIXME: This causes crashes for an unknown reason on certain systems
// Probably the iconview onchange event handler should tempoarily stop this timer before doing anything else
// Really the broken TQt3 iconview should just be modified to include a hidden list; it would make things *so* much easier!
  m_refreshTimer = new TQTimer( this );
  m_reactivateRefreshTimer = new TQTimer( this );
  connect( m_refreshTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(activateSearch()) );
  m_refreshTimer->start( 200, FALSE ); // 200 millisecond continuous timer
  connect( m_reactivateRefreshTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(reactivateRefreshTimer()) );

}

DirFilterPlugin::~DirFilterPlugin()
{
  m_reactivateRefreshTimer->stop();
  m_refreshTimer->stop();
  delete m_pFilterMenu;
  delete m_refreshTimer;
  delete m_reactivateRefreshTimer;
}

void DirFilterPlugin::searchTextChanged(const TQString& newtext)
{
  m_refreshTimer->stop();
  m_reactivateRefreshTimer->stop();
  m_reactivateRefreshTimer->start( 1000, TRUE );
}

void DirFilterPlugin::reactivateRefreshTimer()
{
  m_refreshTimer->start( 200, FALSE ); // 200 millisecond continuous time
}

void DirFilterPlugin::slotOpenURL ()
{
  KURL url = m_part->url();

  //kdDebug(90190) << "DirFilterPlugin: New URL    : " << url.url() << endl;
  //kdDebug(90190) << "DirFilterPlugin: Current URL: " << m_pURL.url() << endl;

  if (m_pURL != url)
  {
    //Clears the hidden list which is by now outdated...
    if (m_searchWidget)
    {
      SessionManager::self()->save(m_pURL, m_searchWidget->text());
      m_searchWidget->clear();

      TQString typedFilter(SessionManager::self()->restoreTypedFilter(url));
      m_searchWidget->completionObject()->addItem(typedFilter);
      m_searchWidget->setText(typedFilter);
    }

    m_pURL = url;
    m_pMimeInfo.clear();
    m_part->setMimeFilter (SessionManager::self()->restoreMimeFilters(url));
  }
}

void DirFilterPlugin::slotShowPopup()
{
  if (!m_part)
  {
    m_pFilterMenu->setEnabled (false);
    return;
  }

  int id = 0;
  uint enableReset = 0;

  TQString label;
  TQStringList inodes;

  m_pFilterMenu->popupMenu()->clear();
  m_pFilterMenu->popupMenu()->insertTitle (i18n("Only Show Items of Type"));

  MimeInfoIterator it = m_pMimeInfo.begin();
  const MimeInfoIterator end = m_pMimeInfo.end();
  for (; it != end ; ++it)
  {
    if (it.key().startsWith("inode"))
    {
      inodes << it.key();
      continue;
    }

    if (!SessionManager::self()->showCount)
      label = it.data().mimeComment;
    else
    {
      label = it.data().mimeComment;
      label += "  (";
      label += TQString::number (it.data().filenames.size ());
      label += ")";
    }

    m_pMimeInfo[it.key()].id = m_pFilterMenu->popupMenu()->insertItem (
                               SmallIconSet(it.data().iconName), label,
                               this, TQT_SLOT(slotItemSelected(int)), 0, ++id);

    if (it.data().useAsFilter)
    {
      m_pFilterMenu->popupMenu()->setItemChecked (id, true);
      enableReset++;
    }
  }

  // Add all the items that have mime-type of "inode/*" here...
  if (!inodes.isEmpty())
  {
    m_pFilterMenu->popupMenu()->insertSeparator ();

    TQStringList::Iterator it = inodes.begin();
    TQStringList::Iterator end = inodes.end();

    for (; it != end; ++it)
    {
      if (!SessionManager::self()->showCount)
        label = m_pMimeInfo[(*it)].mimeComment;
      else
      {
        label = m_pMimeInfo[(*it)].mimeComment;
        label += "  (";
        label += TQString::number (m_pMimeInfo[(*it)].filenames.size ());
        label += ")";
      }

      m_pMimeInfo[(*it)].id = m_pFilterMenu->popupMenu()->insertItem (
                              SmallIconSet(m_pMimeInfo[(*it)].iconName), label,
                              this, TQT_SLOT(slotItemSelected(int)), 0, ++id);

      if (m_pMimeInfo[(*it)].useAsFilter)
      {
        m_pFilterMenu->popupMenu()->setItemChecked (id, true);
        enableReset ++;
      }
    }
  }

  m_pFilterMenu->popupMenu()->insertSeparator ();
  id = m_pFilterMenu->popupMenu()->insertItem (i18n("Use Multiple Filters"),
                                               this, TQT_SLOT(slotMultipleFilters()));
  m_pFilterMenu->popupMenu()->setItemEnabled (id, enableReset <= 1);
  m_pFilterMenu->popupMenu()->setItemChecked (id, SessionManager::self()->useMultipleFilters);

  id = m_pFilterMenu->popupMenu()->insertItem (i18n("Show Count"), this,
                                               TQT_SLOT(slotShowCount()));
  m_pFilterMenu->popupMenu()->setItemChecked (id, SessionManager::self()->showCount);

  id = m_pFilterMenu->popupMenu()->insertItem (i18n("Reset"), this,
                                               TQT_SLOT(slotReset()));

  m_pFilterMenu->popupMenu()->setItemEnabled (id, enableReset);
}

void DirFilterPlugin::slotItemSelected (int id)
{
  if (!m_part)
    return;

  MimeInfoIterator it = m_pMimeInfo.begin();
  while (it != m_pMimeInfo.end () && id != it.data().id)
    it++;

  if (it != m_pMimeInfo.end())
  {
    TQStringList filters;

    if (it.data().useAsFilter)
    {
      it.data().useAsFilter = false;
      filters = m_part->mimeFilter ();
      if (filters.remove (it.key()))
        m_part->setMimeFilter (filters);
    }
    else
    {
      m_pMimeInfo[it.key()].useAsFilter = true;

      if (SessionManager::self()->useMultipleFilters)
      {
        filters = m_part->mimeFilter ();
        filters << it.key();
      }
      else
      {
        filters << it.key();

        MimeInfoIterator item = m_pMimeInfo.begin();
        while ( item != m_pMimeInfo.end() )
        {
          if ( item != it )
            item.data().useAsFilter = false;
          item++;
        }
      }

      m_part->setMimeFilter (filters);
    }

    KURL url = m_part->url();
    m_part->openURL (url);
    SessionManager::self()->save (url, filters);
  }
}

void DirFilterPlugin::slotItemsAdded(const KFileItemList &list)
{
  KURL url = m_part->url();

  if (list.count() == 0 || !m_part || !m_part->nameFilter().isEmpty())
  {
    m_pFilterMenu->setEnabled (m_part->nameFilter().isEmpty());
    return;
  }
  if ( ::tqqt_cast<TDEListView*>(m_part->scrollWidget()) )
  {
    static_cast<TDEListViewSearchLine*>(m_searchWidget)->updateSearch();
  }
  else if ( ::tqqt_cast<TQIconView*>(m_part->scrollWidget()) )
  {
    static_cast<TDEIconViewSearchLine*>(m_searchWidget)->updateSearch();
  }

  // Make sure the filter menu is enabled once a named
  // filter is removed.
  if (!m_pFilterMenu->isEnabled())
    m_pFilterMenu->setEnabled (true);

  for (KFileItemListIterator it (list); it.current (); ++it)
  {
    TQString name = it.current()->name();
    KMimeType::Ptr mime = it.current()->mimeTypePtr(); // don't resolve mimetype if unknown
    if (!mime)
      continue;
    TQString mimeType = mime->name();

    if (!m_pMimeInfo.contains(mimeType))
    {
      MimeInfo& mimeInfo = m_pMimeInfo[mimeType];
      TQStringList filters = m_part->mimeFilter();
      mimeInfo.useAsFilter = (!filters.isEmpty () && filters.contains (mimeType));
      mimeInfo.mimeComment = mime->comment();
      mimeInfo.iconName = mime->icon(KURL(), false);
      mimeInfo.filenames.insert(name, false);
    }
    else
    {
      m_pMimeInfo[mimeType].filenames.insert(name, false);
    }
  }
}

void DirFilterPlugin::slotItemRemoved(const KFileItem* item)
{
	if (!item || !m_part)
		return;

	// Due to the poor implementation of qiconviewitem, there is no way for it to know if an item on the hidden list was deleted
	// This *will* eventually cause the kiconviewsearchline to attempt to reference a deleted object, resulting in a crash
	// This is illustrated well with two Konqueror windows.  Open one, set the filter bar to filter out a file, then delete the filtered file from another window.
	// Now clear the search bar (forcing injection of the deleted iconview object pointer from the hidden list).  Konqueror will crash!
	// If iconDeleted is called, it allows kiconviewsearchline to temporarily add the affected qiconviewitem(s) (by filename) to the iconview so that as far as
	// qiconview is concerned the qiconviewitem objects never left.
	// NOTE: This bug is NOT present in qlistviewitem

	// HACK around it here...
	if ( ::tqqt_cast<TQIconView*>(m_part->scrollWidget()) ) {
		static_cast<TDEIconViewSearchLine*>(m_searchWidget)->iconDeleted(item->name());
	}

	TQString mimeType = item->mimetype().stripWhiteSpace();
	
	if (m_pMimeInfo.contains(mimeType)) {
		MimeInfo info = m_pMimeInfo [mimeType];
		
		if (info.filenames.size () > 1)
			m_pMimeInfo [mimeType].filenames.remove(item->name());
		else {
			if (info.useAsFilter) {
				TQStringList filters = m_part->mimeFilter ();
				filters.remove(mimeType);
				m_part->setMimeFilter(filters);
				SessionManager::self()->save(m_part->url(), filters);
				// Use 1ms delay to avoid possible race conditions
				TQTimer::singleShot(1, this, TQT_SLOT(slotTimeout()));
			}
			m_pMimeInfo.remove(mimeType);
		}
	}
}

void DirFilterPlugin::slotItemsRefresh(const KFileItemList &list)
{
  if (list.count() == 0 || !m_part || !m_part->nameFilter().isEmpty())
  {
    m_pFilterMenu->setEnabled (m_part->nameFilter().isEmpty());
    return;
  }
  if ( ::tqqt_cast<TDEListView*>(m_part->scrollWidget()) )
  {
    static_cast<TDEListViewSearchLine*>(m_searchWidget)->updateSearch();
  }
  else if ( ::tqqt_cast<TQIconView*>(m_part->scrollWidget()) )
  {
    static_cast<TDEIconViewSearchLine*>(m_searchWidget)->updateSearch();
  }
    
  TQMap<TQString,bool> itemsMap;
  if (::tqqt_cast<TDEListView*>(m_part->scrollWidget()))
  {
    TQListView *listview = ::tqqt_cast<TDEListView*>(m_part->scrollWidget());
    for (TQListViewItemIterator lv_it(listview); lv_it.current(); ++lv_it)
    {
      itemsMap.insert(lv_it.current()->text(0), false);   
    }
  }
  else if ( ::tqqt_cast<TQIconView*>(m_part->scrollWidget()) )
  {
    TQIconView *iconview = ::tqqt_cast<TQIconView*>(m_part->scrollWidget());
    for (TQIconViewItem *item = iconview->firstItem(); item; item = item->nextItem())
    {
      itemsMap.insert(item->text(), false);   
    }   
  }

  MimeInfoIterator it;
  for (it = m_pMimeInfo.begin(); it != m_pMimeInfo.end(); /*empty*/)
  {
    TQMap<TQString,bool>::iterator file_it;
    for ( file_it = it.data().filenames.begin(); file_it != it.data().filenames.end(); /*empty*/)
    {
      if (!itemsMap.contains(file_it.key()))
      {
        // Remove mimeInfo for this file (the file was problably renamed)
        TQMap<TQString,bool>::iterator del_fit = file_it;
        ++file_it;
        it.data().filenames.remove(del_fit);
      }
      else
        ++file_it;
    }   
        
    if (it.data().filenames.size() == 0)    
    {
      if (it.data().useAsFilter) {
        TQStringList filters = m_part->mimeFilter();
        filters.remove(it.key());
        m_part->setMimeFilter(filters);
        SessionManager::self()->save(m_part->url(), filters);
        // Use 1ms delay to avoid possible race conditions
        TQTimer::singleShot(1, this, TQT_SLOT(slotTimeout()));
      }
      MimeInfoIterator del_mit = it;
      ++it;
      m_pMimeInfo.remove(del_mit);
    }
    else
      ++it;
  }  

  for (KFileItemListIterator fi_it(list); fi_it.current (); ++fi_it)
  {
    TQString name = fi_it.current()->name();
    KMimeType::Ptr mime = fi_it.current()->mimeTypePtr(); // don't resolve mimetype if unknown
    if (!mime)
      continue;
    TQString mimeType = mime->name();

    if (!m_pMimeInfo.contains(mimeType))
    {
      MimeInfo &mimeInfo = m_pMimeInfo[mimeType];
      TQStringList filters = m_part->mimeFilter();
      mimeInfo.useAsFilter = (!filters.isEmpty () && filters.contains (mimeType));
      mimeInfo.mimeComment = mime->comment();
      mimeInfo.iconName = mime->icon(KURL(), false);
      mimeInfo.filenames.insert(name, false);
    }
    else
    {
      MimeInfo &mimeInfo = m_pMimeInfo[mimeType];
      if (!mimeInfo.filenames.contains(name))
      {
        mimeInfo.filenames.insert(name, false);
      }
    }
  }
}

void DirFilterPlugin::activateSearch()
{
	// FIXME: If any of the files change while they are hidden in iconview mode, they will
	// reappear under the wrong (old) name.  This routine was originally intended to fix that
	// problem, but will need to be able to detect when a change has occurred to be effective.

	if (!m_searchWidget)
		return;

	if (m_oldFilterString == m_searchWidget->text())
		return;

	m_oldFilterString = m_searchWidget->text();

	if ( ::tqqt_cast<TDEListView*>(m_part->scrollWidget()) ) {
		static_cast<TDEListViewSearchLine*>(m_searchWidget)->updateSearch();
	}
	else if ( ::tqqt_cast<TQIconView*>(m_part->scrollWidget()) ) {
		static_cast<TDEIconViewSearchLine*>(m_searchWidget)->updateSearch();
	}
}

void DirFilterPlugin::slotReset()
{
  if (!m_part)
    return;

  MimeInfoIterator it = m_pMimeInfo.begin();
  for (; it != m_pMimeInfo.end(); ++it)
    it.data().useAsFilter = false;

  TQStringList filters;
  KURL url = m_part->url();

  m_part->setMimeFilter (filters);
  SessionManager::self()->save (url, filters);
  m_part->openURL (url);
}

void DirFilterPlugin::slotShowCount()
{
  if (SessionManager::self()->showCount)
    SessionManager::self()->showCount = false;
  else
    SessionManager::self()->showCount = true;
}

void DirFilterPlugin::slotMultipleFilters()
{
  if (SessionManager::self()->useMultipleFilters)
    SessionManager::self()->useMultipleFilters = false;
  else
    SessionManager::self()->useMultipleFilters = true;
}

void DirFilterPlugin::slotTimeout()
{
  if (m_part)
    m_part->openURL (m_part->url());
}

typedef KGenericFactory<DirFilterPlugin> DirFilterFactory;
K_EXPORT_COMPONENT_FACTORY (libdirfilterplugin, DirFilterFactory("dirfilterplugin"))

#include "dirfilterplugin.moc"
