/*

Conversation widget for kdm greeter

Copyright (C) 1997, 1998, 2000 Steffen Hansen <hansen@kde.org>
Copyright (C) 2000-2004 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_winbind.h"

#include "themer/kdmthemer.h"
#include "themer/kdmitem.h"
//#include <klocale.h>
//#include <kglobal.h>
//#include <kdebug.h>
#include <QComboBox> //<kcombobox.h>
#include <QLineEdit> //<klineedit.h>
#include <myuser.h>
#include <QProcess> //<kprocess.h>

#include <QRegExp>
#include <QLayout>
#include <QLabel>
#include <QContextMenuEvent>
#include <QGridLayout>
#include <QTextStream>
#include <QCompleter>

#include <QFileInfo>

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

static int echoMode;

class KDMPasswordEdit : public QLineEdit {
public:
    KDMPasswordEdit( QWidget *parent ) : QLineEdit(parent)
    {
        /*if (::echoMode == -1)
            setPasswordMode(true);
        else*/
            setEchoMode(::echoMode ? Password : NoEcho);
        setContextMenuPolicy(Qt::NoContextMenu);
        setMaxLength(127);
    }
};

static char separator;
static QStringList staticDomains;
static QString defaultDomain;

static void
splitEntity(const QString &ent, QString &dom, QString &usr)
{
    int pos = ent.indexOf(separator);
    if (pos < 0)
        dom = "<local>", usr = ent;
    else
        dom = ent.left(pos), usr = ent.mid(pos + 1);
}

KWinbindGreeter::KWinbindGreeter(KGreeterPluginHandler *_handler,
                                 QWidget *parent,
                                 const QString &_fixedEntity,
                                 Function _func, Context _ctx) :
    QObject(),
    KGreeterPlugin(_handler),
    func(_func),
    ctx(_ctx),
    exp(-1),
    pExp(-1),
    running(false)
{
    m_domainLister=nullptr;

    QGridLayout *grid = nullptr;

    int line = 0;

    if (!_handler->gplugHasNode("domain-entry") ||
        !_handler->gplugHasNode("user-entry") ||
        !_handler->gplugHasNode("pw-entry"))
    {
        parent = new QWidget(parent);
        parent->setObjectName("talker");
        widgetList << parent;
        grid = new QGridLayout(parent);
        grid->setMargin(0);
    }

    domainLabel = loginLabel = passwdLabel = passwd1Label = passwd2Label = nullptr;
    domainCombo = nullptr;
    loginEdit = nullptr;
    passwdEdit = passwd1Edit = passwd2Edit = nullptr;
    if (ctx == ExUnlock || ctx == ExChangeTok)
        splitEntity(MyUser::loginName(), fixedDomain, fixedUser);
    else
        splitEntity(_fixedEntity, fixedDomain, fixedUser);
    if (func != ChAuthTok) {
        if (fixedUser.isEmpty()) {
            domainCombo = new QComboBox(parent);
            connect(domainCombo, SIGNAL(activated(const QString &)),
                    SLOT(slotChangedDomain(const QString &)));
            connect(domainCombo, SIGNAL(activated(const QString &)),
                    SLOT(slotLoginLostFocus()));
            connect(domainCombo, SIGNAL(activated(const QString &)),
                    SLOT(slotChanged()));
            // should handle loss of focus
            loginEdit = new QLineEdit(parent);
            loginEdit->setMaxLength(127); //posix max is 8?
            loginEdit->setContextMenuPolicy(Qt::NoContextMenu);

            if (!grid) {
                loginEdit->setObjectName("user-entry");
                domainCombo->setObjectName("domain-entry");
                widgetList << domainCombo << loginEdit;
            } else {
                domainLabel = new QLabel(tr("&Domain:"), parent);
                domainLabel->setBuddy(domainCombo);
                loginLabel = new QLabel(tr("&Username:"), parent);
                loginLabel->setBuddy(loginEdit);
                grid->addWidget(domainLabel, line, 0);
                grid->addWidget(domainCombo, line++, 1);
                grid->addWidget(loginLabel, line, 0);
                grid->addWidget(loginEdit, line++, 1);
            }
            connect(loginEdit, SIGNAL(editingFinished()), SLOT(slotLoginLostFocus()));
            connect(loginEdit, SIGNAL(editingFinished()), SLOT(slotChanged()));
            connect(loginEdit, SIGNAL(textChanged(const QString &)), SLOT(slotChanged()));
            connect(loginEdit, SIGNAL(selectionChanged()), SLOT(slotChanged()));
            domainCombo->addItems(staticDomains);
            QTimer::singleShot(0, this, SLOT(slotStartDomainList()));
        } else if (ctx != Login && ctx != Shutdown && grid) {
            domainLabel = new QLabel(tr("Domain:"), parent);
            grid->addWidget(domainLabel, line, 0);
            grid->addWidget(new QLabel(fixedDomain, parent), line++, 1);
            loginLabel = new QLabel(tr("Username:"), parent);
            grid->addWidget(loginLabel, line, 0);
            grid->addWidget(new QLabel(fixedUser, parent), line++, 1);
        }
        passwdEdit = new KDMPasswordEdit(parent);
        connect(passwdEdit, SIGNAL(textChanged(const QString &)),
                SLOT(slotChanged()));
        connect(passwdEdit, SIGNAL(editingFinished()), SLOT(slotChanged()));

        if (!grid) {
            passwdEdit->setObjectName("pw-entry");
            widgetList << passwdEdit;
        } else {
            passwdLabel = new QLabel(func == Authenticate ?
                                         tr("&Password:") :
                                         tr("Current &password:"),
                                     parent);
            passwdLabel->setBuddy(passwdEdit);
            grid->addWidget(passwdLabel, line, 0);
            grid->addWidget(passwdEdit, line++, 1);
        }

        if (loginEdit)
            loginEdit->setFocus();
        else
            passwdEdit->setFocus();
    }
    if (func != Authenticate) {
        passwd1Edit = new KDMPasswordEdit(parent);
        passwd1Label = new QLabel(tr("&New password:"), parent);
        passwd1Label->setBuddy(passwd1Edit);
        passwd2Edit = new KDMPasswordEdit(parent);
        passwd2Label = new QLabel(tr("Con&firm password:"), parent);
        passwd2Label->setBuddy(passwd2Edit);
        if (grid) {
            grid->addWidget(passwd1Label, line, 0);
            grid->addWidget(passwd1Edit, line++, 1);
            grid->addWidget(passwd2Label, line, 0);
            grid->addWidget(passwd2Edit, line, 1);
        }
        if (!passwdEdit)
            passwd1Edit->setFocus();
    }
}

// virtual
KWinbindGreeter::~KWinbindGreeter()
{
    abort();
    qDeleteAll(widgetList);
}

void
KWinbindGreeter::slotChangedDomain(const QString &dom)
{
    if (!loginEdit->completer())
        return;
    QStringList users;
    if (dom == "<local>") {
        for (QStringList::ConstIterator it = allUsers.constBegin(); it != allUsers.constEnd(); ++it)
            if ((*it).indexOf(separator) < 0)
                users << *it;
    } else {
        QString st(dom + separator);
        for (QStringList::ConstIterator it = allUsers.constBegin(); it != allUsers.constEnd(); ++it)
            if ((*it).startsWith(st))
                users << (*it).mid(st.length());
    }
    //loginEdit->completionObject()->setItems( users );
    if ( loginEdit->completer()) delete loginEdit->completer(); //alex
    QCompleter *userNamesCompletion = new QCompleter(users,this);
    loginEdit->setCompleter( userNamesCompletion );
    userNamesCompletion->setCompletionMode( QCompleter::InlineCompletion );
}

void // virtual
KWinbindGreeter::loadUsers(const QStringList &users)
{
    allUsers = users;
    QCompleter *userNamesCompletion = new QCompleter(users,this);
    loginEdit->setCompleter(userNamesCompletion);
    //loginEdit->setAutoDeleteCompletionObject(true);
    /*loginEdit*/userNamesCompletion->setCompletionMode(QCompleter::InlineCompletion/*KGlobalSettings::CompletionAuto*/);
   slotChangedDomain(defaultDomain);
}

void // virtual
KWinbindGreeter::presetEntity(const QString &entity, int field)
{
    QString dom, usr;
    splitEntity(entity, dom, usr);
    //domainCombo->setCurrentItem(dom, true); alex:
    int ind = domainCombo->findText( dom );
    if (ind == -1) { domainCombo->addItem( dom ); ind = domainCombo->count()-1; }
    domainCombo->setCurrentIndex( ind );

    slotChangedDomain(dom);
    loginEdit->setText(usr);
    if (field > 1) {
        passwdEdit->setFocus();
    } else if (field == 1 || field == -1) {
        if (field == -1) {
            passwdEdit->setText("     ");
            passwdEdit->setEnabled(false);
            authTok = false;
        }
        loginEdit->setFocus();
        loginEdit->selectAll();
    }
    curUser = entity;
}

QString // virtual
KWinbindGreeter::getEntity() const
{
    QString dom, usr;
    if (fixedUser.isEmpty()) {
        dom = domainCombo->currentText();
        usr = loginEdit->text().trimmed();
        loginEdit->setText(usr);
    } else {
        dom = fixedDomain;
        usr = fixedUser;
    }
    return dom == "<local>" ? usr : dom + separator + usr;
}

void // virtual
KWinbindGreeter::setUser(const QString &user)
{
    // assert (fixedUser.isEmpty());
    curUser = user;
    QString dom, usr;
    splitEntity(user, dom, usr);
    //domainCombo->setCurrentItem(dom, true); alex:
    int ind = domainCombo->findText(dom);
    if (ind == -1) { domainCombo->addItem( dom ); ind = domainCombo->count()-1; }
    domainCombo->setCurrentIndex( ind );

    slotChangedDomain( dom );
    loginEdit->setText(usr);
    passwdEdit->setFocus();
    passwdEdit->selectAll();
}

void // virtual
KWinbindGreeter::setEnabled(bool enable)
{
    // assert(!passwd1Label);
    // assert(func == Authenticate && ctx == Shutdown);
//    if (domainCombo)
//        domainCombo->setEnabled(enable);
//    if (loginLabel)
//        loginLabel->setEnabled(enable);
    passwdLabel->setEnabled(enable);
    setActive(enable);
    if (enable)
        passwdEdit->setFocus();
}

void // private
KWinbindGreeter::returnData()
{
    switch (exp) {
    case 0:
        handler->gplugReturnText(getEntity().toLocal8Bit(),
                                 KGreeterPluginHandler::IsUser);
        break;
    case 1:
        handler->gplugReturnText(passwdEdit->text().toLocal8Bit(),
                                 KGreeterPluginHandler::IsPassword |
                                 KGreeterPluginHandler::IsSecret);
        break;
    case 2:
        handler->gplugReturnText(passwd1Edit->text().toLocal8Bit(),
                                 KGreeterPluginHandler::IsSecret);
        break;
    default: // case 3:
        handler->gplugReturnText(passwd2Edit->text().toLocal8Bit(),
                                 KGreeterPluginHandler::IsNewPassword |
                                 KGreeterPluginHandler::IsSecret);
        break;
    }
}

bool // virtual
KWinbindGreeter::textMessage(const char *text, bool err)
{
    if (!err &&
        QString::fromLocal8Bit(text).indexOf(QRegExp("^Changing password for [^ ]+$")) >= 0)
        return true;
    return false;
}

void // virtual
KWinbindGreeter::textPrompt(const char *prompt, bool echo, bool nonBlocking)
{
    pExp = exp;
    if (echo) {
        exp = 0;
    } else if (!authTok) {
        exp = 1;
    } else {
        QString pr = QString::fromLocal8Bit(prompt);
		if (pr.indexOf(QRegExp("\\bpassword\\b", Qt::CaseInsensitive)) >= 0 ||
            pr.indexOf(QRegExp(tr("\\bpassword\\b"), Qt::CaseInsensitive)) >= 0) { //alex: russian too!
            if (pr.indexOf(QRegExp("\\b(re-?(enter|type)|re?(enter|type)|again|confirm|repeat)\\b",Qt::CaseInsensitive)) >= 0 ||
                   pr.indexOf(QRegExp(tr("\\b(re-?(enter|type)|re?(enter|type)|again|confirm|repeat)\\b"), //alex
                                      Qt::CaseInsensitive)) >= 0) {
            exp = 3;
        } else if (pr.indexOf(QRegExp("\\bnew\\b", Qt::CaseInsensitive)) >= 0 ||
                   pr.indexOf(QRegExp(tr("\\bnew\\b"), Qt::CaseInsensitive)) >= 0) { //alex
            exp = 2;
            } else //if (pr.indexOf(QRegExp("\\b(old|current)\\b", Qt::CaseInsensitive))>=0 ||
                   //    pr.indexOf(QRegExp(tr("\\b(old|current)\\b"), Qt::CaseInsensitive))>=0) //is too strict
            {
                handler->gplugReturnText("", KGreeterPluginHandler::IsOldPassword | KGreeterPluginHandler::IsSecret);
                return;
        }
        } else {
            handler->gplugMsgBox(QMessageBox::Critical,
                                 tr("Unrecognized prompt \"%1\"").arg(pr));
            handler->gplugReturnText(nullptr, 0);
            exp = -1;
            return;
        }
    }

    if (pExp >= 0 && pExp >= exp) {
        revive();
        has = -1;
    }

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

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

void // virtual
KWinbindGreeter::start()
{
    authTok = !(passwdEdit && passwdEdit->isEnabled());
    exp = has = -1;
    running = true;
}

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

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

void // virtual
KWinbindGreeter::next()
{
    // assert(running);
    int pHas = has;
    if (domainCombo && domainCombo->hasFocus()) {
        loginEdit->setFocus();
    } else 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();
    else if (has >= exp && has > pHas)
        returnData();
}

void // virtual
KWinbindGreeter::abort()
{
    running = false;
    if (exp >= 0) {
        exp = -1;
        handler->gplugReturnText(nullptr, 0);
    }
}

void // virtual
KWinbindGreeter::succeeded()
{
    // assert(running || timed_login);
    if (!authTok) {
        setActive(false);
        if (passwd1Edit) {
            authTok = true;
            return;
        }
    } else {
        setActive2(false);
    }
    exp = -1;
    running = false;
}

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

void // virtual
KWinbindGreeter::revive()
{
    // assert(!running);
    setActive2(true);
    if (authTok) {
        passwd1Edit->clear();
        passwd2Edit->clear();
        passwd1Edit->setFocus();
    } else {
        passwdEdit->clear();
        if (loginEdit && loginEdit->isEnabled()) {
            passwdEdit->setEnabled(true);
        } else {
            setActive(true);
            if (loginEdit && loginEdit->text().isEmpty())
                loginEdit->setFocus();
            else
                passwdEdit->setFocus();
        }
    }
}

void // virtual
KWinbindGreeter::clear()
{
    // assert(!running && !passwd1Edit);
    passwdEdit->clear();
    if (loginEdit) {
        domainCombo->setCurrentIndex(domainCombo->findText(defaultDomain));
        slotChangedDomain(defaultDomain);
        loginEdit->clear();
        loginEdit->setFocus();
        curUser.clear();
    } else {
        passwdEdit->setFocus();
    }
}


// private

void
KWinbindGreeter::setActive(bool enable)
{
    if (domainCombo)
        domainCombo->setEnabled(enable);
    if (loginEdit)
        loginEdit->setEnabled(enable);
    if (passwdEdit)
        passwdEdit->setEnabled(enable);
}

void
KWinbindGreeter::setActive2(bool enable)
{
    if (passwd1Edit) {
        passwd1Edit->setEnabled(enable);
        passwd2Edit->setEnabled(enable);
    }
}

void
KWinbindGreeter::slotLoginLostFocus()
{
    if (!running)
        return;
    QString ent(getEntity());
    if (exp > 0) {
        if (curUser == ent)
            return;
        exp = -1;
        handler->gplugReturnText(nullptr, 0);
    }
    curUser = ent;
    handler->gplugSetUser(curUser);
}

void
KWinbindGreeter::slotChanged()
{
    if (running)
        handler->gplugChanged();
}

void
KWinbindGreeter::slotStartDomainList()
{
    static int astra_winbind = -1;
    if (astra_winbind == -1) {
        if (QFileInfo("/usr/bin/astra-winbind-domains").exists()) astra_winbind = 1;
        else astra_winbind = 0;
    }
    if (m_domainLister) delete m_domainLister;
    m_domainLister = new QProcess(this);
    QStringList pargs;
    if (!astra_winbind) pargs << "--own-domain" << "--trusted-domains";
    m_domainLister->setReadChannel(QProcess::StandardOutput); //setOutputChannelMode(KProcess::OnlyStdoutChannel);
    connect(m_domainLister, SIGNAL(finished(int, QProcess::ExitStatus)),
            SLOT(slotEndDomainList()));
    m_domainLister->start(astra_winbind ? QString("astra-winbind-domains") : QString("wbinfo"),pargs);
}

void
KWinbindGreeter::slotEndDomainList()
{
    QStringList domainList;
    if (!m_domainLister) return; //alex

    while (!m_domainLister->atEnd()) {
        QString dom = QString::fromLocal8Bit(m_domainLister->readLine());
        dom.chop(1);
        if (!staticDomains.contains(dom))
            domainList.append(dom);
    }

    delete m_domainLister; m_domainLister=nullptr;

    for (int i = domainCombo->count(), min = staticDomains.count(); --i >= min;) {
        int dli = domainList.indexOf(domainCombo->itemText(i));
        if (dli < 0) {
            if (i == domainCombo->currentIndex())
                domainCombo->setCurrentIndex(domainCombo->findText(defaultDomain));
            domainCombo->removeItem(i);
        } else {
            domainList.removeAt(dli);
        }
    }
    domainCombo->addItems(domainList);

    QTimer::singleShot(60 * 1000, this, SLOT(slotStartDomainList()));
}

// factory

static bool init(const QString &,
                 QVariant(*getConf)(void *, const char *, const QVariant &),
                 void *ctx)
{
    echoMode = getConf(ctx, "EchoPasswd", QVariant(-1)).toInt();

    staticDomains = getConf(ctx, "winbind.Domains", QVariant("")).toString().split(':', QString::SkipEmptyParts);
    if (!staticDomains.size())
        staticDomains << "<local>";
    defaultDomain = getConf(ctx, "winbind.DefaultDomain", QVariant(staticDomains.first())).toString();
    if (!defaultDomain.isEmpty() && !staticDomains.contains(defaultDomain))
        staticDomains.prepend(defaultDomain);
    QString sepstr = getConf(ctx, "winbind.Separator", QVariant(QString())).toString();
    if (sepstr.isNull()) {
        FILE *sepfile = popen("wbinfo --separator 2>/dev/null", "r");
        if (sepfile) {
            QTextStream(sepfile) >> sepstr;
            if (pclose(sepfile))
                sepstr = "\\";
        } else {
            sepstr = "\\";
        }
    }
    separator = sepstr[0].toLatin1();

    //KGlobal::locale()->insertCatalog("kgreet_winbind"); alex: what TODO
    return true;
}

static void done(void)
{
    //KGlobal::locale()->removeCatalog("kgreet_winbind"); alex: what TODO
    // avoid static deletion problems ... hopefully
    staticDomains.clear();
    defaultDomain.clear();
}

static KGreeterPlugin *
create(KGreeterPluginHandler *handler,
       QWidget *parent,
       const QString &fixedEntity,
       KGreeterPlugin::Function func,
       KGreeterPlugin::Context ctx)
{
    return new KWinbindGreeter(handler, parent, fixedEntity, func, ctx);
}

Q_DECL_EXPORT/*KDE_EXPORT*/ KGreeterPluginInfo kgreeterplugin_info = {
    QT_TR_NOOP("Winbind / Samba"), "classic",
    KGreeterPluginInfo::Local | KGreeterPluginInfo::Fielded | KGreeterPluginInfo::Presettable,
    init, done, create
};

//#include "kgreet_winbind.moc"
