/*  This file is part of the KDE libraries
 *  Copyright (C) 2000 Waldo Bastian <bastian@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., 59 Temple Place - Suite 330,
 *  Boston, MA 02111-1307, USA.
 **/

// $Id: kservicegroup.cpp 310019 2004-05-10 12:52:36Z waba $

#include <kiconloader.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <klocale.h>
#include <kdebug.h>
#include <ksortablevaluelist.h>
#include "kservicefactory.h"
#include "kservicegroupfactory.h"
#include "kservicegroup.h"
#include "kservice.h"
#include "ksycoca.h"

class KServiceGroup::Private
{
public:
  Private() { m_bNoDisplay = false; }
  bool m_bNoDisplay;
  QStringList suppressGenericNames;
  QString directoryEntryPath;
  QStringList sortOrder;
};

KServiceGroup::KServiceGroup( const QString & name )
 : KSycocaEntry(name), m_childCount(-1)
{
  d = new KServiceGroup::Private;
  m_bDeleted = false;
}

KServiceGroup::KServiceGroup( const QString &configFile, const QString & _relpath )
 : KSycocaEntry(_relpath), m_childCount(-1)
{
  d = new KServiceGroup::Private;
  m_bDeleted = false;

  QString cfg = configFile;
  if (cfg.isEmpty())
     cfg = _relpath+".directory";

  d->directoryEntryPath = cfg;

  KConfig config( cfg, true, false, "apps" );

  config.setDesktopGroup();

  m_strCaption = config.readEntry( "Name" );
  m_strIcon = config.readEntry( "Icon" );
  m_strComment = config.readEntry( "Comment" );
  m_bDeleted = config.readBoolEntry( "Hidden", false );
  d->m_bNoDisplay = config.readBoolEntry( "NoDisplay", false );
  QStringList tmpList;
  if (config.hasKey("OnlyShowIn"))
  {
     if (!config.readListEntry("OnlyShowIn", ';').contains("KDE"))
        d->m_bNoDisplay = true;
  }
  if (config.hasKey("NotShowIn"))
  {
     if (config.readListEntry("NotShowIn", ';').contains("KDE"))
        d->m_bNoDisplay = true;
  }
  
  m_strBaseGroupName = config.readEntry( "X-KDE-BaseGroup" );
  d->suppressGenericNames = config.readListEntry( "X-KDE-SuppressGenericNames" );
//  d->sortOrder = config.readListEntry("SortOrder");

  // Fill in defaults.
  if (m_strCaption.isEmpty())
  {
     m_strCaption = _relpath;
     if (m_strCaption.right(1) == "/")
        m_strCaption = m_strCaption.left(m_strCaption.length()-1);
     int i = m_strCaption.findRev('/');
     if (i > 0)
        m_strCaption = m_strCaption.mid(i+1);
  }
  if (m_strIcon.isEmpty())
     m_strIcon = "folder";
}

KServiceGroup::KServiceGroup( QDataStream& _str, int offset, bool deep ) :
    KSycocaEntry( _str, offset )
{
  d = new KServiceGroup::Private;
  m_bDeep = deep;
  load( _str );
}

KServiceGroup::~KServiceGroup()
{
  delete d;
}

int KServiceGroup::childCount()
{
  if (m_childCount == -1)
  {
     m_childCount = 0;

     for( List::ConstIterator it = m_serviceList.begin();
          it != m_serviceList.end(); it++)
     {
        KSycocaEntry *p = (*it);
        if (p->isType(KST_KService))
        {
           KService *service = static_cast<KService *>(p);
           if (!service->noDisplay())
              m_childCount++;
        }
        else if (p->isType(KST_KServiceGroup))
        {
           KServiceGroup *serviceGroup = static_cast<KServiceGroup *>(p);
           m_childCount += serviceGroup->childCount();
        }
     }
  }
  return m_childCount;
}


bool KServiceGroup::noDisplay() const
{
  return d->m_bNoDisplay || m_strCaption.startsWith(".");
}

QStringList KServiceGroup::suppressGenericNames() const
{
  return d->suppressGenericNames;
}

void KServiceGroup::load( QDataStream& s )
{
  QStringList groupList;
  Q_INT8 noDisplay;
  s >> m_strCaption >> m_strIcon >>
      m_strComment >> groupList >> m_strBaseGroupName >> m_childCount >>
      noDisplay >> d->suppressGenericNames >> d->directoryEntryPath >>
      d->sortOrder;

  d->m_bNoDisplay = (noDisplay != 0);

  if (m_bDeep)
  {
     for(QStringList::ConstIterator it = groupList.begin();
         it != groupList.end(); it++)
     {
        QString path = *it;
        if (path[path.length()-1] == '/')
        {
           KServiceGroup *serviceGroup;
           serviceGroup = KServiceGroupFactory::self()->findGroupByDesktopPath(path, false);
           if (serviceGroup)
              m_serviceList.append( SPtr(serviceGroup) );
        }
        else
        {
           KService *service;
           service = KServiceFactory::self()->findServiceByDesktopPath(path);
           if (service)
              m_serviceList.append( SPtr(service) );
        }
     }
  }
}

void KServiceGroup::addEntry( KSycocaEntry *entry)
{
  m_serviceList.append(entry);
}

void KServiceGroup::save( QDataStream& s )
{
  KSycocaEntry::save( s );

  QStringList groupList;
  for( List::ConstIterator it = m_serviceList.begin();
       it != m_serviceList.end(); it++)
  {
     KSycocaEntry *p = (*it);
     if (p->isType(KST_KService))
     {
        KService *service = static_cast<KService *>(p);
        groupList.append( service->desktopEntryPath());
     }
     else if (p->isType(KST_KServiceGroup))
     {
        KServiceGroup *serviceGroup = static_cast<KServiceGroup *>(p);
        groupList.append( serviceGroup->relPath());
     }
     else
     {
        //fprintf(stderr, "KServiceGroup: Unexpected object in list!\n");
     }
  }

  (void) childCount();

  Q_INT8 noDisplay = d->m_bNoDisplay ? 1 : 0;
  s << m_strCaption << m_strIcon <<
      m_strComment << groupList << m_strBaseGroupName << m_childCount <<
      noDisplay << d->suppressGenericNames << d->directoryEntryPath <<
      d->sortOrder;
}

KServiceGroup::List
KServiceGroup::entries(bool sort)
{
   return entries(sort, true);
}

KServiceGroup::List
KServiceGroup::entries(bool sort, bool excludeNoDisplay)
{
   return entries(sort, excludeNoDisplay, false);
}

static void addItem(KServiceGroup::List &sorted, const KSycocaEntry::Ptr &p, bool &addSeparator)
{
   if (addSeparator && !sorted.isEmpty())
      sorted.append(new KServiceSeparator());
   sorted.append(p);
   addSeparator = false;
}

KServiceGroup::List
KServiceGroup::entries(bool sort, bool excludeNoDisplay, bool allowSeparators, bool sortByGenericName)
{
    KServiceGroup *group = this;

    // If the entries haven't been loaded yet, we have to reload ourselves
    // together with the entries. We can't only load the entries afterwards
    // since the offsets could have been changed if the database has changed.

    if (!m_bDeep) {

        group =
            KServiceGroupFactory::self()->findGroupByDesktopPath(relPath(), true);

        if (0 == group) // No guarantee that we still exist!
            return List();
    }

    if (!sort)
        return group->m_serviceList;

    // Sort the list alphabetically, according to locale.
    // Groups come first, then services.

    KSortableValueList<SPtr,QCString> slist;
    KSortableValueList<SPtr,QCString> glist;
    for (List::ConstIterator it(group->m_serviceList.begin()); it != group->m_serviceList.end(); ++it)
    {
        KSycocaEntry *p = (*it);
	bool noDisplay = p->isType(KST_KServiceGroup) ?
                                   static_cast<KServiceGroup *>(p)->noDisplay() :
                                   static_cast<KService *>(p)->noDisplay();
        if (excludeNoDisplay && noDisplay)
           continue;
        // Choose the right list
        KSortableValueList<SPtr,QCString> & list = p->isType(KST_KServiceGroup) ? glist : slist;
        QString name;
        if (p->isType(KST_KServiceGroup))
          name = static_cast<KServiceGroup *>(p)->caption();
        else if (sortByGenericName)
          name = static_cast<KService *>(p)->genericName() + " " + p->name();
        else
          name = p->name() + " " + static_cast<KService *>(p)->genericName();
                                                                      
        QCString key( name.length() * 4 + 1 );
        // strxfrm() crashes on Solaris
#ifndef USE_SOLARIS
        // maybe it'd be better to use wcsxfrm() where available
        size_t ln = strxfrm( key.data(), name.local8Bit().data(), key.size());
        if( ln != size_t( -1 ))
        {
            if( ln >= key.size())
            { // didn't fit?
                key.resize( ln + 1 );
                if( strxfrm( key.data(), name.local8Bit().data(), key.size()) == size_t( -1 ))
                    key = name.local8Bit();
            }
        }
        else
#endif
        {
            key = name.local8Bit();
        }
        list.insert(key,SPtr(*it));
    }
    // Now sort
    slist.sort();
    glist.sort();

    if (d->sortOrder.isEmpty())
    {
       d->sortOrder << ":M";
       d->sortOrder << ":F";
    }

    QString rp = relPath();
    if(rp == "/") rp = QString::null;

    // Iterate through the sort spec list.
    // If an entry gets mentioned explicitly, we remove it from the sorted list
    for (QStringList::ConstIterator it(d->sortOrder.begin()); it != d->sortOrder.end(); ++it)
    {
        const QString &item = *it;
        if (item.isEmpty()) continue;
        if (item[0] == '/')
        {
          QString groupPath = rp + item.mid(1) + "/";
           // Remove entry from sorted list of services.
          for(KSortableValueList<SPtr,QCString>::Iterator it2 = glist.begin(); it2 != glist.end(); ++it2)
          {
             KServiceGroup *group = (KServiceGroup *)((KSycocaEntry *)((*it2).value()));
             if (group->relPath() == groupPath)
             {
                glist.remove(it2);
                break;
             }
          }
        }
        else if (item[0] != ':')
        {
           // Remove entry from sorted list of services.
           // TODO: Remove item from sortOrder-list if not found
           // TODO: This prevents duplicates
          for(KSortableValueList<SPtr,QCString>::Iterator it2 = slist.begin(); it2 != slist.end(); ++it2)
          {
             KService *service = (KService *)((KSycocaEntry *)((*it2).value()));
             if (service->menuId() == item)
             {
                slist.remove(it2);
                break;
             }
          }
        }
    }

    List sorted;

    bool needSeparator = false;
    // Iterate through the sort spec list.
    // Add the entries to the list according to the sort spec.
    for (QStringList::ConstIterator it(d->sortOrder.begin()); it != d->sortOrder.end(); ++it)
    {
        const QString &item = *it;
        if (item.isEmpty()) continue;
        if (item[0] == ':')
        {
          // Special condition...
          if (item == ":S")
          {
             if (allowSeparators)
                needSeparator = true;
          }
          else if (item == ":M")
          {
            // Add sorted list of sub-menus
            for(KSortableValueList<SPtr,QCString>::Iterator it2 = glist.begin(); it2 != glist.end(); ++it2)
            {
              addItem(sorted, (*it2).value(), needSeparator);
            }
          }
          else if (item == ":F")
          {
            // Add sorted list of services
            for(KSortableValueList<SPtr,QCString>::Iterator it2 = slist.begin(); it2 != slist.end(); ++it2)
            {
              addItem(sorted, (*it2).value(), needSeparator);
            }
          }
          else if (item == ":A")
          {
            // Add sorted lists of services and submenus
            KSortableValueList<SPtr,QCString>::Iterator it_s = slist.begin();
            KSortableValueList<SPtr,QCString>::Iterator it_g = glist.begin();
            
            while(true)
            {
               if (it_s == slist.end())
               {
                  if (it_g == glist.end())
                     break; // Done
                     
                  // Insert remaining sub-menu
                  addItem(sorted, (*it_g).value(), needSeparator);
                  it_g++;
               }
               else if (it_g == glist.end())
               {
                  // Insert remaining service
                  addItem(sorted, (*it_s).value(), needSeparator);
                  it_s++;
               }
               else if ((*it_g).index() < (*it_s).index())
               {
                  // Insert sub-menu first
                  addItem(sorted, (*it_g).value(), needSeparator);
                  it_g++;
               }
               else
               {
                  // Insert service first
                  addItem(sorted, (*it_s).value(), needSeparator);
                  it_s++;
               }
            }
          }
        }
        else if (item[0] == '/')
        {
          QString groupPath = rp + item.mid(1) + "/";

          for (List::ConstIterator it2(group->m_serviceList.begin()); it2 != group->m_serviceList.end(); ++it2)
          {
            if (!(*it2)->isType(KST_KServiceGroup))
               continue;
            KServiceGroup *group = (KServiceGroup *)((KSycocaEntry *)(*it2));
            if (group->relPath() == groupPath)
            {
               if (!excludeNoDisplay || !group->noDisplay())
                  addItem(sorted, (*it2), needSeparator);
               break;
            }
          }
        }
        else
        {
          for (List::ConstIterator it2(group->m_serviceList.begin()); it2 != group->m_serviceList.end(); ++it2)
          {
            if (!(*it2)->isType(KST_KService))
               continue;
            KService *service = (KService *)((KSycocaEntry *)(*it2));
            if (service->menuId() == item)
            {
               if (!excludeNoDisplay || !service->noDisplay())
                  addItem(sorted, (*it2), needSeparator);
               break;
            }
          }
        }
    }

    return sorted;
}

void KServiceGroup::setLayoutInfo(const QStringList &layout)
{
    d->sortOrder = layout;
}

QStringList KServiceGroup::layoutInfo() const
{
    return d->sortOrder;
}

KServiceGroup::Ptr
KServiceGroup::baseGroup( const QString & _baseGroupName )
{
    return KServiceGroupFactory::self()->findBaseGroup(_baseGroupName, true);
}

KServiceGroup::Ptr
KServiceGroup::root()
{
   return KServiceGroupFactory::self()->findGroupByDesktopPath("/", true);
}

KServiceGroup::Ptr
KServiceGroup::group(const QString &relPath)
{
   if (relPath.isEmpty()) return root();
   return KServiceGroupFactory::self()->findGroupByDesktopPath(relPath, true);
}

KServiceGroup::Ptr
KServiceGroup::childGroup(const QString &parent)
{
   return KServiceGroupFactory::self()->findGroupByDesktopPath("#parent#"+parent, true);
}

QString
KServiceGroup::directoryEntryPath() const
{
   return d->directoryEntryPath;
}


void KServiceGroup::virtual_hook( int id, void* data )
{ KSycocaEntry::virtual_hook( id, data ); }


KServiceSeparator::KServiceSeparator( )
 : KSycocaEntry("separator")
{
}
