/*
    This file is part of libkresources.

    Copyright (c) 2002 Tobias Koenig <tokoe@kde.org>
    Copyright (c) 2002 Jan-Pascal van Best <janpascal@vanbest.org>
    Copyright (c) 2003 Cornelius Schumacher <schumacher@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.
*/
#include "managerimpl.h"

#include <kaboutdata.h>
#include <krandom.h>
#include <kdebug.h>
#include <kconfig.h>
#include <kstandarddirs.h>
#include <kconfiggroup.h>

#include <QtDBus/QtDBus>

#include "resource.h"
#include "factory.h"
#include "manager.h"
#include "kresourcesmanageradaptor.h"

using namespace KRES;

class ManagerImpl::ManagerImplPrivate
{
  public:
    ManagerNotifier *mNotifier;
    QString mFamily;
    KConfig *mConfig;
    KConfig *mStdConfig;
    Resource *mStandard;
    Factory *mFactory;
    Resource::List mResources;
    QString mId;
    bool mConfigRead;

};

ManagerImpl::ManagerImpl( ManagerNotifier *notifier, const QString &family )
  : d( new ManagerImplPrivate )
{
  d->mNotifier = notifier;
  d->mFamily = family;
  d->mConfig = 0;
  d->mStdConfig = 0;
  d->mStandard = 0;
  d->mFactory = 0;
  d->mConfigRead = false;

  new KResourcesManagerAdaptor( this );
  const QString dBusPath = QLatin1String( "/ManagerIface_" ) + family;
  QDBusConnection::sessionBus().registerObject( dBusPath, this );
  kDebug();

  d->mId = KRandom::randomString( 8 );

  // Register with D-Bus
  QDBusConnection::sessionBus().registerService( QLatin1String("org.kde.KResourcesManager") );

  QDBusConnection::sessionBus().connect( QString(), dBusPath,
                                         QLatin1String("org.kde.KResourcesManager"), QLatin1String("signalKResourceAdded"),
      this, SLOT(dbusKResourceAdded(QString,QString)));
  QDBusConnection::sessionBus().connect( QString(), dBusPath,
                                         QLatin1String("org.kde.KResourcesManager"), QLatin1String("signalKResourceModified"),
      this, SLOT(dbusKResourceModified(QString,QString)));
  QDBusConnection::sessionBus().connect( QString(), dBusPath,
                                         QLatin1String("org.kde.KResourcesManager"), QLatin1String("signalKResourceDeleted"),
      this, SLOT(dbusKResourceDeleted(QString,QString)));
}

ManagerImpl::~ManagerImpl()
{
  kDebug();

  qDeleteAll( d->mResources );
  delete d->mStdConfig;
  delete d;
}

void ManagerImpl::createStandardConfig()
{
  if ( !d->mStdConfig ) {
    QString file = defaultConfigFile( d->mFamily );
    d->mStdConfig = new KConfig( file );
  }

  d->mConfig = d->mStdConfig;
}

void ManagerImpl::readConfig( KConfig *cfg )
{
  kDebug();

  if ( d->mFactory ) {
    d->mFactory->reloadConfig();
  } else {
    d->mFactory = Factory::self( d->mFamily );
  }

  if ( !cfg ) {
    createStandardConfig();
  } else {
    d->mConfig = cfg;
  }

  d->mStandard = 0;
  KConfigGroup group = d->mConfig->group( "General" );

  QStringList keys = group.readEntry( "ResourceKeys", QStringList() );
  keys += group.readEntry( "PassiveResourceKeys", QStringList() );

  const QString standardKey = group.readEntry( "Standard" );

  for ( QStringList::const_iterator it = keys.constBegin(); it != keys.constEnd(); ++it ) {
    readResourceConfig( *it, false );
  }

  d->mConfigRead = true;
}

void ManagerImpl::writeConfig( KConfig *cfg )
{
  kDebug();

  if ( !cfg ) {
    createStandardConfig();
  } else {
    d->mConfig = cfg;
  }

  QStringList activeKeys;
  QStringList passiveKeys;

  // First write all keys, collect active and passive keys on the way
  Resource::List::Iterator it;
  for ( it = d->mResources.begin(); it != d->mResources.end(); ++it ) {
    writeResourceConfig( *it, false );

    QString key = ( *it )->identifier();
    if ( ( *it )->isActive() ) {
      activeKeys.append( key );
    } else {
      passiveKeys.append( key );
    }
  }

  // And then the general group

  kDebug() << "Saving general info";
  KConfigGroup group = d->mConfig->group( "General" );
  group.writeEntry( "ResourceKeys", activeKeys );
  group.writeEntry( "PassiveResourceKeys", passiveKeys );
  if ( d->mStandard ) {
    group.writeEntry( "Standard", d->mStandard->identifier() );
  } else {
      group.writeEntry( "Standard", QString() );
  }

  group.sync();
  kDebug() << "finished";
}

void ManagerImpl::add( Resource *resource )
{
  resource->setActive( true );

  if ( d->mResources.isEmpty() ) {
    d->mStandard = resource;
  }

  d->mResources.append( resource );

  if ( d->mConfigRead ) {
    writeResourceConfig( resource, true );
  }

  signalKResourceAdded( d->mId, resource->identifier() );
}

void ManagerImpl::remove( Resource *resource )
{
  if ( d->mStandard == resource ) {
    d->mStandard = 0;
  }
  removeResource( resource );

  d->mResources.removeAll( resource );

  signalKResourceDeleted( d->mId, resource->identifier() );

  delete resource;

  kDebug() << "Finished";
}

void ManagerImpl::change( Resource *resource )
{
  writeResourceConfig( resource, true );

  signalKResourceModified( d->mId, resource->identifier() );
}

void ManagerImpl::setActive( Resource *resource, bool active )
{
  if ( resource && resource->isActive() != active ) {
    resource->setActive( active );
  }
}

Resource *ManagerImpl::standardResource()
{
  return d->mStandard;
}

void ManagerImpl::setStandardResource( Resource *resource )
{
  d->mStandard = resource;
}

// DBUS asynchronous functions

void ManagerImpl::dbusKResourceAdded( const QString &managerId,
                                      const QString &resourceId )
{
  if ( managerId == d->mId ) {
    kDebug() << "Ignore D-Bus notification to myself";
    return;
  }
  kDebug() << "Receive D-Bus call: added resource" << resourceId;

  if ( getResource( resourceId ) ) {
    kDebug() << "This resource is already known to me.";
  }

  if ( !d->mConfig ) {
    createStandardConfig();
  }

  d->mConfig->reparseConfiguration();
  Resource *resource = readResourceConfig( resourceId, true );

  if ( resource ) {
    d->mNotifier->notifyResourceAdded( resource );
  } else {
    kError() << "Received D-Bus: resource added for unknown resource"
             << resourceId;
  }
}

void ManagerImpl::dbusKResourceModified( const QString &managerId,
                                         const QString &resourceId )
{
  if ( managerId == d->mId ) {
    kDebug() << "Ignore D-Bus notification to myself";
    return;
  }
  kDebug() << "Receive D-Bus call: modified resource" << resourceId;

  Resource *resource = getResource( resourceId );
  if ( resource ) {
    d->mNotifier->notifyResourceModified( resource );
  } else {
    kError() << "Received D-Bus: resource modified for unknown resource"
             << resourceId;
  }
}

void ManagerImpl::dbusKResourceDeleted( const QString &managerId,
                                        const QString &resourceId )
{
  if ( managerId == d->mId ) {
    kDebug() << "Ignore D-Bus notification to myself";
    return;
  }
  kDebug() << "Receive D-Bus call: deleted resource" << resourceId;

  Resource *resource = getResource( resourceId );
  if ( resource ) {
    d->mNotifier->notifyResourceDeleted( resource );

    kDebug() << "Removing item from mResources";
    // Now delete item
    if ( d->mStandard == resource ) {
      d->mStandard = 0;
    }
    d->mResources.removeAll( resource );
  } else {
    kError() << "Received D-Bus: resource deleted for unknown resource"
             << resourceId;
  }
}

QStringList ManagerImpl::resourceNames()
{
  QStringList result;

  Resource::List::ConstIterator it;
  Resource::List::ConstIterator end(d->mResources.constEnd());
  for ( it = d->mResources.constBegin(); it != end; ++it ) {
    result.append( ( *it )->resourceName() );
  }
  return result;
}

Resource::List *ManagerImpl::resourceList()
{
  return &d->mResources;
}

QList<Resource *> ManagerImpl::resources()
{
  return QList<Resource *>( d->mResources );
}

QList<Resource *> ManagerImpl::resources( bool active )
{
  QList<Resource *> result;

  for ( int i = 0; i < d->mResources.size(); ++i ) {
    if ( d->mResources.at( i )->isActive() == active ) {
      result.append( d->mResources.at( i ) );
    }
  }
  return result;
}

Resource *ManagerImpl::readResourceConfig( const QString &identifier,
                                           bool checkActive )
{
  kDebug() << identifier;

  if ( !d->mFactory ) {
    kError() << "mFactory is 0. Did the app forget to call readConfig?";
    return 0;
  }

  KConfigGroup group = d->mConfig->group( QLatin1String("Resource_") + identifier );

  QString type = group.readEntry( "ResourceType" );
  QString name = group.readEntry( "ResourceName" );
  Resource *resource = d->mFactory->resource( type, group );
  if ( !resource ) {
    kDebug() << "Failed to create resource with id" << identifier;
    return 0;
  }

  if ( resource->identifier().isEmpty() ) {
    resource->setIdentifier( identifier );
  }

  group = d->mConfig->group( "General" );

  QString standardKey = group.readEntry( "Standard" );
  if ( standardKey == identifier ) {
    d->mStandard = resource;
  }

  if ( checkActive ) {
    QStringList activeKeys = group.readEntry( "ResourceKeys", QStringList() );
    resource->setActive( activeKeys.contains( identifier ) );
  }
  d->mResources.append( resource );

  return resource;
}

void ManagerImpl::writeResourceConfig( Resource *resource, bool checkActive )
{
  QString key = resource->identifier();

  kDebug() << "Saving resource" << key;

  if ( !d->mConfig ) {
    createStandardConfig();
  }

  KConfigGroup group( d->mConfig, QLatin1String("Resource_") + key );
  resource->writeConfig( group );

  group = d->mConfig->group( "General" );
  QString standardKey = group.readEntry( "Standard" );

  if ( resource == d->mStandard  && standardKey != key ) {
    group.writeEntry( "Standard", resource->identifier() );
  } else if ( resource != d->mStandard && standardKey == key ) {
    group.writeEntry( "Standard", "" );
  }

  if ( checkActive ) {
    QStringList activeKeys = group.readEntry( "ResourceKeys", QStringList() );
    QStringList passiveKeys = group.readEntry( "PassiveResourceKeys", QStringList() );
    if ( resource->isActive() ) {
      if ( passiveKeys.contains( key ) ) { // remove it from passive list
        passiveKeys.removeAll( key );
        group.writeEntry( "PassiveResourceKeys", passiveKeys );
      }
      if ( !activeKeys.contains( key ) ) { // add it to active list
        activeKeys.append( key );
        group.writeEntry( "ResourceKeys", activeKeys );
      }
    } else if ( !resource->isActive() ) {
      if ( activeKeys.contains( key ) ) { // remove it from active list
        activeKeys.removeAll( key );
        group.writeEntry( "ResourceKeys", activeKeys );
      }
      if ( !passiveKeys.contains( key ) ) { // add it to passive list
        passiveKeys.append( key );
        group.writeEntry( "PassiveResourceKeys", passiveKeys );
      }
    }
  }

  d->mConfig->sync();
}

void ManagerImpl::removeResource( Resource *resource )
{
  QString key = resource->identifier();

  if ( !d->mConfig ) {
    createStandardConfig();
  }

  KConfigGroup group = d->mConfig->group( "General" );
  QStringList activeKeys = group.readEntry( "ResourceKeys", QStringList() );
  if ( activeKeys.contains( key ) ) {
    activeKeys.removeAll( key );
    group.writeEntry( "ResourceKeys", activeKeys );
  } else {
    QStringList passiveKeys= group.readEntry( "PassiveResourceKeys", QStringList() );
    passiveKeys.removeAll( key );
    group.writeEntry( "PassiveResourceKeys", passiveKeys );
  }

  QString standardKey = group.readEntry( "Standard" );
  if ( standardKey == key ) {
    group.writeEntry( "Standard", "" );
  }

  d->mConfig->deleteGroup( QLatin1String("Resource_") + resource->identifier() );
  group.sync();
}

Resource *ManagerImpl::getResource( const QString &identifier )
{
  Resource::List::ConstIterator it;
  for ( it = d->mResources.constBegin(); it != d->mResources.constEnd(); ++it ) {
    if ( ( *it )->identifier() == identifier ) {
      return *it;
    }
  }
  return 0;
}

QString ManagerImpl::defaultConfigFile( const QString &family )
{
  return KStandardDirs::locateLocal( "config",
                                     QString::fromLatin1( "kresources/%1/stdrc" ).arg( family ) );
}

