/***************************************************************************
                          kmessconfig.h  -  description
                             -------------------
    begin                : Thu May 1 2008
    copyright            : (C) 2008 by Antonio Nastasi
    email                : sifcenter@gmail.com
 ***************************************************************************/

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

#include "kmessconfig.h"
#include "../contact/msnobject.h"
#include "../notification/notificationmanager.h"
#include "../kmessdebug.h"

#include <QDir>

#include <KLocale>
#include <KStandardDirs>


// Initialize the instance to zero
KMessConfig* KMessConfig::instance_(0);



// Constructor
KMessConfig::KMessConfig()
: tempConfigFile_( 0 )
{
}



// Destructor
KMessConfig::~KMessConfig()
{
  // Force the saving to be sure the config is written
  KConfig *config;
  foreach( config, accountsConfigs_ )
  {
    config->sync();
  }
  foreach( config, listsConfigs_ )
  {
    config->sync();
  }

  // Clean up the instance
  qDeleteAll( accountsConfigs_ );
  qDeleteAll( listsConfigs_ );
  accountsConfigs_.clear();
  listsConfigs_.clear();
  instance_ = 0;

  delete tempConfigFile_;

  kDebug() << "DESTROYED";
}



void KMessConfig::destroy()
{
  delete instance_;
  instance_ = 0;
}



// Recursively delete an account's configuration directory and all of its contents
bool KMessConfig::destroyConfigDir( const QString &fullDirPath )
{
  QDir dir( fullDirPath );
  QList<QFileInfo> filesList = dir.entryInfoList( QDir::AllEntries | QDir::NoDotAndDotDot );
  foreach( QFileInfo entry, filesList )
  {
    QString entryPath( entry.absoluteFilePath() );

    if( entry.isDir() )
    {
      if( ! destroyConfigDir( entryPath ) )
      {
        kWarning() << "Cannot remove directory entry" << entryPath << "!";
      }
    }
    else
    {
      dir.remove( entryPath );
    }
  }

  dir.cdUp();
  if( ! dir.rmdir( fullDirPath ) )
  {
    kWarning() << "Cannot remove processed directory" << fullDirPath << "!";
    return false;
  }

  return true;
}



KMessConfig* KMessConfig::instance()
{
   // If the instance is null, create a new current config and return that.
  if ( instance_ == 0 )
  {
    instance_ = new KMessConfig();
  }
  return instance_;
}



/**
 * @brief Return an account's data directory
 *
 * This method returns the full path where an account's data are stored.
 *
 * @param accountHandle  The account name
 * @return QString
 */
const QString KMessConfig::getAccountDirectory( const QString &accountHandle )
{
  KStandardDirs localKdeDir;
  QDir appsDir, kmessDir;

  appsDir.setPath( localKdeDir.localkdedir() + "/share/apps" );
  kmessDir.setPath( appsDir.absolutePath() + "/kmess/" + accountHandle );

  return kmessDir.absolutePath();
}



/**
 * @brief Return the accounts directory
 *
 * This method returns the full path where all the accounts folders are stored.
 *
 * @return QString
 */
const QString KMessConfig::getAccountsDirectory()
{
  KStandardDirs localKdeDir;
  QDir appsDir, kmessDir;

  appsDir.setPath( localKdeDir.localkdedir() + "/share/apps" );
  kmessDir.setPath( appsDir.absolutePath() + "/kmess" );

  return kmessDir.absolutePath();
}



/**
 * @brief Return a list of saved account handles
 *
 * This method returns a list of all saved accounts
 *
 * @return QStringList
 */
QStringList KMessConfig::getAccountsList()
{
  QDir dirs( getAccountsDirectory() );

  return dirs.entryList( QStringList( "*@*" ), QDir::Dirs | QDir::NoDotAndDotDot );
}



/**
 * @brief Select a group from an account's general config
 *
 * This method returns a directly usable configuration group. It will be a config group named groupName in
 * the config file of account accountHandle.
 *
 * @param accountHandle  The account name
 * @param groupName      The name of the requested configuration group in the account's config file
 */
KConfigGroup KMessConfig::getAccountConfig( const QString &accountHandle, const QString &groupName )
{
  KConfigGroup configGroup;

   // Search for config in QHash
  if( accountsConfigs_.contains( accountHandle ) )
  {
    configGroup = accountsConfigs_.value( accountHandle )->group( groupName );
  }
  else
  {
    QDir accountDir( getAccountsDirectory() + "/" + accountHandle );

    KConfig *config = new KConfig( QString( accountDir.absolutePath() + "/settings" ) );

    // insert current config into QHash for future search
    accountsConfigs_.insert( accountHandle, config );

    configGroup = config->group( groupName );
  }

  // We're unable to write on the configuration files,
  // avoid crashing (when kdelibs are compiled in debug mode)
  if( configGroup.accessMode() != KConfigBase::ReadWrite )
  {
    warnUser();
    return getInvalidConfig();
  }

  return configGroup;
}



/**
 * @brief Select a group from an account's contact list config
 *
 * This method returns a directly usable configuration group. It will be the config group named groupName,
 * in the account directory which handle is accountHandle.
 *
 * @param accountHandle  The account name
 * @param groupName      The name of the config group in the account's configuration, like "Contacts" or "Groups"
 */
KConfigGroup KMessConfig::getContactListConfig( const QString &accountHandle, const QString &groupName )
{
  KConfigGroup configGroup;

  // Search for config in QHash
  if( listsConfigs_.contains( accountHandle ) )
  {
    configGroup = listsConfigs_.value( accountHandle )->group( groupName );
  }
  else
  {
    QDir accountDir( getAccountsDirectory() + "/" + accountHandle );
    KConfig *config = new KConfig( QString( accountDir.absolutePath() + "/contactlist" ) );

    // insert current config into QHash for future search
    listsConfigs_.insert( accountHandle, config );

    configGroup = config->group( groupName );
  }

  // We're unable to write on the configuration files,
  // avoid crashing (when kdelibs are compiled in debug mode)
  if( configGroup.accessMode() != KConfigBase::ReadWrite )
  {
    warnUser();
    return getInvalidConfig();
  }

  return configGroup;
}



/**
 * @brief Select a group from the application-wide config
 *
 * This method returns a directly usable configuration group. It will be the config group named groupName
 * contained in the KMess global configuration file.
 *
 * @param groupName      The name of the config group
 */
KConfigGroup KMessConfig::getGlobalConfig( const QString &groupName )
{
  QString generalGroup( "KMess" );

   // Search for config in QHash
  if( accountsConfigs_.contains( generalGroup ) )
  {
    return accountsConfigs_.value( generalGroup )->group( groupName );
  }

  KConfig *config = new KConfig();

  // insert current config into QHash for future search
  accountsConfigs_.insert( generalGroup, config );

  KConfigGroup configGroup( config->group( groupName ) );

  // We're unable to write on the configuration files,
  // avoid crashing (when kdelibs are compiled in debug mode)
  if( configGroup.accessMode() != KConfigBase::ReadWrite )
  {
    warnUser();
    return getInvalidConfig();
  }

  return configGroup;
}


/**
 * Return an invalid config file.
 *
 * When the configuration files cannot be written to, KMess will use fake config
 * files, which means none of the user settings will be read from or written to
 * until the problem gets fixed. This avoid crashes when using debugging KDE
 * libraries.
 *
 * @returns  A config group
 */
KConfigGroup KMessConfig::getInvalidConfig()
{
#ifdef KMESSDEBUG_SHAREDMETHODS
  kDebug() << "Switching to a temporary config file...";
#endif

  if( tempConfigFile_ == 0 )
  {
    tempConfigFile_ = new QTemporaryFile();
    tempConfigFile_->open();
  }

  KConfig *config = new KConfig( tempConfigFile_->fileName() );

  return config->group( "invalid" );
}


/**
 * Return the path for an MsnObject in the cache.
 *
 * This is used to store display pictures, custom emoticons, winks, etc..
 *
 * @param msnObject  The MSNObject the other contact uses to identify his resource.
 *
 * @returns  A file name string.
 */
QString KMessConfig::getMsnObjectFileName(const MsnObject &msnObject)
{
  // Replace bad characters, in case someone intends to send a bad SHA1.
  // The sha1 string is actually base64 encoded, meaning we could
  // also expect a "/" character in the string.
  QString sha1d( msnObject.getDataHash() );
  const QString safeSha1 = sha1d.replace(QRegExp("[^a-zA-Z0-9+=]"), "_");

  // Be friendly for file managers.
  QString extension;
  QString path;
  QDir dir;
  QStringList files;

  switch( msnObject.getType() )
  {
    case MsnObject::DISPLAYPIC:
      path = "displaypics";

      // Check if there is already the file
      dir.setPath( KStandardDirs::locateLocal( "data", QString("kmess/") + path + "/" ) );
      files = dir.entryList( QStringList( safeSha1 + ".*" ), QDir::Files );

      // If the list isn't empty check for image file
      if( ! files.isEmpty() )
      {
        foreach( const QString &file, files )
        {
          if( ! file.contains( ".dat" ) )
          {
            return dir.absolutePath() + "/" + file;
          }
        }
      }
      break;

    case MsnObject::BACKGROUND:
      extension = ".png";
      path = "backgrounds";
      break;

    case MsnObject::EMOTICON:
      extension = ".png";
      path = "customemoticons";
      break;

    case MsnObject::WINK:
      extension = ".cab";
      path = "winks";
      break;

    default:
      extension = ".dat";
      path = QString::null;
  }

  // Locate filename
  return KStandardDirs::locateLocal( "data", QString("kmess/") + path + "/" + safeSha1 + extension );
}



/**
 * Warn the user about a configuration problem.
 *
 * When a configuration file isn't writeable or readable, a message bothers the user
 * to have the problem resolved :)
 */
void KMessConfig::warnUser()
{
  NotificationManager::EventSettings settings;
  settings.sender  = 0;
  settings.contact = 0;
  settings.widget  = 0;
  settings.buttons = NotificationManager::BUTTON_HIDE;

  // Notify the user about the network error
  NotificationManager::instance()->notify( "status",
                                           i18nc( "Passive notification message",
                                                  "<p>KMess was unable to access its configuration files!</p>" ),
                                           settings );
}


