/*
    kpgpui.cpp

    Copyright (C) 2001,2002 the KPGP authors
    See file AUTHORS.kpgp for details

    This file is part of KPGP, the KDE PGP/GnuPG support library.

    KPGP 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.

    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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 */

//#include <stdio.h>

#include <qvgroupbox.h>
#include <qvbox.h>
#include <qlabel.h>
#include <qwhatsthis.h>
#include <qapplication.h>
#include <qtextcodec.h>
#include <qdatetime.h>
#include <qpixmap.h>

#include <klocale.h>
#include <kpassdlg.h>
#include <kcharsets.h>
#include <kseparator.h>
#include <kiconloader.h>

#include "kpgp.h"
#include "kpgpui.h"

namespace Kpgp {

PassphraseDialog::PassphraseDialog( QWidget *parent,
                                    const QString &caption, bool modal,
                                    const QString &keyID )
  :KDialogBase( parent, 0, modal, caption, Ok|Cancel )
{
  QHBox *hbox = makeHBoxMainWidget();
  hbox->setSpacing( spacingHint() );
  hbox->setMargin( marginHint() );

  QLabel *label = new QLabel(hbox);
  label->setPixmap( BarIcon("pgp-keys") );

  QWidget *rightArea = new QWidget( hbox );
  QVBoxLayout *vlay = new QVBoxLayout( rightArea, 0, spacingHint() );

  if (keyID == QString::null)
    label = new QLabel(i18n("Please enter your OpenPGP passphrase"),rightArea);
  else
    label = new QLabel(i18n("Please enter the OpenPGP passphrase for\n\"%1\"").arg(keyID),
                       rightArea);
  lineedit = new KPasswordEdit( rightArea );
  lineedit->setEchoMode(QLineEdit::Password);
  lineedit->setMinimumWidth( fontMetrics().maxWidth()*20 );
  lineedit->setFocus();
  connect( lineedit, SIGNAL(returnPressed()), this, SLOT(slotOk()) );

  vlay->addWidget( label );
  vlay->addWidget( lineedit );

  disableResize();
}


PassphraseDialog::~PassphraseDialog()
{
}

const char * PassphraseDialog::passphrase()
{
  return lineedit->password();
}


// ------------------------------------------------------------------------
// Forbidden accels for KMail: AC GH OP
//                  for KNode: ACE H O
Config::Config( QWidget *parent, const char *name, bool encrypt )
  : QWidget( parent, name ), pgp( Module::getKpgp() )
{
  QGroupBox * group;
  QLabel    * label;

  QVBoxLayout *topLayout = new QVBoxLayout( this, 0, KDialog::spacingHint() );

  group = new QVGroupBox( i18n("Warning"), this );
  group->layout()->setSpacing( KDialog::spacingHint() );
  // (mmutz) work around Qt label bug in 3.0.0 (and possibly later):
  // 1. Don't use rich text: No <qt><b>...</b></qt>
  label = new QLabel( i18n("Please check if encryption really "
  	"works before you start using it seriously. Also note that attachments "
	"are not encrypted by the PGP/GPG module."), group );
  // 2. instead, set the font to bold:
  QFont labelFont = label->font();
  labelFont.setBold( true );
  label->setFont( labelFont );
  // 3. and activate wordwarp:
  label->setAlignment( AlignLeft|WordBreak );
  // end; to remove the workaround, add <qt><b>..</b></qt> around the
  // text and remove lines QFont... -> label->setAlignment(...).
  topLayout->addWidget( group );

  group = new QVGroupBox( i18n("Encryption Tool"), this );
  group->layout()->setSpacing( KDialog::spacingHint() );

  QHBox * hbox = new QHBox( group );
  label = new QLabel( i18n("Select encryption tool to &use:"), hbox );
  toolCombo = new QComboBox( false, hbox );
  toolCombo->insertStringList( QStringList()
			       << i18n("Autodetect")
			       << i18n("GnuPG - Gnu Privacy Guard")
			       << i18n("PGP Version 2.x")
			       << i18n("PGP Version 5.x")
			       << i18n("PGP Version 6.x")
			       << i18n("Don't use any encryption tool") );
  label->setBuddy( toolCombo );
  hbox->setStretchFactor( toolCombo, 1 );
  // This is the place to add a KURLRequester to be used for asking
  // the user for the path to the executable...
  topLayout->addWidget( group );

  group = new QVGroupBox( i18n("Options"), this );
  group->layout()->setSpacing( KDialog::spacingHint() );
  storePass = new QCheckBox( i18n("&Keep passphrase in memory"), group );
  if (encrypt)
    encToSelf = new QCheckBox( i18n("Always encr&ypt to self"), group );
  else
    encToSelf = 0;
  showCipherText =
    new QCheckBox( i18n("&Show ciphered/signed text after composing"), group );
  showKeyApprovalDlg =
    new QCheckBox( i18n("Always show the encryption keys &for approval"),
		   group );
  topLayout->addWidget( group );

  topLayout->addStretch(1);

  setValues();  // is this needed by KNode, b/c for KMail, it's not.
}


Config::~Config()
{
}

void
Config::setValues()
{
  // set default values
  storePass->setChecked( pgp->storePassPhrase() );
  if (encToSelf) encToSelf->setChecked( pgp->encryptToSelf() );
  showCipherText->setChecked( pgp->showCipherText() );
  showKeyApprovalDlg->setChecked( pgp->showKeyApprovalDlg() );

  int type = 0;
  switch (pgp->pgpType) {
    // translate Kpgp::Module enum to combobox' entries:
  default:
  case Module::tAuto: type = 0; break;
  case Module::tGPG:  type = 1; break;
  case Module::tPGP2: type = 2; break;
  case Module::tPGP5: type = 3; break;
  case Module::tPGP6: type = 4; break;
  case Module::tOff:  type = 5; break;
  }
  toolCombo->setCurrentItem( type );
}

void
Config::applySettings()
{
  pgp->setStorePassPhrase(storePass->isChecked());
  if (encToSelf) pgp->setEncryptToSelf(encToSelf->isChecked());
  pgp->setShowCipherText(showCipherText->isChecked());
  pgp->setShowKeyApprovalDlg( showKeyApprovalDlg->isChecked() );

  Module::PGPType type;
  switch ( toolCombo->currentItem() ) {
    // convert combobox entry indices to Kpgp::Module constants:
  default:
  case 0: type = Module::tAuto; break;
  case 1: type = Module::tGPG;  break;
  case 2: type = Module::tPGP2; break;
  case 3: type = Module::tPGP5; break;
  case 4: type = Module::tPGP6; break;
  case 5: type = Module::tOff;  break;
  }
  pgp->pgpType = type;

  pgp->writeConfig(true);
}



// ------------------------------------------------------------------------
KeySelectionDialog::KeySelectionDialog( const KeyList& keyList,
                                        const QString& title,
                                        const QString& text,
                                        const QCString& keyId,
                                        const bool rememberChoice,
                                        const int acceptedKeys,
                                        QWidget *parent, const char *name,
                                        bool modal )
  :KDialogBase( parent, name, modal, title, Ok|Cancel, Ok)
{
  mAcceptedKeys = acceptedKeys;

  // load the key status icons
  keyGood    = new QPixmap( UserIcon("key_ok") );
  keyBad     = new QPixmap( UserIcon("key_bad") );
  keyUnknown = new QPixmap( UserIcon("key_unknown") );

  QFrame *page = makeMainWidget();
  QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );

  if( !text.isEmpty() )
  {
    QLabel *label = new QLabel( page );
    label->setText(text);
    topLayout->addWidget( label );
  }

  mListView = new QListView( page );
  mListView->setMaximumWidth( 560 );
  mListView->addColumn( i18n("Key ID") );
  mListView->addColumn( i18n("User ID") );
  mListView->setAllColumnsShowFocus( true );
  mListView->setResizeMode( QListView::LastColumn );
  mListView->setRootIsDecorated( true );
  mListView->setShowSortIndicator( true );
  mListView->setSorting( 1, true ); // sort by User ID
  mListView->setShowToolTips( true );
  topLayout->addWidget( mListView, 10 );

  if (rememberChoice) {
    mRememberCB = new QCheckBox( i18n("Remember choice"), page );
    topLayout->addWidget( mRememberCB );
    QWhatsThis::add(mRememberCB,
                    i18n("<qt><p>If you check this box your choice will "
                         "be stored and you will not be asked again."
                         "</p></qt>"));
  }

  mKeyID = QCString();

  initKeylist( keyList, keyId );

  QListViewItem *lvi = mListView->selectedItem();
  slotSelectionChanged( lvi );
  // make sure that the selected item is visible
  // (ensureItemVisible(...) doesn't work correctly in Qt 3.0.0)
  if( lvi != 0 )
    mListView->center( mListView->contentsX(), mListView->itemPos( lvi ) );

  connect( mListView, SIGNAL( selectionChanged( QListViewItem * ) ),
           this,      SLOT( slotSelectionChanged( QListViewItem * ) ) );
}


void KeySelectionDialog::initKeylist( const KeyList& keyList,
                                      const QCString& keyId )
{
  // build a list of all public keys
  for( KeyListIterator it(keyList); it.current(); ++it )
  {
    QString curKeyId = (*it)->primaryKeyID();

    QListViewItem * primaryUserID = new QListViewItem( mListView, curKeyId,
                                                       (*it)->primaryUserID() );

    // select and open the given key
    if( curKeyId == QString(keyId) )
    {
      mListView->setSelected( primaryUserID, true );
      primaryUserID->setOpen( true );
    }
    else
      primaryUserID->setOpen( false );

    // set icon for this key
    switch( keyValidity( *it ) )
    {
    case 0:
      primaryUserID->setPixmap( 0, *keyUnknown );
      break;
    case 1:
      primaryUserID->setPixmap( 0, *keyGood );
      break;
    case -1:
      primaryUserID->setPixmap( 0, *keyBad );
      break;
    }

    new QListViewItem( primaryUserID, "", keyInfo( *it ) );

    UserIDList userIDs = (*it)->userIDs();
    UserIDListIterator uidit(userIDs);
    if (uidit.current())
    {
      ++uidit; // skip the primary user ID
      for (; uidit.current(); ++uidit)
      {
        new QListViewItem( primaryUserID, "", (*uidit)->text() );
      }
    }
  }
}


QString KeySelectionDialog::keyInfo( const Kpgp::Key *key ) const
{
  QString status, remark;
  if( key->revoked() )
  {
    status = i18n("Revoked");
  }
  else if( key->expired() )
  {
    status = i18n("Expired");
  }
  else if( key->disabled() )
  {
    status = i18n("Disabled");
  }
  else if( key->invalid() )
  {
    status = i18n("Invalid");
  }
  else
  {
    Validity keyTrust = key->keyTrust();
    switch( keyTrust )
    {
    case KPGP_VALIDITY_UNDEFINED:
      status = i18n("Undefined trust");
      break;
    case KPGP_VALIDITY_NEVER:
      status = i18n("Untrusted");
      break;
    case KPGP_VALIDITY_MARGINAL:
      status = i18n("Marginally trusted");
      break;
    case KPGP_VALIDITY_FULL:
      status = i18n("Fully trusted");
      break;
    case KPGP_VALIDITY_ULTIMATE:
      status = i18n("Ultimately trusted");
      break;
    case KPGP_VALIDITY_UNKNOWN:
    default:
      status = i18n("Unknown");
    }
    if( key->secret() )
      remark = i18n("Secret key available");
    else if( !key->canEncrypt() )
      remark = i18n("Sign only key");
    else if( !key->canSign() )
      remark = i18n("Encryption only key");
  }

  QDateTime dt;
  dt.setTime_t( key->creationDate() );
  if( remark.isEmpty() )
    return " " + i18n("creation date and status of an OpenPGP key",
                      "Creation date: %1, Status: %2")
                     .arg( KGlobal::locale()->formatDate( dt.date(), true ) )
                     .arg( status );
  else
    return " " + i18n("creation date, status and remark of an OpenPGP key",
                      "Creation date: %1, Status: %2 (%3)")
                     .arg( KGlobal::locale()->formatDate( dt.date(), true ) )
                     .arg( status )
                     .arg( remark );
}

int KeySelectionDialog::keyValidity( const Kpgp::Key *key ) const
{
  int val = 0;

  switch( mAcceptedKeys )
  {
  case 0: // show all keys
    if( key->revoked() || key->expired() || key->disabled() || key->invalid() )
      val = -1;
    break;
  case 1: // only show encryption keys
    if( !key->isValidEncryptionKey() )
      val = -1;
    break;
  case 2: // only show signing keys
    if( !key->isValidSigningKey() )
      val = -1;
    break;
  }

  if( !val )
  {
    Validity keyTrust = key->keyTrust();
    switch( keyTrust )
    {
    case KPGP_VALIDITY_UNDEFINED:
    case KPGP_VALIDITY_NEVER:
      val = -1;
      break;
    case KPGP_VALIDITY_MARGINAL:
    case KPGP_VALIDITY_FULL:
    case KPGP_VALIDITY_ULTIMATE:
      val = 1;
      break;
    case KPGP_VALIDITY_UNKNOWN:
    default:
      val = 0;
    }
  }

  return val;
}


void KeySelectionDialog::updateSelectedKey( const Kpgp::Key *key ) const
{
  QListViewItem *lvi = mListView->selectedItem();

  if ( lvi != 0 )
  {
    if ( lvi->parent() != 0 )
      lvi = lvi->parent();
    
    // update the icon for this key
    switch( keyValidity( key ) )
    {
    case 0:
      lvi->setPixmap( 0, *keyUnknown );
      break;
    case 1:
      lvi->setPixmap( 0, *keyGood );
      break;
    case -1:
      lvi->setPixmap( 0, *keyBad );
      break;
    }

    // update the key info for this key
    // the key info is identified by a leading space; this shouldn't be
    // a problem because User Ids shouldn't start with a space
    for( lvi = lvi->firstChild(); lvi; lvi = lvi->nextSibling() )
      if( lvi->text( 1 ).at(0) == ' ' )
      {
        lvi->setText( 1, keyInfo( key ) );
        break;
      }
  }
}


bool
KeySelectionDialog::acceptKey( const QCString& keyId ) const
{
  if( mAcceptedKeys == 0 )
    return true;

  Kpgp::Module *pgp = Kpgp::Module::getKpgp();

  if( pgp == 0 )
    return false;

  Kpgp::Key* key = pgp->publicKey( keyId );

  if( key == 0 )
    return false;
  
  int val = keyValidity( key );
  if( val == 0 )
  {
    key = pgp->rereadKey( keyId, true );
    updateSelectedKey( key );
    val = keyValidity( key );
  }

  return ( val == 1 );
}


void KeySelectionDialog::slotSelectionChanged( QListViewItem * lvi )
{
  if ( lvi != 0 )
  {
    // get the key id of the chosen key
    if ( lvi->parent() != 0 )
      mKeyID = lvi->parent()->text(0).local8Bit();
    else
      mKeyID = lvi->text(0).local8Bit();
    
    enableButtonOK( acceptKey( mKeyID ) );
  }
  else
  {
    mKeyID = QCString();
    enableButtonOK( false );
  }
}

void KeySelectionDialog::slotOk()
{
  accept();
}


void KeySelectionDialog::slotCancel()
{
  mKeyID = QCString();
  reject();
}

// ------------------------------------------------------------------------
KeyApprovalDialog::KeyApprovalDialog( const QStringList& addresses,
                                      const QStrList& keyIDs,
                                      QWidget *parent, const char *name,
                                      bool modal )
  :KDialogBase( parent, name, modal, i18n("Encryption Key Approval"),
                Ok|Cancel, Ok ),
   mPrefsChanged( false )
{
  Kpgp::Module *pgp = Kpgp::Module::getKpgp();

  if( pgp == 0 )
    return;

  // ##### error handling
  // if( addresses.isEmpty() || keyList.isEmpty() || 
  //     addresses.count() != keyList.count() )
  //   do something;

  QFrame *page = makeMainWidget();
  QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
  
  QLabel *label = new QLabel( i18n("The following keys will be used for "
                                   "encryption:"),
                              page );
  topLayout->addWidget( label );

  QScrollView* sv = new QScrollView( page );
  sv->setResizePolicy( QScrollView::AutoOneFit );
  topLayout->addWidget( sv );
  QVBox* bigvbox = new QVBox( sv->viewport() );
  bigvbox->setMargin( 5 );
  bigvbox->setSpacing( 3 );
  sv->addChild( bigvbox );

  QButtonGroup *mChangeButtonGroup = new QButtonGroup( bigvbox );
  mChangeButtonGroup->hide();
  QStringList::ConstIterator ait;
  QStrListIterator kit( keyIDs );
  for( ait = addresses.begin(), kit.toFirst();
       (ait != addresses.end()) && (*kit);
       ++ait, ++kit
       )
  {
    QCString keyId = *kit;

    if( !kit.atFirst() )
      new KSeparator( Horizontal, bigvbox );

    QHBox *hbox = new QHBox( bigvbox );
    new QLabel( i18n("Recipient:"), hbox );
    QLabel *addressL = new QLabel( *ait, hbox );
    hbox->setStretchFactor( addressL, 10 );
    mAddressLabels.append( addressL );

    hbox = new QHBox( bigvbox );
    new QLabel( i18n("Encryption key:"), hbox );
    QLabel *keyidL;
    if( keyId.isEmpty() )
      keyidL = new QLabel( i18n("<none>"), hbox );
    else
      keyidL = new QLabel( keyId, hbox );
    keyidL->setFrameStyle( QFrame::Panel | QFrame::Sunken );
    QPushButton *button = new QPushButton( i18n("Change..."), hbox );
    mChangeButtonGroup->insert( button );
    button->setAutoDefault( false );
    hbox->setStretchFactor( keyidL, 10 );
    mKeyIdLabels.append( keyidL );
    
    hbox = new QHBox( bigvbox );
    new QLabel( i18n("Encryption preference:"), hbox );
    QComboBox *encrPrefCombo = new QComboBox( hbox );
    encrPrefCombo->insertItem( i18n("<none>") );
    encrPrefCombo->insertItem( i18n("Never encrypt with this key") );
    encrPrefCombo->insertItem( i18n("Always encrypt with this key") );
    encrPrefCombo->insertItem( i18n("Encrypt whenever encryption is possible") );
    encrPrefCombo->insertItem( i18n("Always ask") );
    encrPrefCombo->insertItem( i18n("Ask whenever encryption is possible") );

    EncryptPref encrPref = pgp->readEncryptionPreference( *ait );
    switch( encrPref )
    {
      case NeverEncrypt:
        encrPrefCombo->setCurrentItem( 1 );
        break;
      case AlwaysEncrypt:
        encrPrefCombo->setCurrentItem( 2 );
        break;
      case AlwaysEncryptIfPossible:
        encrPrefCombo->setCurrentItem( 3 );
        break;
      case AlwaysAskForEncryption:
        encrPrefCombo->setCurrentItem( 4 );
        break;
      case AskWheneverPossible:
        encrPrefCombo->setCurrentItem( 5 );
        break;
      default:
        encrPrefCombo->setCurrentItem( 0 );
    }
    connect( encrPrefCombo, SIGNAL(activated(int)),
             this, SLOT(slotPrefsChanged(int)) );
    mEncrPrefCombos.append( encrPrefCombo );
  }
  connect( mChangeButtonGroup, SIGNAL(clicked(int)),
           this, SLOT(slotChangeEncryptionKey(int)) );
}

void KeyApprovalDialog::show()
{
  // Reimplemented to get rid of the scrollbars if they are not needed.
  bool firstShow = !isVisible();

  KDialogBase::show();

  // Ingo, 2001-11-23:
  // For some reason the dialog is just a few pixels too small to show the
  // QScrollView without scrollbars. Therefore I simply grow the dialog a
  // few pixels to get rid of the scrollbars (if they are not needed).
  // This is a nasty hack because the dialog gets resized when it's already
  // shown. But before KDialogBase::show() the width() and height() is
  // set to some default values. So resizing before showing is not possible.
  // This hack won't work very well if the dialog is too high or too wide to
  // fit on the screen.
  if( firstShow )
  {
    resize( width()+4, height()+4 );
  }
}

void
KeyApprovalDialog::slotChangeEncryptionKey( int nr )
{
  Kpgp::Module *pgp = Kpgp::Module::getKpgp();

  kdDebug(5100)<<"Key approval dialog size is "
               <<width()<<"x"<<height()<<endl;

  if( pgp == 0 )
    return;

  QCString keyId = mKeyIdLabels.at(nr)->text().local8Bit();
  keyId = pgp->selectPublicKey( i18n("Encryption Key Selection"),
                                i18n("Select the key which should "
                                     "be used to encrypt the message "
                                     "for\n%1.")
                                .arg( mAddressLabels.at(nr)->text() ),
                                keyId,
                                mAddressLabels.at(nr)->text(),
                                true, false );
  if( !keyId.isEmpty() )
    mKeyIdLabels.at(nr)->setText( keyId );
}


void
KeyApprovalDialog::slotOk()
{
  Kpgp::Module *pgp = Kpgp::Module::getKpgp();

  if( pgp == 0 )
  {
    accept();
    return;
  }

  QPtrListIterator<QLabel> ait( mAddressLabels );
  QPtrListIterator<QLabel> kit( mKeyIdLabels );
  QPtrListIterator<QComboBox> prefit( mEncrPrefCombos );
  for( ; (*ait); ++ait, ++kit, ++prefit )
  { // traverse all address, Key Id and Encryption Preference widgets
    QString keyId = (*kit)->text();
    if( keyId != i18n("<none>") )
    {
      mKeys.append( keyId.local8Bit() );
      if( mPrefsChanged )
      { // store the changed preferences
        EncryptPref encrPref;
        switch( (*prefit)->currentItem() )
        {
          case 1:
            encrPref = NeverEncrypt;
            break;
          case 2:
            encrPref = AlwaysEncrypt;
            break;
          case 3:
            encrPref = AlwaysEncryptIfPossible;
            break;
          case 4:
            encrPref = AlwaysAskForEncryption;
            break;
          case 5:
            encrPref = AskWheneverPossible;
            break;
          default:
          case 0:
            encrPref = UnknownEncryptPref;
        }
        pgp->writeEncryptionPreference( (*ait)->text(), encrPref );
      }
    }
  }

  accept();
}


void
KeyApprovalDialog::slotCancel()
{
  reject();
}



// ------------------------------------------------------------------------
CipherTextDialog::CipherTextDialog( const QCString & text,
                                    const QCString & charset, QWidget *parent,
                                    const char *name, bool modal )
  :KDialogBase( parent, name, modal, i18n("OpenPGP Information"), Ok|Cancel, Ok)
{
  // FIXME (post KDE2.2): show some more info, e.g. the output of GnuPG/PGP
  QFrame *page = makeMainWidget();
  QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );

  QLabel *label = new QLabel( page );
  label->setText(i18n("Result of the last encryption / sign operation:"));
  topLayout->addWidget( label );

  mEditBox = new QMultiLineEdit( page );
  mEditBox->setReadOnly(true);
  topLayout->addWidget( mEditBox, 10 );

  QString unicodeText;
  if (charset.isEmpty())
    unicodeText = QString::fromLocal8Bit(text.data());
  else {
    bool ok=true;
    QTextCodec *codec = KGlobal::charsets()->codecForName(charset, ok);
    if(!ok)
      unicodeText = QString::fromLocal8Bit(text.data());
    else
      unicodeText = codec->toUnicode(text.data(), text.length());
  }

  mEditBox->setText(unicodeText);

  setMinimumSize();
}

void CipherTextDialog::setMinimumSize()
{
  // this seems to force a layout of the entire document, so we get a
  // a proper contentsWidth(). Is there a better way?
  for ( int i = 0; i < mEditBox->paragraphs(); i++ )
      (void) mEditBox->paragraphRect( i );

  mEditBox->setMinimumHeight( mEditBox->fontMetrics().lineSpacing() * 25 );

  int textWidth = mEditBox->contentsWidth() + 30;
  int scnum = QApplication::desktop()->screenNumber(parentWidget());
  int maxWidth = QApplication::desktop()->screenGeometry(scnum).width() - 100;
  
  mEditBox->setMinimumWidth( QMIN( textWidth, maxWidth ) );
}

}; // namespace Kpgp

#include "kpgpui.moc"
