/***************************************************************************
                          accountsmanager.cpp -  description
                             -------------------
    begin                : Sat May 3 2008
    copyright            : (C) 2008 by Valerio Pilo
    email                : valerio@kmess.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "accountsmanager.h"

#include "utils/kmessconfig.h"
#include "currentaccount.h"
#include "kmess.h"
#include "kmessapplication.h"
#include "kmessdebug.h"

#include <QDir>

#include <KConfig>
#include <KLocale>
#include <KMessageBox>


#ifdef KMESSDEBUG_ACCOUNTSMANAGER
  #define KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
#endif



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



/**
 * Constructor
 */
AccountsManager::AccountsManager()
: doPasswordRead_( false ),
  doPasswordWrite_( false ),
  passwordManager_( 0 )
{
  // Load the saved accounts from disk and create them
  readProperties();
}



// Destructor
AccountsManager::~AccountsManager()
{
  // Delete the accounts
  qDeleteAll( accounts_ );
  accounts_.clear();

  // Delete the password manager instance
  delete passwordManager_;

#ifdef KMESSDEBUG_ACCOUNTSMANAGER
  kmDebug() << "DESTROYED.";
#endif
}



// Add a new account to the list
void AccountsManager::addAccount( Account *account )
{
  // Append account
  accounts_.append( account );

#ifdef KMESSDEBUG_ACCOUNTSMANAGER
  kmDebug() << "New account created:" << account->getHandle() << ".";
#endif

  emit accountAdded( account );
}



// An account's settings have been changed
void AccountsManager::changeAccount( Account *account, QString oldHandle, QString oldFriendlyName )
{
  if( KMESS_NULL( account ) ) return;

  QString newHandle( account->getHandle() );

  // We're saving settings for a new account
  if( getAccountByHandle( oldHandle ) == 0 )
  {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
    kmDebug() << "Saving settings for new account" << newHandle;
#endif

    // Add the account to the account list.
    addAccount( account );
  }
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
  else
  {
    kmDebug() << "Saving settings for " << oldHandle;
  }
#endif

  // If the account is set to use autologin, make sure that all other accounts do not
  if( ! account->isDeleted() && account->getUseAutologin() )
  {
    foreach( Account *otherAccount, accounts_ )
    {
      if( otherAccount != account )
      {
        otherAccount->setUseAutologin( false );
      }
    }
  }

  // Rename the account's directory if the handle has changed
  if( oldHandle != newHandle )
  {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
    kmDebug() << "Moving account dir from" << oldHandle << "to" << newHandle;
#endif

    QString accountsPath( KMessConfig::instance()->getAccountsDirectory() );

    QDir accountsDir( accountsPath );

    if( accountsDir.exists( newHandle ) )
    {
      kmWarning() << "Destination account directory" << newHandle << "already exists!";
    }

    if( accountsDir.rename( oldHandle, newHandle ) )
    {
      kmWarning() << "Unable to move account directory" << oldHandle << "to" << newHandle << "!";
    }
  }

  // Commit configuration changes to disk, now
  // Makes sure the settings are not lost if KMess is terminated or crashes.
  account->saveProperties();

  // Emit a signal so that the UI can be updated
  emit accountChanged( account, oldHandle, oldFriendlyName );
}



// Verify is there is a saved account with the specified handle
bool AccountsManager::contains( Account *account ) const
{
  if( account == CurrentAccount::instance() )
  {
    kmWarning() << "Called contains() on the current account!";
    return true;
  }

  return accounts_.contains( account );
}



// Delete the given account
void AccountsManager::deleteAccount( Account *account )
{
  if( KMESS_NULL(account) ) return;

  emit accountDeleted( account );

  QString handle( account->getHandle() );
  KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );

  if( kmessApp->getContactListWindow()->isConnected()
  &&  CurrentAccount::instance()->getHandle() == handle )
  {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
    kmDebug() << "Cannot delete current account:" << handle;
#endif
    return;
  }

#ifdef KMESSDEBUG_ACCOUNTSMANAGER
  kmDebug() << "Deleting account for " << handle << ".";
#endif

  // Remove the account from the list of accounts
  if( accounts_.removeAll( account ) == 0 )
  {
    kmWarning() << "Account" << handle << "not found in collection!";
  }

  // Delete the account directory
  KMessConfig::instance()->destroyConfigDir( KMessConfig::instance()->getAccountsDirectory() + "/" + handle );

  // Delete the account's contents
  account->setDeleted();
  account->deleteLater();
}



/**
 * Delete the instance of the accounts manager
 */
void AccountsManager::destroy()
{
  delete instance_;
  instance_ = 0;
}



// Return the account for a given handle
Account *AccountsManager::getAccountByHandle( const QString &handle )
{
  if( handle.isEmpty() )
  {
    return 0;
  }

  foreach( Account *account, accounts_ )
  {
    if( account->getHandle() == handle )
    {
      return account;
    }
  }

  return 0;
}



// Get the list of all saved accounts
const QList<Account *> AccountsManager::getAccounts() const
{
  return accounts_;
}



// Initialize the KWallet password manager
void AccountsManager::initializePasswordManager( bool block )
{
  // Don't initialize if it is already
  if( passwordManager_ != 0 )
  {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
    kmDebug() << "KWallet is already initialized.";
#endif
    return;
  }

  // Obtain the window ID of the main KMess window
  KMessApplication *kmessApp = static_cast<KMessApplication*>( kapp );
  WId wId = kmessApp->getContactListWindow()->window()->winId();

  if( block )
  {
    // Open KWallet (synchronously - blocks KMess until the password is given)
    passwordManager_ = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), wId, KWallet::Wallet::Synchronous );

    // Call the slot manually
    slotWalletOpen( true );
    return;
  }
  else
  {
    // Open KWallet (asynchronously - the slot will be called when - and if - the wallet will be open)
    passwordManager_ = KWallet::Wallet::openWallet( KWallet::Wallet::LocalWallet(), wId, KWallet::Wallet::Asynchronous );

    if( passwordManager_ )
    {
      connect( passwordManager_, SIGNAL(   walletOpened(bool) ),
               this,             SLOT  ( slotWalletOpen(bool) ) );
    }
    else
    {
      // If the wallet service is not available, initialize the plain text password storage
      slotWalletOpen( false );
      return;
    }
  }
}



// Queue a read for all passwords
void AccountsManager::readPasswords( bool block )
{
  doPasswordRead_ = true;
  initializePasswordManager( block );
}



// Save passwords for all accounts (only actually saves when there are accounts to be updated!)
void AccountsManager::savePasswords( bool block )
{
  bool passwordsToWrite = false;

  // check if there are passwords to be written
  foreach( const Account *account, accounts_ )
  {
    if( account->getSavedPassword() != account->getPassword() )
    {
      passwordsToWrite = true;
      break;
    }
  }

  if( passwordsToWrite )
  {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
    kmDebug() << "There were passwords to be updated, scheduling an update.";
#endif
    doPasswordWrite_ = true;
    initializePasswordManager( block );
  }
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
  else
  {
    kmDebug() << "There were no passwords to be updated, skipping save request.";
  }
#endif
}



// Read the passwords from the just opened wallet
void AccountsManager::slotWalletOpen( bool success )
{
  // if the wallet couldn't be opened, delete it
  if( ! success ||
    ( passwordManager_ != 0 && ! passwordManager_->isOpen() ) )
  {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
    kmDebug() << "Wallet opening failed, falling back to plain text passwords.";
#endif

    delete passwordManager_;
    passwordManager_ = 0;
  }

  // initialize the wallet
  if( passwordManager_ )
  {
    setupPasswordManager();
  }

  // Read the passwords
  if( doPasswordRead_ )
  {
    if( passwordManager_ == 0 )
    {
      KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) );
      const QStringList passwordAccountList( passwordGroup.keyList() );

#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
      kmDebug() << "Reading passwords from plain-text storage instead of KWallet!";
#endif

      // Read the passwords from the KMess config file
      foreach( const QString &accountHandle, passwordAccountList )
      {
        Account *account = getAccountByHandle( accountHandle );

        // Skip passwords for invalid and deleted accounts, and remove them too
        if( account == 0 )
        {
          passwordGroup.deleteEntry( accountHandle );
          continue;
        }

#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
        kmDebug() << "Password have been read for account: " << accountHandle;
#endif

        QString password = passwordGroup.readEntry( accountHandle, QString() );
        account->setPassword( password );
        account->setSavedPassword( password );
      }
    }
    else // passwordManager_ != 0
    {
      QString password;

      // Read the password for each account
      foreach( Account *account, accounts_ )
      {
        if( passwordManager_->readPassword( account->getHandle(), password ) != 0 || password.isEmpty() )
        {
          continue;
        }

#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
        kmDebug() << "Password have been read for account: " << account->getHandle();
#endif

        account->setPassword( password );
        account->setSavedPassword( password );
      }
    }

    emit passwordsReady();
  }

  // Write the passwords
  if( doPasswordWrite_ )
  {
    if( passwordManager_ == 0 )
    {
      kmWarning() << "KWallet is not available: saving passwords in plain text.";

      KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) );

      // Store the password for each account
      foreach( Account *account, accounts_ )
      {
        const QString& handle = account->getHandle();

        // If we have already the entry, check if the user want still save the password..
        if( passwordGroup.hasKey( handle ) )
        {
          // If the password should not be saved, then delete any already saved password
          if( account->getSavePassword() == false || account->getPassword().isEmpty() )
          {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
            kmDebug() << "Password for account" << handle << "removed.";
#endif

            account->setSavedPassword( "" );
            passwordGroup.deleteEntry( handle );
            continue;
          }
        }

        // Save the password
        account->setSavedPassword( account->getPassword() );
        passwordGroup.writeEntry( handle, account->getPassword() );

#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
        kmDebug() << "Password for account" << handle << "saved.";
#endif
      }
    }
    else // passwordManager_ != 0
    {
      // Store the password for each account
      foreach( Account *account, accounts_ )
      {
        const QString& handle = account->getHandle();
        bool savePassword = ( account->getSavePassword() && ! account->getPassword().isEmpty() );

        // If the password should not be saved, then delete any already saved password
        if( savePassword == false && passwordManager_->hasEntry( handle ) )
        {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
          kmDebug() << "Removing account" << handle << "from wallet";
#endif

          account->setSavedPassword( "" );
          passwordManager_->removeEntry( handle );
          continue;
        }
        // Update the password if we should
        else if( savePassword == true && account->getSavedPassword() != account->getPassword()
              && passwordManager_->writePassword( handle, account->getPassword() ) != 0 )
        {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
          kmDebug() << "Couldn't store password for account" << handle;
#endif
          account->setSavedPassword( "" );
          continue;
        }

        account->setSavedPassword( account->getPassword() );
      }

      // Save the passwords to disk now
      passwordManager_->sync();
    }
  }

  doPasswordRead_  = false;
  doPasswordWrite_ = false;

  // Clean up the password manager, it's not needed anymore
  // (not cleaning it up could result in weird issues if the program crashes or so)
  delete passwordManager_;
  passwordManager_ = 0;
}



// Set up the passwordManager_ (change to the KMess folder, import plaintext passwords, etc)
void AccountsManager::setupPasswordManager()
{
#ifdef KMESSTEST
  KMESS_ASSERT( passwordManager_->isOpen() );
#endif

  // Look for an existing kmess folder and create one if needed
  if( ! passwordManager_->hasFolder( "KMess" ) )
  {
    passwordManager_->createFolder( "KMess" );
  }

  // Set the current folder for password storing, it should't fail
  if( ! passwordManager_->setFolder( "KMess" ) )
  {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
    kmWarning() << "Setting KWallet folder failed!";
#endif

    delete passwordManager_;
    passwordManager_ = 0;

    return;
  }

  // Wallet is open: verify if the user has any useable plaintext password set.
  // If so, ask if they should be deleted or copied to the wallet :)
  KConfigGroup passwordGroup( KMessConfig::instance()->getGlobalConfig( "Passwords" ) );
  const QStringList passwordAccountList( passwordGroup.keyList() );
  if( ! passwordAccountList.isEmpty() )
  {
    QStringList validAccounts;

    foreach( const QString &accountHandle, passwordAccountList )
    {
      Account *account = getAccountByHandle( accountHandle );

      // Skip passwords for invalid and deleted accounts, and remove them too
      if( account == 0 )
      {
        passwordGroup.deleteEntry( accountHandle );
        continue;
      }

      validAccounts << accountHandle;
    }

    // There are some accounts passwords. Ask the user
    if( validAccounts.count() > 0 )
    {
      kmWarning() << "Insecure account passwords found for accounts: " << validAccounts;

      int res = KMessageBox::questionYesNoCancel( 0,
                                                  i18n( "<html>Some insecurely stored account passwords have been found.<br />"
                                                        "Do you want to import the passwords to the KDE Wallet "
                                                        "named '%1', keep the insecurely stored passwords, or delete "
                                                        "them permanently?<br /><br />"
                                                        "<i>Note: it is not recommended to keep insecurely stored passwords "
                                                        "if the KDE Wallet is available, because your passwords "
                                                        "will be easily readable in the KMess configuration files.</i></html>",
                                                        KWallet::Wallet::LocalWallet() ),
                                                  i18nc( "Dialog box caption", "Secure Password Storage" ),
                                                  KGuiItem( i18nc( "Dialog button: Import passwords to a KDE wallet",
                                                                   "Import" ),
                                                            "document-import"   ), // Yes option
                                                  KGuiItem( i18nc( "Dialog button: Delete insecurely stored passwords",
                                                                   "Delete" ),
                                                            "edit-delete-shred" ), // No option
                                                  KGuiItem( i18nc( "Dialog button: Keep insecurely stored passwords",
                                                                   "Keep" ),
                                                            "mail-mark-notjunk" ), // Cancel option
                                                  "insecurePasswordsDetected" );
      switch( res )
      {
        case KMessageBox::Yes:
          // Import passwords to the wallet, and remove the plaintext ones

#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
          kmDebug() << "Importing plaintext passwords to the wallet.";
#endif
          foreach( const QString &accountHandle, passwordAccountList )
          {
            Account *account = getAccountByHandle( accountHandle );
            if( account != 0 )
            {
              passwordManager_->writePassword( accountHandle, passwordGroup.readEntry( accountHandle, QString() ) );
              passwordGroup.deleteEntry( accountHandle );
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
              kmDebug() << "Imported password for account:" << accountHandle;
#endif
            }
          }
          break;

        case KMessageBox::No:
          // Only delete the plain text passwords

#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
          kmDebug() << "Deleting plain text account passwords.";
#endif
          foreach( const QString &accountHandle, passwordAccountList )
          {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
            kmDebug() << "Deleting plaintext password for account:" << accountHandle;
#endif
            passwordGroup.deleteEntry( accountHandle );
          }
          break;

        case KMessageBox::Cancel:
          // Do nothing (useful if you wish to postpone the decision)

#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
            kmDebug() << "No action against plaintext passwords will be taken now.";
#endif
          break;
      }
    }
  }

#ifdef KMESSDEBUG_ACCOUNTSMANAGER_KWALLET
  kmDebug() << "KWallet successfully opened.";
#endif
}



/**
 * Return a singleton instance of the accounts manager
 */
AccountsManager* AccountsManager::instance()
{
  // If the instance is null, create a new accounts manager and return that.
  if ( instance_ == 0 )
  {
    instance_ = new AccountsManager();
  }
  return instance_;
}



/**
 * Load the accounts from disk
 */
void AccountsManager::readProperties()
{
  // Obtain the list of account directories in the KMess config directory
  QStringList accountList( KMessConfig::instance()->getAccountsList() );

  // Add each one of the saved accounts to the internal list
  foreach( const QString &handle, accountList )
  {
    if( Account::isValidEmail( handle ) )
    {
      Account *account = new Account();
      account->readProperties( handle );
      addAccount( account );
    }
  }
}



// Show the settings dialog for a given account
void AccountsManager::showAccountSettings( Account *account, QWidget *parentWindow, AccountSettingsDialog::Page startingPage )
{
  // No account has been specified, create one
  if( ! account )
  {
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
    kmDebug() << "Opening settings for a new account";
#endif

    // Create an account to store the profile information
    account = new Account();

    // Assume the user *does* want to save it's settings,
    // otherwise he/she would have used the main login dialog.
    account->setGuestAccount( false );
  }
#ifdef KMESSDEBUG_ACCOUNTSMANAGER
  else
  {
    kmDebug() << "Opening settings for " << account->getHandle();
  }
#endif

  CurrentAccount *currentAccount = CurrentAccount::instance();
  KMess *kmess = static_cast<KMessApplication*>( kapp )->getContactListWindow();
  bool isCurrentAccount = ( account->getHandle() == currentAccount->getHandle() );
  bool isConnectedCurrentAccount = isCurrentAccount && ( kmess->isConnected() );

  // If we are changing the current account, directly change the CurrentAccount object
  // instead of the stored account object
  if( isConnectedCurrentAccount )
  {
    account = currentAccount;
  }

  // Show the settings dialog
  AccountSettingsDialog *accountSettingsDialog = AccountSettingsDialog::instance( parentWindow );

  connect( accountSettingsDialog, SIGNAL(   deleteAccount(Account*)                 ),
           this,                  SLOT  (   deleteAccount(Account*)                 ) );
  connect( accountSettingsDialog, SIGNAL( changedSettings(Account*,QString,QString) ),
           this,                  SLOT  (   changeAccount(Account*,QString,QString) ) );


  accountSettingsDialog->loadSettings( account, isConnectedCurrentAccount, startingPage );
  accountSettingsDialog->show();
  accountSettingsDialog->raise();
}



#include "accountsmanager.moc"
