/*  This file is part of the KDE libraries
 *  Copyright (C) 2003 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., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 **/

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdlib.h> // getenv

#include <kdebug.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <kservice.h>
#include <kde_file.h>

#include <tqmap.h>
#include <tqfile.h>
#include <tqdir.h>
#include <tqregexp.h>

#include "vfolder_menu.h"

static void foldNode(TQDomElement &docElem, TQDomElement &e, TQMap<TQString,TQDomElement> &dupeList, TQString s=TQString::null)
{
   if (s.isEmpty())
      s = e.text();
   TQMap<TQString,TQDomElement>::iterator it = dupeList.find(s);
   if (it != dupeList.end())
   {
      kdDebug(7021) << e.tagName() << " and " << s << " requires combining!" << endl;

      docElem.removeChild(*it);
      dupeList.remove(it);
   }
   dupeList.insert(s, e);
}

static void replaceNode(TQDomElement &docElem, TQDomNode &n, const TQStringList &list, const TQString &tag)
{
   for(TQStringList::ConstIterator it = list.begin();
       it != list.end(); ++it)
   {
      TQDomElement e = docElem.ownerDocument().createElement(tag);
      TQDomText txt = docElem.ownerDocument().createTextNode(*it);
      e.appendChild(txt);
      docElem.insertAfter(e, n);
   }

   TQDomNode next = n.nextSibling();
   docElem.removeChild(n);
   n = next;
//   kdDebug(7021) << "Next tag = " << n.toElement().tagName() << endl;
}

void VFolderMenu::registerFile(const TQString &file)
{
   int i = file.findRev('/');
   if (i < 0)
      return;

   TQString dir = file.left(i+1); // Include trailing '/'
   registerDirectory(dir);
}

void VFolderMenu::registerDirectory(const TQString &directory)
{
   m_allDirectories.append(directory);
}

TQStringList VFolderMenu::allDirectories()
{
   if (m_allDirectories.isEmpty())
     return m_allDirectories;
   m_allDirectories.sort();

   TQStringList::Iterator it = m_allDirectories.begin();
   TQString previous = *it++;
   for(;it != m_allDirectories.end();)
   {
     if ((*it).startsWith(previous))
     {
        it = m_allDirectories.remove(it);
     }
     else
     {
        previous = *it;
        ++it;
     }
   }
   return m_allDirectories;
}

static void
track(const TQString &menuId, const TQString &menuName, TQDict<KService> *includeList, TQDict<KService> *excludeList, TQDict<KService> *itemList, const TQString &comment)
{
   if (itemList->find(menuId))
      printf("%s: %s INCL %d EXCL %d\n", menuName.latin1(), comment.latin1(), includeList->find(menuId) ? 1 : 0, excludeList->find(menuId) ? 1 : 0);
}

void
VFolderMenu::includeItems(TQDict<KService> *items1, TQDict<KService> *items2)
{
   for(TQDictIterator<KService> it(*items2); it.current(); ++it)
   {
       items1->replace(it.current()->menuId(), it.current());
   }
}

void
VFolderMenu::matchItems(TQDict<KService> *items1, TQDict<KService> *items2)
{
   for(TQDictIterator<KService> it(*items1); it.current(); )
   {
       TQString id = it.current()->menuId();
       ++it;
       if (!items2->find(id))
          items1->remove(id);
   }
}

void
VFolderMenu::excludeItems(TQDict<KService> *items1, TQDict<KService> *items2)
{
   for(TQDictIterator<KService> it(*items2); it.current(); ++it)
   {
       items1->remove(it.current()->menuId());
   }
}

VFolderMenu::SubMenu*
VFolderMenu::takeSubMenu(SubMenu *parentMenu, const TQString &menuName)
{
   int i = menuName.find('/');
   TQString s1 = i > 0 ? menuName.left(i) : menuName;
   TQString s2 = menuName.mid(i+1);

   // Look up menu
   for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next())
   {
      if (menu->name == s1)
      {
         if (i == -1)
         {
            // Take it out
            return parentMenu->subMenus.take();
         }
         else
         {
            return takeSubMenu(menu, s2);
         }
      }
   }
   return 0; // Not found
}

void
VFolderMenu::mergeMenu(SubMenu *menu1, SubMenu *menu2, bool reversePriority)
{
   if (m_track)
   {
      track(m_trackId, menu1->name, &(menu1->items), &(menu1->excludeItems), &(menu2->items), TQString("Before MenuMerge w. %1 (incl)").arg(menu2->name));
      track(m_trackId, menu1->name, &(menu1->items), &(menu1->excludeItems), &(menu2->excludeItems), TQString("Before MenuMerge w. %1 (excl)").arg(menu2->name));
   }
   if (reversePriority)
   {
      // Merge menu1 with menu2, menu1 takes precedent
      excludeItems(&(menu2->items), &(menu1->excludeItems));
      includeItems(&(menu1->items), &(menu2->items));
      excludeItems(&(menu2->excludeItems), &(menu1->items));
      includeItems(&(menu1->excludeItems), &(menu2->excludeItems));
   }
   else
   {
      // Merge menu1 with menu2, menu2 takes precedent
      excludeItems(&(menu1->items), &(menu2->excludeItems));
      includeItems(&(menu1->items), &(menu2->items));
      includeItems(&(menu1->excludeItems), &(menu2->excludeItems));
      menu1->isDeleted = menu2->isDeleted;
   }
   for(; menu2->subMenus.first(); )
   {
      SubMenu *subMenu = menu2->subMenus.take();
      insertSubMenu(menu1, subMenu->name, subMenu, reversePriority);
   }

   if (reversePriority)
   {
      // Merge menu1 with menu2, menu1 takes precedent
      if (menu1->directoryFile.isEmpty())
         menu1->directoryFile = menu2->directoryFile;
      if (menu1->defaultLayoutNode.isNull())
         menu1->defaultLayoutNode = menu2->defaultLayoutNode;
      if (menu1->layoutNode.isNull())
         menu1->layoutNode = menu2->layoutNode;
   }
   else
   {
      // Merge menu1 with menu2, menu2 takes precedent
      if (!menu2->directoryFile.isEmpty())
         menu1->directoryFile = menu2->directoryFile;
      if (!menu2->defaultLayoutNode.isNull())
         menu1->defaultLayoutNode = menu2->defaultLayoutNode;
      if (!menu2->layoutNode.isNull())
         menu1->layoutNode = menu2->layoutNode;
   }

   if (m_track)
   {
      track(m_trackId, menu1->name, &(menu1->items), &(menu1->excludeItems), &(menu2->items), TQString("After MenuMerge w. %1 (incl)").arg(menu2->name));
      track(m_trackId, menu1->name, &(menu1->items), &(menu1->excludeItems), &(menu2->excludeItems), TQString("After MenuMerge w. %1 (excl)").arg(menu2->name));
   }

   delete menu2;
}

void
VFolderMenu::insertSubMenu(SubMenu *parentMenu, const TQString &menuName, SubMenu *newMenu, bool reversePriority)
{
   int i = menuName.find('/');

   TQString s1 = menuName.left(i);
   TQString s2 = menuName.mid(i+1);

   // Look up menu
   for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next())
   {
      if (menu->name == s1)
      {
         if (i == -1)
         {
            mergeMenu(menu, newMenu, reversePriority);
            return;
         }
         else
         {
            insertSubMenu(menu, s2, newMenu, reversePriority);
            return;
         }
      }
   }
   if (i == -1)
   {
     // Add it here
     newMenu->name = menuName;
     parentMenu->subMenus.append(newMenu);
   }
   else
   {
     SubMenu *menu = new SubMenu;
     menu->name = s1;
     parentMenu->subMenus.append(menu);
     insertSubMenu(menu, s2, newMenu);
   }
}

void
VFolderMenu::insertService(SubMenu *parentMenu, const TQString &name, KService *newService)
{
   int i = name.find('/');

   if (i == -1)
   {
     // Add it here
     parentMenu->items.replace(newService->menuId(), newService);
     return;
   }

   TQString s1 = name.left(i);
   TQString s2 = name.mid(i+1);

   // Look up menu
   for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next())
   {
      if (menu->name == s1)
      {
         insertService(menu, s2, newService);
         return;
      }
   }

   SubMenu *menu = new SubMenu;
   menu->name = s1;
   parentMenu->subMenus.append(menu);
   insertService(menu, s2, newService);
}


VFolderMenu::VFolderMenu() : m_usedAppsDict(797), m_track(false)
{
   m_rootMenu = 0;
   initDirs();
}

VFolderMenu::~VFolderMenu()
{
   delete m_rootMenu;
}

#define FOR_ALL_APPLICATIONS(it) \
   for(appsInfo *info = m_appsInfoStack.first(); \
       info; info = m_appsInfoStack.next()) \
   { \
      for(TQDictIterator<KService> it( info->applications ); \
          it.current(); ++it ) \
      {
#define FOR_ALL_APPLICATIONS_END } }

#define FOR_CATEGORY(category, it) \
   for(appsInfo *info = m_appsInfoStack.first(); \
       info; info = m_appsInfoStack.next()) \
   { \
      KService::List *list = info->dictCategories.find(category); \
      if (list) for(KService::List::ConstIterator it = list->begin(); \
             it != list->end(); ++it) \
      {
#define FOR_CATEGORY_END } }

KService *
VFolderMenu::findApplication(const TQString &relPath)
{
   for(appsInfo *info = m_appsInfoStack.first();
       info; info = m_appsInfoStack.next())
   {
      KService *s = info->applications.find(relPath);
      if (s)
         return s;
   }
   return 0;
}

void
VFolderMenu::addApplication(const TQString &id, KService *service)
{
   service->setMenuId(id);
   m_appsInfo->applications.replace(id, service);
}

void
VFolderMenu::buildApplicationIndex(bool unusedOnly)
{
   TQPtrList<appsInfo>::ConstIterator appsInfo_it =  m_appsInfoList.begin();
   for( ; appsInfo_it != m_appsInfoList.end(); ++appsInfo_it )
   {
      appsInfo *info = *appsInfo_it;
      info->dictCategories.clear();
      for(TQDictIterator<KService> it( info->applications );
          it.current(); )
      {
         KService *s = it.current();
         TQDictIterator<KService> tmpIt = it;
         ++it;
         if (unusedOnly && m_usedAppsDict.find(s->menuId()))
         {
            // Remove and skip this one
            info->applications.remove(tmpIt.currentKey());
            continue;
         }

         TQStringList cats = s->categories();
         for(TQStringList::ConstIterator it2 = cats.begin();
             it2 != cats.end(); ++it2)
         {
            const TQString &cat = *it2;
            KService::List *list = info->dictCategories.find(cat);
            if (!list)
            {
               list = new KService::List();
               info->dictCategories.insert(cat, list);
            }
            list->append(s);
         }
      }
   }
}

void
VFolderMenu::createAppsInfo()
{
   if (m_appsInfo) return;

   m_appsInfo = new appsInfo;
   m_appsInfoStack.prepend(m_appsInfo);
   m_appsInfoList.append(m_appsInfo);
   m_currentMenu->apps_info = m_appsInfo;
}

void
VFolderMenu::loadAppsInfo()
{
   m_appsInfo = m_currentMenu->apps_info;
   if (!m_appsInfo)
      return; // No appsInfo for this menu

   if (m_appsInfoStack.first() == m_appsInfo)
      return; // Already added (By createAppsInfo?)

   m_appsInfoStack.prepend(m_appsInfo); // Add
}

void
VFolderMenu::unloadAppsInfo()
{
   m_appsInfo = m_currentMenu->apps_info;
   if (!m_appsInfo)
      return; // No appsInfo for this menu

   if (m_appsInfoStack.first() != m_appsInfo)
   {
      return; // Already removed (huh?)
   }

   m_appsInfoStack.remove(m_appsInfo); // Remove
   m_appsInfo = 0;
}

TQString
VFolderMenu::absoluteDir(const TQString &_dir, const TQString &baseDir, bool keepRelativeToCfg)
{
   TQString dir = _dir;
   if (TQDir::isRelativePath(dir))
   {
      dir = baseDir + dir;
   }
   if (!dir.endsWith("/"))
      dir += '/';

   if (TQDir::isRelativePath(dir) && !keepRelativeToCfg)
   {
      dir = TDEGlobal::dirs()->findResource("xdgconf-menu", dir);
   }

   dir = TDEGlobal::dirs()->realPath(dir);

   return dir;
}

static void tagBaseDir(TQDomDocument &doc, const TQString &tag, const TQString &dir)
{
   TQDomNodeList mergeFileList = doc.elementsByTagName(tag);
   for(int i = 0; i < (int)mergeFileList.count(); i++)
   {
      TQDomAttr attr = doc.createAttribute("__BaseDir");
      attr.setValue(dir);
      mergeFileList.item(i).toElement().setAttributeNode(attr);
   }
}

static void tagBasePath(TQDomDocument &doc, const TQString &tag, const TQString &path)
{
   TQDomNodeList mergeFileList = doc.elementsByTagName(tag);
   for(int i = 0; i < (int)mergeFileList.count(); i++)
   {
      TQDomAttr attr = doc.createAttribute("__BasePath");
      attr.setValue(path);
      mergeFileList.item(i).toElement().setAttributeNode(attr);
   }
}

TQDomDocument
VFolderMenu::loadDoc()
{
   TQDomDocument doc;
   if ( m_docInfo.path.isEmpty() )
   {
      return doc;
   }
   TQFile file( m_docInfo.path );
   if ( !file.open( IO_ReadOnly ) )
   {
      kdWarning(7021) << "Could not open " << m_docInfo.path << endl;
      return doc;
   }
   TQString errorMsg;
   int errorRow;
   int errorCol;
   if ( !doc.setContent( &file, &errorMsg, &errorRow, &errorCol ) ) {
      kdWarning(7021) << "Parse error in " << m_docInfo.path << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg << endl;
      file.close();
      return doc;
   }
   file.close();

   tagBaseDir(doc, "MergeFile", m_docInfo.baseDir);
   tagBasePath(doc, "MergeFile", m_docInfo.path);
   tagBaseDir(doc, "MergeDir", m_docInfo.baseDir);
   tagBaseDir(doc, "DirectoryDir", m_docInfo.baseDir);
   tagBaseDir(doc, "AppDir", m_docInfo.baseDir);
   tagBaseDir(doc, "LegacyDir", m_docInfo.baseDir);

   return doc;
}


void
VFolderMenu::mergeFile(TQDomElement &parent, const TQDomNode &mergeHere)
{
kdDebug(7021) << "VFolderMenu::mergeFile: " << m_docInfo.path << endl;
   TQDomDocument doc = loadDoc();

   TQDomElement docElem = doc.documentElement();
   TQDomNode n = docElem.firstChild();
   TQDomNode last = mergeHere;
   while( !n.isNull() )
   {
      TQDomElement e = n.toElement(); // try to convert the node to an element.
      TQDomNode next = n.nextSibling();

      if (e.isNull())
      {
         // Skip
      }
      // The spec says we must ignore any Name nodes
      else if (e.tagName() != "Name")
      {
         parent.insertAfter(n, last);
         last = n;
      }

      docElem.removeChild(n);
      n = next;
   }
}


void
VFolderMenu::mergeMenus(TQDomElement &docElem, TQString &name)
{
   TQMap<TQString,TQDomElement> menuNodes;
   TQMap<TQString,TQDomElement> directoryNodes;
   TQMap<TQString,TQDomElement> appDirNodes;
   TQMap<TQString,TQDomElement> directoryDirNodes;
   TQMap<TQString,TQDomElement> legacyDirNodes;
   TQDomElement defaultLayoutNode;
   TQDomElement layoutNode;

   TQDomNode n = docElem.firstChild();
   while( !n.isNull() ) {
      TQDomElement e = n.toElement(); // try to convert the node to an element.
      if( e.isNull() ) {
// kdDebug(7021) << "Empty node" << endl;
      }
      else if( e.tagName() == "DefaultAppDirs") {
         // Replace with m_defaultAppDirs
         replaceNode(docElem, n, m_defaultAppDirs, "AppDir");
         continue;
      }
      else if( e.tagName() == "DefaultDirectoryDirs") {
         // Replace with m_defaultDirectoryDirs
         replaceNode(docElem, n, m_defaultDirectoryDirs, "DirectoryDir");
         continue;
      }
      else if( e.tagName() == "DefaultMergeDirs") {
         // Replace with m_defaultMergeDirs
         replaceNode(docElem, n, m_defaultMergeDirs, "MergeDir");
         continue;
      }
      else if( e.tagName() == "AppDir") {
         // Filter out dupes
         foldNode(docElem, e, appDirNodes);
      }
      else if( e.tagName() == "DirectoryDir") {
         // Filter out dupes
         foldNode(docElem, e, directoryDirNodes);
      }
      else if( e.tagName() == "LegacyDir") {
         // Filter out dupes
         foldNode(docElem, e, legacyDirNodes);
      }
      else if( e.tagName() == "Directory") {
         // Filter out dupes
         foldNode(docElem, e, directoryNodes);
      }
      else if( e.tagName() == "Move") {
         // Filter out dupes
         TQString orig;
         TQDomNode n2 = e.firstChild();
         while( !n2.isNull() ) {
            TQDomElement e2 = n2.toElement(); // try to convert the node to an element.
            if( e2.tagName() == "Old")
            {
               orig = e2.text();
               break;
            }
            n2 = n2.nextSibling();
         }
         foldNode(docElem, e, appDirNodes, orig);
      }
      else if( e.tagName() == "Menu") {
         TQString name;
         mergeMenus(e, name);
         TQMap<TQString,TQDomElement>::iterator it = menuNodes.find(name);
         if (it != menuNodes.end())
         {
           TQDomElement docElem2 = *it;
           TQDomNode n2 = docElem2.firstChild();
           TQDomNode first = e.firstChild();
           while( !n2.isNull() ) {
             TQDomElement e2 = n2.toElement(); // try to convert the node to an element.
             TQDomNode n3 = n2.nextSibling();
             e.insertBefore(n2, first);
             docElem2.removeChild(n2);
             n2 = n3;
           }
           // We still have duplicated Name entries
           // but we don't care about that

           docElem.removeChild(docElem2);
           menuNodes.remove(it);
         }
         menuNodes.insert(name, e);
      }
      else if( e.tagName() == "MergeFile") {
         if ((e.attribute("type") == "parent"))
            pushDocInfoParent(e.attribute("__BasePath"), e.attribute("__BaseDir"));
         else
            pushDocInfo(e.text(), e.attribute("__BaseDir"));

         if (!m_docInfo.path.isEmpty())
            mergeFile(docElem, n);
         popDocInfo();

         TQDomNode last = n;
         n = n.nextSibling();
         docElem.removeChild(last); // Remove the MergeFile node
         continue;
      }
      else if( e.tagName() == "MergeDir") {
         TQString dir = absoluteDir(e.text(), e.attribute("__BaseDir"), true);

         TQStringList dirs = TDEGlobal::dirs()->findDirs("xdgconf-menu", dir);
         for(TQStringList::ConstIterator it=dirs.begin();
             it != dirs.end(); ++it)
         {
            registerDirectory(*it);
         }

         TQStringList fileList;
         if (!TQDir::isRelativePath(dir))
         {
            // Absolute
            fileList = TDEGlobal::dirs()->findAllResources("xdgconf-menu", dir+"*.menu", false, false);
         }
         else
         {
            // Relative
            (void) TDEGlobal::dirs()->findAllResources("xdgconf-menu", dir+"*.menu", false, true, fileList);
         }

         for(TQStringList::ConstIterator it=fileList.begin();
             it != fileList.end(); ++it)
         {
            pushDocInfo(*it);
            mergeFile(docElem, n);
            popDocInfo();
         }

         TQDomNode last = n;
         n = n.nextSibling();
         docElem.removeChild(last); // Remove the MergeDir node

         continue;
      }
      else if( e.tagName() == "Name") {
         name = e.text();
      }
      else if( e.tagName() == "DefaultLayout") {
         if (!defaultLayoutNode.isNull())
            docElem.removeChild(defaultLayoutNode);
         defaultLayoutNode = e;
      }
      else if( e.tagName() == "Layout") {
         if (!layoutNode.isNull())
            docElem.removeChild(layoutNode);
         layoutNode = e;
      }
      n = n.nextSibling();
   }
}

void
VFolderMenu::pushDocInfo(const TQString &fileName, const TQString &baseDir)
{
   m_docInfoStack.push(m_docInfo);
   if (!baseDir.isEmpty())
   {
      if (!TQDir::isRelativePath(baseDir))
         m_docInfo.baseDir = TDEGlobal::dirs()->relativeLocation("xdgconf-menu", baseDir);
      else
         m_docInfo.baseDir = baseDir;
   }

   TQString baseName = fileName;
   if (!TQDir::isRelativePath(baseName))
      registerFile(baseName);
   else
      baseName = m_docInfo.baseDir + baseName;

   m_docInfo.path = locateMenuFile(fileName);
   if (m_docInfo.path.isEmpty())
   {
      m_docInfo.baseDir = TQString::null;
      m_docInfo.baseName = TQString::null;
      kdDebug(7021) << "Menu " << fileName << " not found." << endl;
      return;
   }
   int i;
   i = baseName.findRev('/');
   if (i > 0)
   {
      m_docInfo.baseDir = baseName.left(i+1);
      m_docInfo.baseName = baseName.mid(i+1, baseName.length() - i - 6);
   }
   else
   {
      m_docInfo.baseDir = TQString::null;
      m_docInfo.baseName = baseName.left( baseName.length() - 5 );
   }
}

void
VFolderMenu::pushDocInfoParent(const TQString &basePath, const TQString &baseDir)
{
    m_docInfoStack.push(m_docInfo);

   m_docInfo.baseDir = baseDir;

   TQString fileName = basePath.mid(basePath.findRev('/')+1);
   m_docInfo.baseName = fileName.left( fileName.length() - 5 );
   TQString baseName = TQDir::cleanDirPath(m_docInfo.baseDir + fileName);

   TQStringList result = TDEGlobal::dirs()->findAllResources("xdgconf-menu", baseName);

   while( !result.isEmpty() && (result[0] != basePath))
      result.remove(result.begin());

   if (result.count() <= 1)
   {
      m_docInfo.path = TQString::null; // No parent found
      return;
   }
   m_docInfo.path = result[1];
}

void
VFolderMenu::popDocInfo()
{
   m_docInfo = m_docInfoStack.pop();
}

TQString
VFolderMenu::locateMenuFile(const TQString &fileName)
{
   if (!TQDir::isRelativePath(fileName))
   {
      if (TDEStandardDirs::exists(fileName))
         return fileName;
      return TQString::null;
   }

   TQString result;

   //TQString xdgMenuPrefix = TQString::fromLocal8Bit(getenv("XDG_MENU_PREFIX"));
   // hardcode xdgMenuPrefix to "tde-" string until proper upstream fix
   TQString xdgMenuPrefix = "tde-";
   if (!xdgMenuPrefix.isEmpty())
   {
      TQFileInfo fileInfo(fileName);

      TQString fileNameOnly = fileInfo.fileName();
      if (!fileNameOnly.startsWith(xdgMenuPrefix))
         fileNameOnly = xdgMenuPrefix + fileNameOnly;

      TQString baseName = TQDir::cleanDirPath(m_docInfo.baseDir +
                                            fileInfo.dirPath() + "/" +
                                            fileNameOnly);
      result = locate("xdgconf-menu", baseName);
   }

   if (result.isEmpty())
   {
       TQString baseName = TQDir::cleanDirPath(m_docInfo.baseDir + fileName);
       result = locate("xdgconf-menu", baseName);
   }

   return result;
}

TQString
VFolderMenu::locateDirectoryFile(const TQString &fileName)
{
   if (fileName.isEmpty())
      return TQString::null;

   if (!TQDir::isRelativePath(fileName))
   {
      if (TDEStandardDirs::exists(fileName))
         return fileName;
      return TQString::null;
   }

   // First location in the list wins
   TQString tmp;
   for(TQStringList::ConstIterator it = m_directoryDirs.begin();
       it != m_directoryDirs.end();
       ++it)
   {
      tmp = (*it)+fileName;
      if (TDEStandardDirs::exists(tmp))
         return tmp;
   }

   return TQString::null;
}

void
VFolderMenu::initDirs()
{
   m_defaultDataDirs = TQStringList::split(':', TDEGlobal::dirs()->kfsstnd_prefixes());
   TQString localDir = m_defaultDataDirs.first();
   m_defaultDataDirs.remove(localDir); // Remove local dir

   m_defaultAppDirs = TDEGlobal::dirs()->findDirs("xdgdata-apps", TQString::null);
   m_defaultDirectoryDirs = TDEGlobal::dirs()->findDirs("xdgdata-dirs", TQString::null);
   m_defaultLegacyDirs = TDEGlobal::dirs()->resourceDirs("apps");
}

void
VFolderMenu::loadMenu(const TQString &fileName)
{
   m_defaultMergeDirs.clear();

   if (!fileName.endsWith(".menu"))
      return;

   pushDocInfo(fileName);
   m_defaultMergeDirs << m_docInfo.baseName+"-merged/";
   m_doc = loadDoc();
   popDocInfo();

   if (m_doc.isNull())
   {
      if (m_docInfo.path.isEmpty())
         kdError(7021) << fileName << " not found in " << m_allDirectories << endl;
      else
         kdWarning(7021) << "Load error (" << m_docInfo.path << ")" << endl;
      return;
   }

   TQDomElement e = m_doc.documentElement();
   TQString name;
   mergeMenus(e, name);
}

void
VFolderMenu::processCondition(TQDomElement &domElem, TQDict<KService> *items)
{
   if (domElem.tagName() == "And")
   {
      TQDomNode n = domElem.firstChild();
      // Look for the first child element
      while (!n.isNull()) // loop in case of comments
      {
         TQDomElement e = n.toElement();
         n = n.nextSibling();
         if ( !e.isNull() ) {
             processCondition(e, items);
             break; // we only want the first one
         }
      }

      TQDict<KService> andItems;
      while( !n.isNull() ) {
         TQDomElement e = n.toElement();
         if (e.tagName() == "Not")
         {
            // Special handling for "and not"
            TQDomNode n2 = e.firstChild();
            while( !n2.isNull() ) {
               TQDomElement e2 = n2.toElement();
               andItems.clear();
               processCondition(e2, &andItems);
               excludeItems(items, &andItems);
               n2 = n2.nextSibling();
            }
         }
         else
         {
            andItems.clear();
            processCondition(e, &andItems);
            matchItems(items, &andItems);
         }
         n = n.nextSibling();
      }
   }
   else if (domElem.tagName() == "Or")
   {
      TQDomNode n = domElem.firstChild();
      // Look for the first child element
      while (!n.isNull()) // loop in case of comments
      {
         TQDomElement e = n.toElement();
         n = n.nextSibling();
         if ( !e.isNull() ) {
             processCondition(e, items);
             break; // we only want the first one
         }
      }

      TQDict<KService> orItems;
      while( !n.isNull() ) {
         TQDomElement e = n.toElement();
         if ( !e.isNull() ) {
             orItems.clear();
             processCondition(e, &orItems);
             includeItems(items, &orItems);
         }
         n = n.nextSibling();
      }
   }
   else if (domElem.tagName() == "Not")
   {
      FOR_ALL_APPLICATIONS(it)
      {
         KService *s = it.current();
         items->replace(s->menuId(), s);
      }
      FOR_ALL_APPLICATIONS_END

      TQDict<KService> notItems;
      TQDomNode n = domElem.firstChild();
      while( !n.isNull() ) {
         TQDomElement e = n.toElement();
         if ( !e.isNull() ) {
             notItems.clear();
             processCondition(e, &notItems);
             excludeItems(items, &notItems);
         }
         n = n.nextSibling();
      }
   }
   else if (domElem.tagName() == "Category")
   {
      FOR_CATEGORY(domElem.text(), it)
      {
         KService *s = *it;
         items->replace(s->menuId(), s);
      }
      FOR_CATEGORY_END
   }
   else if (domElem.tagName() == "All")
   {
      FOR_ALL_APPLICATIONS(it)
      {
         KService *s = it.current();
         items->replace(s->menuId(), s);
      }
      FOR_ALL_APPLICATIONS_END
   }
   else if (domElem.tagName() == "Filename")
   {
      TQString filename = domElem.text();
kdDebug(7021) << "Adding file " << filename << endl;
      KService *s = findApplication(filename);
      if (s)
         items->replace(filename, s);
   }
}

void
VFolderMenu::loadApplications(const TQString &dir, const TQString &prefix)
{
   kdDebug(7021) << "Looking up applications under " << dir << endl;

   // We look for a set of files.
   DIR *dp = opendir( TQFile::encodeName(dir));
   if (!dp)
      return;

   struct dirent *ep;
   KDE_struct_stat buff;

   TQString _dot(".");
   TQString _dotdot("..");

   while( ( ep = readdir( dp ) ) != 0L )
   {
      TQString fn( TQFile::decodeName(ep->d_name));
      if (fn == _dot || fn == _dotdot || TQChar(fn.at(fn.length() - 1)).latin1() == '~')
         continue;

      TQString pathfn = dir + fn;
      if ( KDE_stat( TQFile::encodeName(pathfn), &buff ) != 0 ) {
         continue; // Couldn't stat (e.g. no read permissions)
      }
      if ( S_ISDIR( buff.st_mode )) {
         loadApplications(pathfn + '/', prefix + fn + '-');
         continue;
      }

      if ( S_ISREG( buff.st_mode))
      {
         if (!fn.endsWith(".desktop"))
            continue;

         KService *service = 0;
         emit newService(pathfn, &service);
         if (service)
            addApplication(prefix+fn, service);
      }
    }
    closedir( dp );
}

void
VFolderMenu::processKDELegacyDirs()
{
kdDebug(7021) << "processKDELegacyDirs()" << endl;

   TQDict<KService> items;
   TQString prefix = "tde-";

   TQStringList relFiles;
   TQRegExp files("\\.(desktop|kdelnk)$");
   TQRegExp dirs("\\.directory$");

   (void) TDEGlobal::dirs()->findAllResources( "apps",
                                             TQString::null,
                                             true, // Recursive!
                                             true, // uniq
                                             relFiles);
   for(TQStringList::ConstIterator it = relFiles.begin();
       it != relFiles.end(); ++it)
   {
      if (!m_forcedLegacyLoad && (dirs.search(*it) != -1))
      {
         TQString name = *it;
         if (!name.endsWith("/.directory"))
            continue; // Probably ".directory", skip it.

         name = name.left(name.length()-11);

         SubMenu *newMenu = new SubMenu;
         newMenu->directoryFile = locate("apps", *it);

         insertSubMenu(m_currentMenu, name, newMenu);
         continue;
      }

      if (files.search(*it) != -1)
      {
         TQString name = *it;
         KService *service = 0;
         emit newService(name, &service);

         if (service && !m_forcedLegacyLoad)
         {
            TQString id = name;
            // Strip path from id
            int i = id.findRev('/');
            if (i >= 0)
               id = id.mid(i+1);

            id.prepend(prefix);

            // TODO: add Legacy category
            addApplication(id, service);
            items.replace(service->menuId(), service);
            if (service->categories().isEmpty())
               insertService(m_currentMenu, name, service);

         }
      }
   }
   markUsedApplications(&items);
   m_legacyLoaded = true;
}

void
VFolderMenu::processLegacyDir(const TQString &dir, const TQString &relDir, const TQString &prefix)
{
kdDebug(7021) << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")" << endl;

   TQDict<KService> items;
   // We look for a set of files.
   DIR *dp = opendir( TQFile::encodeName(dir));
   if (!dp)
      return;

   struct dirent *ep;
   KDE_struct_stat buff;

   TQString _dot(".");
   TQString _dotdot("..");

   while( ( ep = readdir( dp ) ) != 0L )
   {
      TQString fn( TQFile::decodeName(ep->d_name));
      if (fn == _dot || fn == _dotdot || TQChar(fn.at(fn.length() - 1)).latin1() == '~')
         continue;

      TQString pathfn = dir + fn;
      if ( KDE_stat( TQFile::encodeName(pathfn), &buff ) != 0 ) {
         continue; // Couldn't stat (e.g. no read permissions)
      }
      if ( S_ISDIR( buff.st_mode )) {
         SubMenu *parentMenu = m_currentMenu;

         m_currentMenu = new SubMenu;
         m_currentMenu->name = fn;
         m_currentMenu->directoryFile = dir + fn + "/.directory";

         parentMenu->subMenus.append(m_currentMenu);

         processLegacyDir(pathfn + '/', relDir+fn+'/', prefix);
         m_currentMenu = parentMenu;
         continue;
      }

      if ( S_ISREG( buff.st_mode))
      {
         if (!fn.endsWith(".desktop"))
            continue;

         KService *service = 0;
         emit newService(pathfn, &service);
         if (service)
         {
            TQString id = prefix+fn;

            // TODO: Add legacy category
            addApplication(id, service);
            items.replace(service->menuId(), service);

            if (service->categories().isEmpty())
               m_currentMenu->items.replace(id, service);
         }
      }
    }
    closedir( dp );
    markUsedApplications(&items);
}



void
VFolderMenu::processMenu(TQDomElement &docElem, int pass)
{
   SubMenu *parentMenu = m_currentMenu;
   unsigned int oldDirectoryDirsCount = m_directoryDirs.count();

   TQString name;
   TQString directoryFile;
   bool onlyUnallocated = false;
   bool isDeleted = false;
   bool kdeLegacyDirsDone = false;
   TQDomElement defaultLayoutNode;
   TQDomElement layoutNode;

   TQDomElement query;
   TQDomNode n = docElem.firstChild();
   while( !n.isNull() ) {
      TQDomElement e = n.toElement(); // try to convert the node to an element.
      if (e.tagName() == "Name")
      {
         name = e.text();
      }
      else if (e.tagName() == "Directory")
      {
         directoryFile = e.text();
      }
      else if (e.tagName() == "DirectoryDir")
      {
         TQString dir = absoluteDir(e.text(), e.attribute("__BaseDir"));

         m_directoryDirs.prepend(dir);
      }
      else if (e.tagName() == "OnlyUnallocated")
      {
         onlyUnallocated = true;
      }
      else if (e.tagName() == "NotOnlyUnallocated")
      {
         onlyUnallocated = false;
      }
      else if (e.tagName() == "Deleted")
      {
         isDeleted = true;
      }
      else if (e.tagName() == "NotDeleted")
      {
         isDeleted = false;
      }
      else if (e.tagName() == "DefaultLayout")
      {
         defaultLayoutNode = e;
      }
      else if (e.tagName() == "Layout")
      {
         layoutNode = e;
      }
      n = n.nextSibling();
   }

   // Setup current menu entry
   if (pass == 0)
   {
      m_currentMenu = 0;
      // Look up menu
      if (parentMenu)
      {
         for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next())
         {
            if (menu->name == name)
            {
               m_currentMenu = menu;
               break;
            }
         }
      }

      if (!m_currentMenu) // Not found?
      {
         // Create menu
         m_currentMenu = new SubMenu;
         m_currentMenu->name = name;

         if (parentMenu)
            parentMenu->subMenus.append(m_currentMenu);
         else
            m_rootMenu = m_currentMenu;
      }
      if (directoryFile.isEmpty())
      {
         kdDebug(7021) << "Menu " << name << " does not specify a directory file." << endl;
      }

      // Override previous directoryFile iff available
      TQString tmp = locateDirectoryFile(directoryFile);
      if (! tmp.isEmpty())
         m_currentMenu->directoryFile = tmp;
      m_currentMenu->isDeleted = isDeleted;

      m_currentMenu->defaultLayoutNode = defaultLayoutNode;
      m_currentMenu->layoutNode = layoutNode;
   }
   else
   {
      // Look up menu
      if (parentMenu)
      {
         for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next())
         {
            if (menu->name == name)
            {
               m_currentMenu = menu;
               break;
            }
         }
      }
      else
      {
         m_currentMenu = m_rootMenu;
      }
   }

   // Process AppDir and LegacyDir
   if (pass == 0)
   {
      TQDomElement query;
      TQDomNode n = docElem.firstChild();
      while( !n.isNull() ) {
         TQDomElement e = n.toElement(); // try to convert the node to an element.
         if (e.tagName() == "AppDir")
         {
            createAppsInfo();
            TQString dir = absoluteDir(e.text(), e.attribute("__BaseDir"));

            registerDirectory(dir);

            loadApplications(dir, TQString::null);
         }
         else if (e.tagName() == "KDELegacyDirs")
         {
            createAppsInfo();
            if (!kdeLegacyDirsDone)
            {
kdDebug(7021) << "Processing KDE Legacy dirs for <KDE>" << endl;
               SubMenu *oldMenu = m_currentMenu;
               m_currentMenu = new SubMenu;

               processKDELegacyDirs();

               m_legacyNodes.replace("<KDE>", m_currentMenu);
               m_currentMenu = oldMenu;

               kdeLegacyDirsDone = true;
            }
         }
         else if (e.tagName() == "LegacyDir")
         {
            createAppsInfo();
            TQString dir = absoluteDir(e.text(), e.attribute("__BaseDir"));

            TQString prefix = e.attributes().namedItem("prefix").toAttr().value();

            if (m_defaultLegacyDirs.contains(dir))
            {
               if (!kdeLegacyDirsDone)
               {
kdDebug(7021) << "Processing KDE Legacy dirs for " << dir << endl;
                  SubMenu *oldMenu = m_currentMenu;
                  m_currentMenu = new SubMenu;

                  processKDELegacyDirs();

                  m_legacyNodes.replace("<KDE>", m_currentMenu);
                  m_currentMenu = oldMenu;

                  kdeLegacyDirsDone = true;
               }
            }
            else
            {
               SubMenu *oldMenu = m_currentMenu;
               m_currentMenu = new SubMenu;

               registerDirectory(dir);

               processLegacyDir(dir, TQString::null, prefix);

               m_legacyNodes.replace(dir, m_currentMenu);
               m_currentMenu = oldMenu;
            }
         }
         n = n.nextSibling();
      }
   }

   loadAppsInfo(); // Update the scope wrt the list of applications

   if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated))
   {
      n = docElem.firstChild();

      while( !n.isNull() ) {
         TQDomElement e = n.toElement(); // try to convert the node to an element.
         if (e.tagName() == "Include")
         {
            TQDict<KService> items;

            TQDomNode n2 = e.firstChild();
            while( !n2.isNull() ) {
               TQDomElement e2 = n2.toElement();
               items.clear();
               processCondition(e2, &items);
               if (m_track)
                  track(m_trackId, m_currentMenu->name, &(m_currentMenu->items), &(m_currentMenu->excludeItems), &items, "Before <Include>");
               includeItems(&(m_currentMenu->items), &items);
               excludeItems(&(m_currentMenu->excludeItems), &items);
               markUsedApplications(&items);

               if (m_track)
                  track(m_trackId, m_currentMenu->name, &(m_currentMenu->items), &(m_currentMenu->excludeItems), &items, "After <Include>");

               n2 = n2.nextSibling();
            }
         }

         else if (e.tagName() == "Exclude")
         {
            TQDict<KService> items;

            TQDomNode n2 = e.firstChild();
            while( !n2.isNull() ) {
               TQDomElement e2 = n2.toElement();
               items.clear();
               processCondition(e2, &items);
               if (m_track)
                  track(m_trackId, m_currentMenu->name, &(m_currentMenu->items), &(m_currentMenu->excludeItems), &items, "Before <Exclude>");
               excludeItems(&(m_currentMenu->items), &items);
               includeItems(&(m_currentMenu->excludeItems), &items);
               if (m_track)
                  track(m_trackId, m_currentMenu->name, &(m_currentMenu->items), &(m_currentMenu->excludeItems), &items, "After <Exclude>");
               n2 = n2.nextSibling();
            }
         }

         n = n.nextSibling();
      }
   }

   n = docElem.firstChild();
   while( !n.isNull() ) {
      TQDomElement e = n.toElement(); // try to convert the node to an element.
      if (e.tagName() == "Menu")
      {
         processMenu(e, pass);
      }
// We insert legacy dir in pass 0, this way the order in the .menu-file determines
// which .directory file gets used, but the menu-entries of legacy-menus will always
// have the lowest priority.
//      else if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated))
      else if (pass == 0)
      {
         if (e.tagName() == "LegacyDir")
         {
            // Add legacy nodes to Menu structure
            TQString dir = absoluteDir(e.text(), e.attribute("__BaseDir"));
            SubMenu *legacyMenu = m_legacyNodes.find(dir);
            if (legacyMenu)
            {
               mergeMenu(m_currentMenu, legacyMenu);
            }
         }

         else if (e.tagName() == "KDELegacyDirs")
         {
            // Add legacy nodes to Menu structure
            TQString dir = "<KDE>";
            SubMenu *legacyMenu = m_legacyNodes.find(dir);
            if (legacyMenu)
            {
               mergeMenu(m_currentMenu, legacyMenu);
            }
         }
      }
      n = n.nextSibling();
   }

   if (pass == 2)
   {
      n = docElem.firstChild();
      while( !n.isNull() ) {
         TQDomElement e = n.toElement(); // try to convert the node to an element.
         if (e.tagName() == "Move")
         {
            TQString orig;
            TQString dest;
            TQDomNode n2 = e.firstChild();
            while( !n2.isNull() ) {
               TQDomElement e2 = n2.toElement(); // try to convert the node to an element.
               if( e2.tagName() == "Old")
                  orig = e2.text();
               if( e2.tagName() == "New")
                  dest = e2.text();
               n2 = n2.nextSibling();
            }
            kdDebug(7021) << "Moving " << orig << " to " << dest << endl;
            if (!orig.isEmpty() && !dest.isEmpty())
            {
              SubMenu *menu = takeSubMenu(m_currentMenu, orig);
              if (menu)
              {
                insertSubMenu(m_currentMenu, dest, menu, true); // Destination has priority
              }
            }
         }
         n = n.nextSibling();
      }

   }

   unloadAppsInfo(); // Update the scope wrt the list of applications

   while (m_directoryDirs.count() > oldDirectoryDirsCount)
      m_directoryDirs.pop_front();

   m_currentMenu = parentMenu;
}



static TQString parseAttribute( const TQDomElement &e)
{
    TQString option;
    if ( e.hasAttribute( "show_empty" ) )
    {
        TQString str = e.attribute( "show_empty" );
        if ( str=="true" )
            option= "ME ";
        else if ( str=="false" )
            option= "NME ";
        else
            kdDebug()<<" Error in parsing show_empty attribute :"<<str<<endl;
    }
    if ( e.hasAttribute( "inline" ) )
    {
        TQString str = e.attribute( "inline" );
        if (  str=="true" )
            option+="I ";
        else if ( str=="false" )
            option+="NI ";
        else
            kdDebug()<<" Error in parsing inlibe attribute :"<<str<<endl;
    }
    if ( e.hasAttribute( "inline_limit" ) )
    {
        bool ok;
        int value = e.attribute( "inline_limit" ).toInt(&ok);
        if ( ok )
            option+=TQString( "IL[%1] " ).arg( value );
    }
    if ( e.hasAttribute( "inline_header" ) )
    {
        TQString str = e.attribute( "inline_header" );
        if ( str=="true")
            option+="IH ";
        else if ( str == "false" )
            option+="NIH ";
        else
            kdDebug()<<" Error in parsing of inline_header attribute :"<<str<<endl;

    }
    if ( e.hasAttribute( "inline_alias" ) && e.attribute( "inline_alias" )=="true")
    {
        TQString str = e.attribute( "inline_alias" );
        if ( str=="true" )
            option+="IA";
        else if ( str=="false" )
            option+="NIA";
        else
            kdDebug()<<" Error in parsing inline_alias attribute :"<<str<<endl;
    }
    if( !option.isEmpty())
    {
        option = option.prepend(":O");
    }
    return option;

}

static TQStringList parseLayoutNode(const TQDomElement &docElem)
{
   TQStringList layout;

   TQString optionDefaultLayout;
   if( docElem.tagName()=="DefaultLayout")
       optionDefaultLayout =  parseAttribute( docElem);
   if ( !optionDefaultLayout.isEmpty() )
       layout.append( optionDefaultLayout );

   TQDomNode n = docElem.firstChild();
   while( !n.isNull() ) {
      TQDomElement e = n.toElement(); // try to convert the node to an element.
      if (e.tagName() == "Separator")
      {
         layout.append(":S");
      }
      else if (e.tagName() == "Filename")
      {
         layout.append(e.text());
      }
      else if (e.tagName() == "Menuname")
      {
         layout.append("/"+e.text());
         TQString option = parseAttribute( e );
         if( !option.isEmpty())
             layout.append( option );
      }
      else if (e.tagName() == "Merge")
      {
         TQString type = e.attributeNode("type").value();
         if (type == "files")
            layout.append(":F");
         else if (type == "menus")
            layout.append(":M");
         else if (type == "all")
            layout.append(":A");
      }

      n = n.nextSibling();
   }
   return layout;
}

void
VFolderMenu::layoutMenu(VFolderMenu::SubMenu *menu, TQStringList defaultLayout)
{
   if (!menu->defaultLayoutNode.isNull())
   {
      defaultLayout = parseLayoutNode(menu->defaultLayoutNode);
   }

   if (menu->layoutNode.isNull())
   {
     menu->layoutList = defaultLayout;
   }
   else
   {
     menu->layoutList = parseLayoutNode(menu->layoutNode);
     if (menu->layoutList.isEmpty())
        menu->layoutList = defaultLayout;
   }

   for(VFolderMenu::SubMenu *subMenu = menu->subMenus.first(); subMenu; subMenu = menu->subMenus.next())
   {
      layoutMenu(subMenu, defaultLayout);
   }
}

void
VFolderMenu::markUsedApplications(TQDict<KService> *items)
{
   for(TQDictIterator<KService> it(*items); it.current(); ++it)
   {
      m_usedAppsDict.replace(it.current()->menuId(), it.current());
   }
}

VFolderMenu::SubMenu *
VFolderMenu::parseMenu(const TQString &file, bool forceLegacyLoad)
{
   m_forcedLegacyLoad = false;
   m_legacyLoaded = false;
   m_appsInfo = 0;

   TQStringList dirs = TDEGlobal::dirs()->resourceDirs("xdgconf-menu");
   for(TQStringList::ConstIterator it=dirs.begin();
       it != dirs.end(); ++it)
   {
      registerDirectory(*it);
   }

   loadMenu(file);

   delete m_rootMenu;
   m_rootMenu = m_currentMenu = 0;

   TQDomElement docElem = m_doc.documentElement();

   for (int pass = 0; pass <= 2; pass++)
   {
      processMenu(docElem, pass);

      if (pass == 0)
      {
         buildApplicationIndex(false);
      }
      if (pass == 1)
      {
         buildApplicationIndex(true);
      }
      if (pass == 2)
      {
         TQStringList defaultLayout;
         defaultLayout << ":M"; // Sub-Menus
         defaultLayout << ":F"; // Individual entries
         layoutMenu(m_rootMenu, defaultLayout);
      }
   }

   if (!m_legacyLoaded && forceLegacyLoad)
   {
      m_forcedLegacyLoad = true;
      processKDELegacyDirs();
   }

   return m_rootMenu;
}

void
VFolderMenu::setTrackId(const TQString &id)
{
   m_track = !id.isEmpty();
   m_trackId = id;
}

#include "vfolder_menu.moc"
