/*

Conversation widget for tdm greeter

Copyright (C) 2008 Dirk Mueller <mueller@kde.org>

based on classic tdm greeter:

  Copyright (C) 1997, 1998, 2000 Steffen Hansen <hansen@kde.org>
  Copyright (C) 2000-2003 Oswald Buddenhagen <ossi@kde.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.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

#include "kgreet_pam.h"
#include "themer/tdmthemer.h"
#include "themer/tdmlabel.h"

#include <tdelocale.h>
#include <klineedit.h>
#include <kpassdlg.h>
#include <kuser.h>

#include <tqregexp.h>
#include <tqlayout.h>
#include <tqlabel.h>
#include <tqtimer.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <syslog.h>

//#define PAM_GREETER_DEBUG

class TDMPasswordEdit : public KPasswordEdit {
public:
	TDMPasswordEdit( TQWidget *parent ) : KPasswordEdit( parent, 0 ) {}
	TDMPasswordEdit( KPasswordEdit::EchoModes echoMode, TQWidget *parent ) : KPasswordEdit( echoMode, parent, 0 ) {}
protected:
	virtual void contextMenuEvent( TQContextMenuEvent * ) {}
};

static FILE *logFile;
static void kg_debug(const char* fmt, ...)
{
    va_list lst;
    va_start(lst, fmt);

#ifdef PAM_GREETER_DEBUG
#if 0
    vfprintf(logFile, fmt, lst);
    fflush(logFile);
#else
    char buf[6000];
    sprintf(buf, "*** %s\n", fmt);
    vsyslog(LOG_WARNING, buf, lst);
#endif
#endif
    va_end(lst);
}

static KPasswordEdit::EchoModes echoMode;

KPamGreeter::KPamGreeter( KGreeterPluginHandler *_handler,
                                  KdmThemer *themer,
                                  TQWidget *parent, TQWidget *pred,
                                  const TQString &_fixedEntity,
                                  Function _func, Context _ctx ) :
	TQObject(),
	KGreeterPlugin( _handler ),
	fixedUser( _fixedEntity ),
	func( _func ),
	ctx( _ctx ),
	exp( -1 ),
	pExp( -1 ),
	running( false ),
	userEntryLocked(false),
	suppressInfoMsg(false)
{
        ctx = Login;

        kg_debug("KPamGreeter constructed\n");

	m_parentWidget = parent;

	KdmItem *user_entry = 0, *pw_entry = 0;
	int line = 0;

	layoutItem = 0;

	if (themer &&
	    (!(user_entry = themer->findNode( "user-entry" )) ||
	     !(pw_entry = themer->findNode( "pw-entry" ))))
		themer = 0;

        m_themer = themer;

	if (!themer)
		layoutItem = TQT_TQLAYOUTITEM(new TQGridLayout( 0, 0, 10 ));

        loginLabel = 0;
        authLabel.clear();
        authEdit.clear();
	loginLabel = 0;
	loginEdit = 0;
	if (ctx == ExUnlock || ctx == ExChangeTok)
		fixedUser = KUser().loginName();
	if (func != ChAuthTok) {
		kg_debug("func != ChAuthTok\n");
		kg_debug("fixedUser: *%s*\n", fixedUser.latin1());

		if (fixedUser.isEmpty()) {
			loginEdit = new KLineEdit( parent );
			loginEdit->setContextMenuEnabled( false );
			connect( loginEdit, TQT_SIGNAL(lostFocus()), TQT_SLOT(slotLoginLostFocus()) );
			connect( loginEdit, TQT_SIGNAL(lostFocus()), TQT_SLOT(slotActivity()) );
			connect( loginEdit, TQT_SIGNAL(textChanged( const TQString & )), TQT_SLOT(slotActivity()) );
			connect( loginEdit, TQT_SIGNAL(selectionChanged()), TQT_SLOT(slotActivity()) );
			if (pred) {
				parent->setTabOrder( pred, loginEdit );
				pred = loginEdit;
			}
			if (!getLayoutItem()) {
				loginEdit->adjustSize();
				user_entry->setWidget( loginEdit );
			} else {
				loginLabel = new TQLabel( loginEdit, i18n("Username:"), parent );
				getLayoutItem()->addWidget( loginLabel, line, 0 );
				getLayoutItem()->addWidget( loginEdit, line++, 1 );
			}
		} else if (ctx != Login && ctx != Shutdown && getLayoutItem()) {
			loginLabel = new TQLabel( i18n("Username:"), parent );
			getLayoutItem()->addWidget( loginLabel, line, 0 );
			getLayoutItem()->addWidget( new TQLabel( fixedUser, parent ), line++, 1 );
		}
#if 0
		if (echoMode == -1)
			passwdEdit = new TDMPasswordEdit( parent );
		else
			passwdEdit = new TDMPasswordEdit( echoMode,
			                                  parent );
		connect( passwdEdit, TQT_SIGNAL(textChanged( const TQString & )),
		         TQT_SLOT(slotActivity()) );
		connect( passwdEdit, TQT_SIGNAL(lostFocus()), TQT_SLOT(slotActivity()) );
		if (pred) {
			parent->setTabOrder( pred, passwdEdit );
			pred = passwdEdit;
		}
		if (!getLayoutItem()) {
			passwdEdit->adjustSize();
			pw_entry->setWidget( passwdEdit );
		} else {
			passwdLabel = new TQLabel( passwdEdit,
			                          func == Authenticate ?
			                            i18n("hello &Password:") :
			                            i18n("Current &password:"),
			                          parent );
			getLayoutItem()->addWidget( passwdLabel, line, 0 );
			getLayoutItem()->addWidget( passwdEdit, line++, 1 );
		}
#endif
		if (loginEdit)
			loginEdit->setFocus();
	}
	if (func != Authenticate) {
		if (echoMode == -1) {
                        authEdit << new TDMPasswordEdit( echoMode, parent );
			authEdit << new TDMPasswordEdit( echoMode, parent );
		} else {
			authEdit << new TDMPasswordEdit( parent );
			authEdit << new TDMPasswordEdit( parent );
		}
		authLabel << new TQLabel( authEdit[0], i18n("&New password:"), parent );
		authLabel << new TQLabel( authEdit[1], i18n("Con&firm password:"), parent );
		if (pred) {
			parent->setTabOrder( pred, authEdit[0] );
			parent->setTabOrder( authEdit[0], authEdit[1] );
		}
		if (getLayoutItem()) {
			getLayoutItem()->addWidget( authLabel[0], line, 0 );
			getLayoutItem()->addWidget( authEdit[0], line++, 1 );
			getLayoutItem()->addWidget( authLabel[1], line, 0 );
			getLayoutItem()->addWidget( authEdit[1], line, 1 );
		}
		if (authEdit.size() >= 2)
                        authEdit[1]->setFocus();
	}
}

// virtual
KPamGreeter::~KPamGreeter()
{
        kg_debug("KPamGreeter::~KPamGreeter");
	abort();
	if (!layoutItem) {
		delete loginEdit;
		return;
	}
	TQLayoutIterator it = TQT_TQLAYOUT(layoutItem)->iterator();
	for (TQLayoutItem *itm = it.current(); itm; itm = ++it)
		 delete itm->widget();
	delete layoutItem;
        kg_debug("destructor finished, good bye");
}

void // virtual
KPamGreeter::loadUsers( const TQStringList &users )
{
	TDECompletion *userNamesCompletion = new TDECompletion;
	userNamesCompletion->setItems( users );
	loginEdit->setCompletionObject( userNamesCompletion );
	loginEdit->setAutoDeleteCompletionObject( true );
	loginEdit->setCompletionMode( TDEGlobalSettings::CompletionAuto );
}

void // virtual
KPamGreeter::presetEntity( const TQString &entity, int field )
{
        kg_debug("presetEntity(%s,%d) called!\n", entity.latin1(), field);
	loginEdit->setText( entity );
	if (field == 1 && authEdit.size() >= 1)
		authEdit[0]->setFocus();
	else {
		loginEdit->setFocus();
		loginEdit->selectAll();
		if (field == -1 && authEdit.size() >= 1) {
			authEdit[0]->setText( "     " );
			authEdit[0]->setEnabled( false );
			authTok = false;
		}
	}
	curUser = entity;
}

TQString // virtual
KPamGreeter::getEntity() const
{
	return fixedUser.isEmpty() ? loginEdit->text() : fixedUser;
}

void // virtual
KPamGreeter::setUser( const TQString &user )
{
	// assert( fixedUser.isEmpty() );
	curUser = user;
	loginEdit->setText( user );
        if (authEdit.size() >= 1) {
	    authEdit[0]->setFocus();
 	    authEdit[0]->selectAll();
        }
}

void KPamGreeter::lockUserEntry( const bool lock ) {
	userEntryLocked = lock;
	loginEdit->setEnabled(!lock);
}

void // virtual
KPamGreeter::setPassword( const TQString &pass )
{
	authEdit[0]->erase();
	authEdit[0]->insert( pass );
}

void // virtual
KPamGreeter::setEnabled(bool enable)
{
	// assert( !passwd1Label );
	// assert( func == Authenticate && ctx == Shutdown );
//	if (loginLabel)
//		loginLabel->setEnabled( enable );
		authEdit[0]->setEnabled( enable );
		setActive( enable );
		if (enable) {
			authEdit[0]->setFocus();
		}
	}

void KPamGreeter::setInfoMessageDisplay(bool enable) {
	suppressInfoMsg = !enable;
}

void KPamGreeter::setPasswordPrompt(const TQString &prompt) {
#if 0
	if (passwdLabel) {
		if (prompt != TQString::null) {
			passwdLabel->setText(prompt);
		}
		else {
			passwdLabel->setText(passwordPrompt());
		}
		if (grid) {
			grid->invalidate();
			grid->activate();
		}
	}
#endif
}

void // private
KPamGreeter::returnData()
{
       kg_debug("*************** returnData called with exp %d\n", exp);


	switch (exp) {
	case 0:
		handler->gplugReturnText( (loginEdit ? loginEdit->text() :
						       fixedUser).local8Bit(),
					  KGreeterPluginHandler::IsUser );
		break;
	case 1:
		handler->gplugReturnText( authEdit[0]->password().local8Bit(),
					  KGreeterPluginHandler::IsPassword |
					  KGreeterPluginHandler::IsSecret );
		break;
	case 2:
		handler->gplugReturnText( authEdit[1]->password().local8Bit(),
					  KGreeterPluginHandler::IsSecret );
		break;
	default: // case 3:
		handler->gplugReturnText( authEdit[2]->password().local8Bit(),
					  KGreeterPluginHandler::IsNewPassword |
					  KGreeterPluginHandler::IsSecret );
		break;
	}
}

bool // virtual
KPamGreeter::textMessage( const char *text, bool err )
{
	kg_debug(" ************** textMessage(%s, %d)\n", text, err);

	if (!authEdit.size()) {
		return false;
	}

	if (!err && suppressInfoMsg) {
		return true;
	}

	if (getLayoutItem()) {
		TQLabel* label = new TQLabel(TQString::fromUtf8(text), m_parentWidget);
		getLayoutItem()->addWidget(label, state+1, 0, 0);
	}

	return true;
}

void // virtual
KPamGreeter::textPrompt( const char *prompt, bool echo, bool nonBlocking )
{
    kg_debug("textPrompt called with prompt %s echo %d nonBlocking %d", prompt, echo, nonBlocking);
    kg_debug("state is %d, authEdit.size is %d\n", state, authEdit.size());

    if (state == 0 && echo) {
        if (loginLabel) {
	    loginLabel->setText(TQString::fromUtf8(prompt));
	}
        else if (m_themer) {
            KdmLabel *tdmlabel = static_cast<KdmLabel*>(m_themer->findNode("user-label"));
            if (tdmlabel) {
                //userLabel->setText(TQString::fromUtf8(prompt));
                tdmlabel->label.text = TQString::fromUtf8(prompt);
                TQTimer::singleShot(0, tdmlabel, TQT_SLOT(update()));
            }
        }
    }
    else if (state >= authEdit.size()) {
	if (getLayoutItem()) {
   	    TQLabel* label = new TQLabel(TQString::fromUtf8(prompt), m_parentWidget);
	    getLayoutItem()->addWidget(label, state+1, 0, 0);
            kg_debug("added label widget to layout");
        }
        else if (m_themer) {
            kg_debug("themer found!");

            KdmLabel *tdmlabel = static_cast<KdmLabel*>(m_themer->findNode("pw-label"));
            if (tdmlabel) {
                //userLabel->setText(TQString::fromUtf8(prompt));
                TQString str = TQString::fromUtf8(prompt);
                tdmlabel->label.text = str;
                TQTimer::singleShot(0, tdmlabel, TQT_SLOT(update()));
            }
        }

	TDMPasswordEdit* passwdEdit;

	if (echoMode == -1)
	    passwdEdit = new TDMPasswordEdit( m_parentWidget );
	else
	    passwdEdit = new TDMPasswordEdit( echoMode, m_parentWidget);
	connect( passwdEdit, TQT_SIGNAL(textChanged( const TQString & )),
		TQT_SLOT(slotActivity()) );
	connect( passwdEdit, TQT_SIGNAL(lostFocus()), TQT_SLOT(slotActivity()) );
	authEdit << passwdEdit;

#if 1
       for(TQValueList<KPasswordEdit*>::iterator it = authEdit.begin();
        it != authEdit.end();
        ++it) {
       if ((*it)->isEnabled() && (*it)->text().isEmpty()) {
          (*it)->setFocus();
          break;
        }
       }
#endif
       if (getLayoutItem())
	   getLayoutItem()->addWidget(passwdEdit, state+1, 1, 0);

       if (m_themer) {
           kg_debug("themer found!");
	   KdmItem *pw_entry = 0;

	   pw_entry = m_themer->findNode("pw-entry");

	   if (pw_entry && passwdEdit)
	       pw_entry->setWidget(passwdEdit);

           if (0) {
                //userLabel->setText(TQString::fromUtf8(prompt));
                //tdmlabel->label.text = TQString::fromUtf8(prompt);
                //TQTimer::singleShot(0, tdmlabel, TQT_SLOT(update()));
           }
       } 
       else
           kg_debug("no themer found!");
    }
    ++state;
    pExp = exp;

    exp = authEdit.size();
    kg_debug("state %d exp: %d, has %d\n", state, exp, has);

    if (has >= exp || nonBlocking)
	returnData();
}

bool // virtual
KPamGreeter::binaryPrompt( const char *, bool )
{
	// this simply cannot happen ... :}
	return true;
}

void // virtual
KPamGreeter::start()
{
   kg_debug("******* start() called\n");

   while(authEdit.begin() != authEdit.end()) {
       KPasswordEdit* item = *authEdit.remove(authEdit.begin());
       delete item;
   }

   while(authLabel.begin() != authLabel.end()) {
       TQLabel* item = *authLabel.remove(authLabel.begin());
       delete item;
   }

   authTok = !(authEdit.size() >= 2 && authEdit[1]->isEnabled());
   exp = has = -1;
   state = 0;
   running = true;
   handler->gplugStart();
}

void // virtual
KPamGreeter::suspend()
{
}

void // virtual
KPamGreeter::resume()
{
}

void // virtual
KPamGreeter::next()
{
        kg_debug("********* next() called state %d\n", state);

	if (state == 0 && running && handler) {
                kg_debug(" **** returned text!\n");
		handler->gplugReturnText( (loginEdit ? loginEdit->text() :
		                  fixedUser).local8Bit(),
		                          KGreeterPluginHandler::IsUser );
                setActive(false);
        }
	
        has = 0;

    for(TQValueList<KPasswordEdit*>::iterator it = authEdit.begin();
        it != authEdit.end();
        ++it) {

          has++;
        if ((*it)->hasFocus()) {
          ++it;
          if (it != authEdit.end())
              (*it)->setFocus();
          break;
        }
        if (it == authEdit.end())
           has = -1;
   }

   kg_debug(" has %d and exp %d\n", has, exp);

#if 0
	// assert( running );
	if (loginEdit && loginEdit->hasFocus()) {
		passwdEdit->setFocus(); // will cancel running login if necessary
		has = 0;
	} else if (passwdEdit && passwdEdit->hasFocus()) {
		if (passwd1Edit)
			passwd1Edit->setFocus();
		has = 1;
	} else if (passwd1Edit) {
		if (passwd1Edit->hasFocus()) {
			passwd2Edit->setFocus();
			has = 1; // sic!
		} else
			has = 3;
	} else
		has = 1;
	if (exp < 0)
		handler->gplugStart();
#endif
	if (has >= exp)
		returnData();
}

void // virtual
KPamGreeter::abort()
{
	kg_debug("***** abort() called\n");

	running = false;
	if (exp >= 0) {
		exp = -1;
		handler->gplugReturnText( 0, 0 );
	}
}

void // virtual
KPamGreeter::succeeded()
{
        kg_debug("**** succeeded() called\n");

	// assert( running || timed_login );
	if (!authTok)
		setActive( false );
	else
		setAllActive( false );
	exp = -1;
	running = false;
}

void // virtual
KPamGreeter::failed()
{
	// assert( running || timed_login );
	setActive( false );
	setAllActive( false );
	exp = -1;
	running = false;
}

#include<assert.h>
void // virtual
KPamGreeter::revive()
{
	// assert( !running );
	setAllActive( true );

#if 1
        if (authEdit.size()  < 1)
                return;
#endif

        assert(authEdit.size() >= 1);
	if (authTok) {
		authEdit[0]->erase();
                if(authEdit.size() >= 2)
			authEdit[1]->erase();
		authEdit[0]->setFocus();
	} else {
		authEdit[0]->erase();
		if (loginEdit && loginEdit->isEnabled())
			authEdit[0]->setEnabled( true );
		else {
			setActive( true );
			if (loginEdit && loginEdit->text().isEmpty())
				loginEdit->setFocus();
			else
				authEdit[0]->setFocus();
		}
	}
}

void // virtual
KPamGreeter::clear()
{
	// assert( !running && !passwd1Edit );
	authEdit[0]->erase();
	if (loginEdit) {
		loginEdit->clear();
		loginEdit->setFocus();
		curUser = TQString::null;
	} else
		authEdit[0]->setFocus();
}


// private

void
KPamGreeter::setActive( bool enable )
{
	if (loginEdit) {
		if (userEntryLocked) {
			loginEdit->setEnabled( false );
		}
		else {
			loginEdit->setEnabled( enable );
		}
	}
}

void
KPamGreeter::setAllActive( bool enable )
{
    for(TQValueList<KPasswordEdit*>::iterator it = authEdit.begin();
        it != authEdit.end();
        ++it)
        (*it)->setEnabled( enable );
}

void
KPamGreeter::slotLoginLostFocus()
{
	if (!running)
		return;
	if (exp > 0) {
		if (curUser == loginEdit->text())
			return;
		exp = -1;
		handler->gplugReturnText( 0, 0 );
	}
	curUser = loginEdit->text();
        kg_debug("curUser is %s", curUser.latin1());
	handler->gplugSetUser( curUser );
}

void
KPamGreeter::slotActivity()
{  
        kg_debug("slotActivity");

	if (running)
		handler->gplugActivity();
}

// factory

static bool init( const TQString &,
                  TQVariant (*getConf)( void *, const char *, const TQVariant & ),
                  void *ctx )
{
	echoMode = (KPasswordEdit::EchoModes) getConf( ctx, "EchoMode", TQVariant( -1 ) ).toInt();
	TDEGlobal::locale()->insertCatalogue( "kgreet_pam" );
	return true;
}

static void done( void )
{
	TDEGlobal::locale()->removeCatalogue( "kgreet_pam" );
	if (logFile && (logFile != stderr))
	{
	    fclose(logFile);
	}
	logFile = 0;
}

static KGreeterPlugin *
create( KGreeterPluginHandler *handler, KdmThemer *themer,
        TQWidget *parent, TQWidget *predecessor,
        const TQString &fixedEntity,
        KGreeterPlugin::Function func,
        KGreeterPlugin::Context ctx )
{
	return new KPamGreeter( handler, themer, parent, predecessor, fixedEntity, func, ctx );
}

KDE_EXPORT kgreeterplugin_info kgreeterplugin_info = {
	I18N_NOOP("Pam conversation plugin"), "pam",
	kgreeterplugin_info::Local | kgreeterplugin_info::Presettable,
	init, done, create
};

#include "kgreet_pam.moc"
