/*
  This file is part of the KDE libraries
  Copyright (c) 1999 Preston Brown <pbrown@kde.org>
  Copyright (C) 1997-1999 Matthias Kalle Dalheimer (kalle@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 as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  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.
*/

// $Id$

#include <config.h>

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#include <stdlib.h>
#include <unistd.h>

#include <tqfileinfo.h>

#include <kapplication.h>
#include "kconfigbackend.h"

#include "kconfig.h"
#include "kglobal.h"
#include "kstandarddirs.h"
#include "kstaticdeleter.h"
#include <tqtimer.h>

KConfig::KConfig( const TQString& fileName,
                 bool bReadOnly, bool bUseKderc, const char *resType )
  : KConfigBase(), bGroupImmutable(false), bFileImmutable(false),
    bForceGlobal(false)
{
  // set the object's read-only status.
  setReadOnly(bReadOnly);

  // for right now we will hardcode that we are using the INI
  // back end driver.  In the future this should be converted over to
  // a object factory of some sorts.
  KConfigINIBackEnd *aBackEnd = new KConfigINIBackEnd(this,
						      fileName,
                                                      resType,
						      bUseKderc);

  // set the object's back end pointer to this new backend
  backEnd = aBackEnd;

  // read initial information off disk
  reparseConfiguration();

  // we let KStandardDirs add custom user config files. It will do
  // this only once. So only the first call ever to this constructor
  // will anything else than return here We have to reparse here as
  // configuration files may appear after customized directories have
  // been added. and the info they contain needs to be inserted into the
  // config object.
  // Since this makes only sense for config directories, addCustomized
  // returns true only if new config directories appeared.
  if (KGlobal::dirs()->addCustomized(this))
      reparseConfiguration();
}

KConfig::KConfig(KConfigBackEnd *aBackEnd, bool bReadOnly)
    : bGroupImmutable(false), bFileImmutable(false),
    bForceGlobal(false)
{
  setReadOnly(bReadOnly);
  backEnd = aBackEnd;
  reparseConfiguration();
}

KConfig::~KConfig()
{
  sync();

  delete backEnd;
}

void KConfig::rollback(bool bDeep)
{
  KConfigBase::rollback(bDeep);

  if (!bDeep)
    return; // object's bDeep flag is set in KConfigBase method

  // clear any dirty flags that entries might have set
  for (KEntryMapIterator aIt = aEntryMap.begin();
       aIt != aEntryMap.end(); ++aIt)
    (*aIt).bDirty = false;
}

TQStringList KConfig::groupList() const
{
  TQStringList retList;

  KEntryMapConstIterator aIt = aEntryMap.begin();
  KEntryMapConstIterator aEnd = aEntryMap.end();
  for (; aIt != aEnd; ++aIt)
  {
    while(aIt.key().mKey.isEmpty())
    {
      TQCString group = aIt.key().mGroup;
      ++aIt;
      while (true)
      {
         if (aIt == aEnd)
            return retList; // done

         if (aIt.key().mKey.isEmpty())
            break; // Group is empty, next group

         if (!aIt.key().bDefault && !(*aIt).bDeleted)
         {
            if (group != "$Version") // Special case!
               retList.append(TQString::fromUtf8(group));
            break; // Group is non-empty, added, next group
         }
         ++aIt;
      }
    }
  }

  return retList;
}

TQMap<TQString, TQString> KConfig::entryMap(const TQString &pGroup) const
{
  TQCString pGroup_utf = pGroup.utf8();
  KEntryKey groupKey( pGroup_utf, 0 );
  TQMap<TQString, TQString> tmpMap;

  KEntryMapConstIterator aIt = aEntryMap.find(groupKey);
  if (aIt == aEntryMap.end())
     return tmpMap;
  ++aIt; // advance past special group entry marker
  for (; aIt.key().mGroup == pGroup_utf && aIt != aEntryMap.end(); ++aIt)
  {
    // Leave the default values out && leave deleted entries out
    if (!aIt.key().bDefault && !(*aIt).bDeleted)
      tmpMap.insert(TQString::fromUtf8(aIt.key().mKey), TQString::fromUtf8((*aIt).mValue.data(), (*aIt).mValue.length()));
  }

  return tmpMap;
}

void KConfig::reparseConfiguration()
{
  // Don't lose pending changes
  if (!isReadOnly() && backEnd && bDirty)
    backEnd->sync();

  aEntryMap.clear();

  // add the "default group" marker to the map
  KEntryKey groupKey("<default>", 0);
  aEntryMap.insert(groupKey, KEntry());

  bFileImmutable = false;
  parseConfigFiles();
  bFileImmutable = bReadOnly;
}

KEntryMap KConfig::internalEntryMap(const TQString &pGroup) const
{
  TQCString pGroup_utf = pGroup.utf8();
  KEntry aEntry;
  KEntryMapConstIterator aIt;
  KEntryKey aKey(pGroup_utf, 0);
  KEntryMap tmpEntryMap;

  aIt = aEntryMap.find(aKey);
  if (aIt == aEntryMap.end()) {
    // the special group key is not in the map,
    // so it must be an invalid group.  Return
    // an empty map.
    return tmpEntryMap;
  }
  // we now have a pointer to the nodes we want to copy.
  for (; aIt.key().mGroup == pGroup_utf && aIt != aEntryMap.end(); ++aIt)
  {
    tmpEntryMap.insert(aIt.key(), *aIt);
  }

  return tmpEntryMap;
}

void KConfig::putData(const KEntryKey &_key, const KEntry &_data, bool _checkGroup)
{
  if (bFileImmutable && !_key.bDefault)
    return;

  // check to see if the special group key is present,
  // and if not, put it in.
  if (_checkGroup)
  {
    KEntryKey groupKey( _key.mGroup, 0);
    KEntry &entry = aEntryMap[groupKey];
    bGroupImmutable = entry.bImmutable;
  }
  if (bGroupImmutable && !_key.bDefault)
    return;

  // now either add or replace the data
  KEntry &entry = aEntryMap[_key];
  bool immutable = entry.bImmutable;
  if (immutable && !_key.bDefault)
    return;

  entry = _data;
  entry.bImmutable |= immutable;
  entry.bGlobal |= bForceGlobal; // force to kdeglobals

  if (_key.bDefault)
  {
     // We have added the data as default value,
     // add it as normal value as well.
     KEntryKey key(_key);
     key.bDefault = false;
     aEntryMap[key] = _data;
  }
}

KEntry KConfig::lookupData(const KEntryKey &_key) const
{
  KEntryMapConstIterator aIt = aEntryMap.find(_key);
  if (aIt != aEntryMap.end())
  {
    const KEntry &entry = *aIt;
    if (entry.bDeleted)
       return KEntry();
    else
       return entry;
  }
  else {
    return KEntry();
  }
}

bool KConfig::internalHasGroup(const TQCString &group) const
{
  KEntryKey groupKey( group, 0);

  KEntryMapConstIterator aIt = aEntryMap.find(groupKey);
  KEntryMapConstIterator aEnd = aEntryMap.end();

  if (aIt == aEnd)
     return false;
  ++aIt;
  for(; (aIt != aEnd); ++aIt)
  {
     if (aIt.key().mKey.isEmpty())
        break;

     if (!aIt.key().bDefault && !(*aIt).bDeleted)
        return true;
  }
  return false;
}

void KConfig::setFileWriteMode(int mode)
{
  backEnd->setFileWriteMode(mode);
}

KLockFile::Ptr KConfig::lockFile(bool bGlobal)
{
  KConfigINIBackEnd *aBackEnd = dynamic_cast<KConfigINIBackEnd*>(backEnd);
  if (!aBackEnd) return 0;
  return aBackEnd->lockFile(bGlobal);
}

void KConfig::checkUpdate(const TQString &id, const TQString &updateFile)
{
  TQString oldGroup = group();
  setGroup("$Version");
  TQString cfg_id = updateFile+":"+id;
  TQStringList ids = readListEntry("update_info");
  if (!ids.contains(cfg_id))
  {
     TQStringList args;
     args << "--check" << updateFile;
     KApplication::kdeinitExecWait("kconf_update", args);
     reparseConfiguration();
  }
  setGroup(oldGroup);
}

KConfig* KConfig::copyTo(const TQString &file, KConfig *config) const
{
  if (!config)
     config = new KConfig(TQString::null, false, false);
  config->backEnd->changeFileName(file, "config", false);
  config->setReadOnly(false);
  config->bFileImmutable = false;
  config->backEnd->mConfigState = ReadWrite;

  TQStringList groups = groupList();
  for(TQStringList::ConstIterator it = groups.begin();
      it != groups.end(); ++it)
  {
     TQMap<TQString, TQString> map = entryMap(*it);
     config->setGroup(*it);
     for (TQMap<TQString,TQString>::Iterator it2  = map.begin();
          it2 != map.end(); ++it2)
     {
        config->writeEntry(it2.key(), it2.data());
     }

  }
  return config;
}

void KConfig::virtual_hook( int id, void* data )
{ KConfigBase::virtual_hook( id, data ); }

static KStaticDeleter< TQValueList<KSharedConfig*> > sd;
TQValueList<KSharedConfig*> *KSharedConfig::s_list = 0;

KSharedConfig::Ptr KSharedConfig::openConfig(const TQString& fileName, bool readOnly, bool useKDEGlobals )
{
  if (s_list)
  {
     for(TQValueList<KSharedConfig*>::ConstIterator it = s_list->begin();
         it != s_list->end(); ++it)
     {
        if ((*it)->backEnd->fileName() == fileName &&
                (*it)->bReadOnly == readOnly &&
                (*it)->backEnd->useKDEGlobals == useKDEGlobals )
           return (*it);
     }
  }
  return new KSharedConfig(fileName, readOnly, useKDEGlobals);
}

KSharedConfig::KSharedConfig( const TQString& fileName, bool readonly, bool usekdeglobals)
 : KConfig(fileName, readonly, usekdeglobals)
{
  if (!s_list)
  {
    sd.setObject(s_list, new TQValueList<KSharedConfig*>);
  }

  s_list->append(this);
}

KSharedConfig::~KSharedConfig()
{
  if ( s_list )
    s_list->remove(this);
}

#include "kconfig.moc"
