/*
    kopetepassword.cpp - Kopete Password

    Copyright (c) 2004      by Richard Smith         <kde@metafoo.co.uk>
    Kopete    (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * This library is free software; you can redistribute it and/or         *
    * modify it under the terms of the GNU Lesser General Public            *
    * License as published by the Free Software Foundation; either          *
    * version 2 of the License, or (at your option) any later version.      *
    *                                                                       *
    *************************************************************************
*/

#include "kopeteuiglobal.h"
#include "kopetepassword.h"
#include "kopetepassworddialog.h"
#include "kopetewalletmanager.h"

#include <tdewallet.h>

#include <tqapplication.h>
#include <tqlabel.h>
#include <tqlineedit.h>
#include <tqcheckbox.h>

#include <kactivelabel.h>
#include <tdeapplication.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <kdialogbase.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kiconloader.h>
#include <kpassdlg.h>
#include <kstringhandler.h>

class Kopete::Password::Private
{
public:
	Private( const TQString &group, uint maxLen, bool blanksAllowed )
	 : refCount( 1 ), configGroup( group ), remembered( false ), maximumLength( maxLen ),
	 isWrong( false ), allowBlankPassword( blanksAllowed )
	{
	}
	Private *incRef()
	{
		++refCount;
		return this;
	}
	void decRef()
	{
		if( --refCount == 0 )
			delete this;
	}
	/** Reference count */
	int refCount;
	/** Group to use for TDEConfig and TDEWallet */
	const TQString configGroup;
	/** Is the password being remembered? */
	bool remembered;
	/** The current password in the TDEConfig file, or TQString() if no password there */
	TQString passwordFromTDEConfig;
	/** The maximum length allowed for this password, or -1 if there is no limit */
	uint maximumLength;
	/** Is the current password known to be wrong? */
	bool isWrong;
	/** Are we allowed to have blank passwords? */
	bool allowBlankPassword;
	/** The cached password */
	TQString cachedValue;
};

/**
 * Implementation detail of Kopete::Password: manages a single password request
 * @internal
 * @author Richard Smith <kde@metafoo.co.uk>
 */
class KopetePasswordRequest : public KopetePasswordRequestBase
{
public:
	KopetePasswordRequest( TQObject *owner, Kopete::Password &pass )
	 : TQObject( owner ), mPassword( pass ), mWallet( 0 )
	{
	}

	/**
	 * Start the request - ask for the wallet
	 */
	void begin()
	{
		kdDebug( 14010 ) << k_funcinfo << endl;
		Kopete::WalletManager::self()->openWallet( this, TQ_SLOT( walletReceived( TDEWallet::Wallet* ) ) );
	}

	void walletReceived( TDEWallet::Wallet *wallet )
	{
		kdDebug( 14010 ) << k_funcinfo << endl;
		mWallet = wallet;
		processRequest();
	}

	/**
	 * Got wallet; now carry out whatever action this request represents
	 */
	virtual void processRequest() = 0;

	void slotOkPressed() {}
	void slotCancelPressed() {}

protected:
	Kopete::Password mPassword;
	TDEWallet::Wallet *mWallet;
};

/**
 * Implementation detail of Kopete::Password: manages a single password retrieval request
 * @internal
 * @author Richard Smith <kde@metafoo.co.uk>
 */
class KopetePasswordGetRequest : public KopetePasswordRequest
{
public:
	KopetePasswordGetRequest( TQObject *owner, Kopete::Password &pass )
	 : KopetePasswordRequest( owner, pass )
	{
	}

	TQString grabPassword()
	{
		// Before trying to read from the wallet, check if the config file holds a password.
		// If so, remove it from the config and set it through TDEWallet instead.
		TQString pwd;
		if ( mPassword.d->remembered && !mPassword.d->passwordFromTDEConfig.isNull() )
		{
			pwd = mPassword.d->passwordFromTDEConfig;
			mPassword.set( pwd );
			return pwd;
		}

		if ( mWallet && mWallet->readPassword( mPassword.d->configGroup, pwd ) == 0 && !pwd.isNull() )
			return pwd;

		if ( mPassword.d->remembered && !mPassword.d->passwordFromTDEConfig.isNull() )
			return mPassword.d->passwordFromTDEConfig;

		return TQString();
	}

	void finished( const TQString &result )
	{
		mPassword.d->cachedValue = result;
		emit requestFinished( result );
		delete this;
	}
};

class KopetePasswordGetRequestPrompt : public KopetePasswordGetRequest
{
public:
	KopetePasswordGetRequestPrompt( TQObject *owner, Kopete::Password &pass,  const TQPixmap &image, const TQString &prompt, Kopete::Password::PasswordSource source )
	 : KopetePasswordGetRequest( owner, pass ), mImage( image ), mPrompt( prompt ), mSource( source ), mView( 0 )
	{
	}

	void processRequest()
	{
		TQString result = grabPassword();
		if ( mSource == Kopete::Password::FromUser || result.isNull() )
			doPasswordDialog();
		else
			finished( result );
	}

	void doPasswordDialog()
	{
		kdDebug( 14010 ) << k_funcinfo << endl;

		KDialogBase *passwdDialog = new KDialogBase( Kopete::UI::Global::mainWidget(), "passwdDialog", true, i18n( "Password Required" ),
			KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true );

		mView = new KopetePasswordDialog( passwdDialog );
		passwdDialog->setMainWidget( mView );

		mView->m_text->setText( mPrompt );
		mView->m_image->setPixmap( mImage );
		/* Do not put the default password, or it will confuse those which doesn't echo anything for the password
		mView->m_password->insert( password );
		*/
		int maxLength = mPassword.maximumLength();
		if ( maxLength != 0 )
			mView->m_password->setMaxLength( maxLength );
		mView->m_password->setFocus();

		// FIXME: either document what these are for or remove them - lilac
		mView->adjustSize();
		passwdDialog->adjustSize();

		connect( passwdDialog, TQ_SIGNAL( okClicked() ), TQ_SLOT( slotOkPressed() ) );
		connect( passwdDialog, TQ_SIGNAL( cancelClicked() ), TQ_SLOT( slotCancelPressed() ) );
		connect( this, TQ_SIGNAL( destroyed() ), passwdDialog, TQ_SLOT( deleteLater() ) );
		passwdDialog->show();
	}

	void slotOkPressed()
	{
		TQString result = mView->m_password->password();
		if ( mView->m_save_passwd->isChecked() )
			mPassword.set( result );

		finished( result );
	}

	void slotCancelPressed()
	{
		finished( TQString() );
	}

private:
	TQPixmap mImage;
	TQString mPrompt;
	Kopete::Password::PasswordSource mSource;
	unsigned int mMaxLength;
	KopetePasswordDialog *mView;
};

class KopetePasswordGetRequestNoPrompt : public KopetePasswordGetRequest
{
public:
	KopetePasswordGetRequestNoPrompt( TQObject *owner, Kopete::Password &pass )
	 : KopetePasswordGetRequest( owner, pass )
	{
	}

	void processRequest()
	{
		finished( grabPassword() );
	}
};

/**
 * Implementation detail of Kopete::Password: manages a single password change request
 * @internal
 * @author Richard Smith <kde@metafoo.co.uk>
 */
class KopetePasswordSetRequest : public KopetePasswordRequest
{
public:
	KopetePasswordSetRequest( Kopete::Password &pass, const TQString &newPass )
	 : KopetePasswordRequest( 0, pass ), mNewPass( newPass )
	{
		if ( TDEApplication *app = TDEApplication::kApplication() )
			app->ref();
	}
	~KopetePasswordSetRequest()
	{
		if ( TDEApplication *app = TDEApplication::kApplication() )
			app->deref();
		kdDebug( 14010 ) << k_funcinfo << "job complete" << endl;
	}
	void processRequest()
	{
		if ( setPassword() )
		{
			mPassword.setWrong( false );
			mPassword.d->cachedValue = mNewPass;
		}
		delete this;
	}
	bool setPassword()
	{
		kdDebug( 14010 ) << k_funcinfo << " setting password for " << mPassword.d->configGroup << endl;

		if ( mWallet && mWallet->writePassword( mPassword.d->configGroup, mNewPass ) == 0 )
		{
			mPassword.d->remembered = true;
			mPassword.d->passwordFromTDEConfig = TQString();
			mPassword.writeConfig();
			return true;
		}

		if ( TDEWallet::Wallet::isEnabled() )
		{
			// If we end up here, the wallet is enabled, but failed somehow.
			// Ask the user what to do now.

			//NOTE: This will start a nested event loop. However, this is fine; the only code we
			// call after this point is in Kopete::Password, so as long as we've not been deleted
			// everything should work out OK. We have no parent TQObject, so we should survive.
			if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(),
			        i18n( "<qt>Kopete is unable to save your password securely in your wallet;<br>"
			              "do you want to save the password in the <b>unsafe</b> configuration file instead?</qt>" ),
			        i18n( "Unable to Store Secure Password" ),
			        KGuiItem( i18n( "Store &Unsafe" ), TQString::fromLatin1( "unlock" ) ),
			        TQString::fromLatin1( "TDEWalletFallbackToTDEConfig" ) ) != KMessageBox::Continue )
			{
				return false;
			}
		}
		mPassword.d->remembered = true;
		mPassword.d->passwordFromTDEConfig = mNewPass;
		mPassword.writeConfig();
		return true;
	}

private:
	TQString mNewPass;
};

class KopetePasswordClearRequest : public KopetePasswordRequest
{
public:
	KopetePasswordClearRequest( Kopete::Password &pass )
	 : KopetePasswordRequest( 0, pass )
	{
		if ( TDEApplication *app = TDEApplication::kApplication() )
			app->ref();
	}
	~KopetePasswordClearRequest()
	{
		if ( TDEApplication *app = TDEApplication::kApplication() )
			app->deref();
		kdDebug( 14010 ) << k_funcinfo << "job complete" << endl;
	}
	void processRequest()
	{
		if ( clearPassword() )
		{
			mPassword.setWrong( true );
			mPassword.d->cachedValue = TQString();
		}

		delete this;
	}
	bool clearPassword()
	{
		kdDebug( 14010 ) << k_funcinfo << " clearing password" << endl;

		mPassword.d->remembered = false;
		mPassword.d->passwordFromTDEConfig = TQString();
		mPassword.writeConfig();
		if ( mWallet )
			mWallet->removeEntry( mPassword.d->configGroup );
		return true;
	}
};

Kopete::Password::Password( const TQString &configGroup, uint maximumLength, const char *name )
 : TQObject( 0, name ), d( new Private( configGroup, maximumLength, false ) )
{
	readConfig();
}

Kopete::Password::Password( const TQString &configGroup, uint maximumLength,
	bool allowBlankPassword, const char *name )
 : TQObject( 0, name ), d( new Private( configGroup, maximumLength, allowBlankPassword ) )
{
	readConfig();
}

Kopete::Password::Password( Password &other, const char *name )
 : TQObject( 0, name ), d( other.d->incRef() )
{
}

Kopete::Password::~Password()
{
	d->decRef();
}

Kopete::Password &Kopete::Password::operator=( Password &other )
{
	if ( d == other.d ) return *this;
	d->decRef();
	d = other.d->incRef();
	return *this;
}

void Kopete::Password::readConfig()
{
	TDEConfig *config = TDEGlobal::config();
	config->setGroup( d->configGroup );

	TQString passwordCrypted = config->readEntry( "Password" );
	if ( passwordCrypted.isNull() )
		d->passwordFromTDEConfig = TQString();
	else
		d->passwordFromTDEConfig = KStringHandler::obscure( passwordCrypted );

	d->remembered = config->readBoolEntry( "RememberPassword", false );
	d->isWrong = config->readBoolEntry( "PasswordIsWrong", false );
}

void Kopete::Password::writeConfig()
{
	TDEConfig *config = TDEGlobal::config();
	if(!config->hasGroup(d->configGroup))
	{
		//### (KOPETE)
		// if the kopete account has been removed, we have no way to know it.
		//  but we don't want in any case to recreate the group.
		//  see Bug 106460
		// (the problem is that when we remove the account, we remove the password
		//  also, which cause a call to this function )
		return;
	}
		  
	config->setGroup( d->configGroup );

	if ( d->remembered && !d->passwordFromTDEConfig.isNull() )
		config->writeEntry( "Password", KStringHandler::obscure( d->passwordFromTDEConfig ) );
	else
		config->deleteEntry( "Password" );

	config->writeEntry( "RememberPassword", d->remembered );
	config->writeEntry( "PasswordIsWrong", d->isWrong );
}

int Kopete::Password::preferredImageSize()
{
	return IconSize(TDEIcon::Toolbar);
}

bool Kopete::Password::allowBlankPassword()
{
	return d->allowBlankPassword;
}

uint Kopete::Password::maximumLength()
{
	return d->maximumLength;
}

void Kopete::Password::setMaximumLength( uint max )
{
	d->maximumLength = max;
}

bool Kopete::Password::isWrong()
{
	return d->isWrong;
}

void Kopete::Password::setWrong( bool bWrong )
{
	d->isWrong = bWrong;
	writeConfig();

	if ( bWrong ) d->cachedValue = TQString();
}

void Kopete::Password::requestWithoutPrompt( TQObject *returnObj, const char *slot )
{
	KopetePasswordRequest *request = new KopetePasswordGetRequestNoPrompt( returnObj, *this );
	// call connect on returnObj so we can still connect if 'slot' is protected/private
	returnObj->connect( request, TQ_SIGNAL( requestFinished( const TQString & ) ), slot );
	request->begin();
}

void Kopete::Password::request( TQObject *returnObj, const char *slot, const TQPixmap &image, const TQString &prompt, Kopete::Password::PasswordSource source )
{
	KopetePasswordRequest *request = new KopetePasswordGetRequestPrompt( returnObj, *this, image, prompt, source );
	returnObj->connect( request, TQ_SIGNAL( requestFinished( const TQString & ) ), slot );
	request->begin();
}

TQString Kopete::Password::cachedValue()
{
	return d->cachedValue;
}

void Kopete::Password::set( const TQString &pass )
{
	// if we're being told to forget the password, and we aren't remembering one,
	// don't try to open the wallet. fixes bug #71804.
	if( pass.isNull() && !d->allowBlankPassword )
	{
		if( remembered() )
			clear();
		return;
	}

	KopetePasswordRequest *request = new KopetePasswordSetRequest( *this, pass );
	request->begin();
}

void Kopete::Password::clear()
{
	KopetePasswordClearRequest *request = new KopetePasswordClearRequest( *this );
	request->begin();
}

bool Kopete::Password::remembered()
{
	return d->remembered;
}

#include "kopetepassword.moc"
