/*
    This file is part of Akonadi Contact.

    Copyright (c) 2009 Tobias Koenig <tokoe@kde.org>

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

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

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to the
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301, USA.
*/

#include "addresseditwidget.h"

#include "autoqpointer_p.h"

#include <QtCore/QEvent>
#include <QtCore/QList>
#include <QtGui/QApplication>
#include <QtGui/QBoxLayout>
#include <QtGui/QButtonGroup>
#include <QtGui/QCheckBox>
#include <QtGui/QFrame>
#include <QtGui/QGridLayout>
#include <QtGui/QGroupBox>
#include <QtGui/QKeyEvent>
#include <QtGui/QLabel>
#include <QtGui/QPushButton>

#include <kacceleratormanager.h>
#include <kcombobox.h>
#include <kdebug.h>
#include <khbox.h>
#include <kinputdialog.h>
#include <klineedit.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kseparator.h>
#include <ktextedit.h>

#include <functional>

struct LocaleAwareLessThan : std::binary_function<QString,QString,bool> {
  bool operator()( const QString &s1, const QString &s2 ) const
  {
    return QString::localeAwareCompare( s1, s2 ) < 0 ;
  }
};

class TabPressEater : public QObject
{
  public:
    TabPressEater( QObject *parent )
      : QObject( parent )
    {
      setObjectName( QLatin1String( "TabPressEater" ) );
    }

  protected:
    bool eventFilter( QObject*, QEvent *event )
    {
      if ( event->type() == QEvent::KeyPress ) {
        QKeyEvent *keyEvent = (QKeyEvent*)event;
        if ( keyEvent->key() == Qt::Key_Tab ) {
          QApplication::sendEvent( parent(), event );
          return true;
        } else
          return false;
      } else {
        return false;
      }
    }
};

/**
 * Dialog for creating a new address types.
 *
 * @note This dialog is only used by AddressTypeCombo.
 */
class AddressTypeDialog : public KDialog
{
  public:
    AddressTypeDialog( KABC::Address::Type type, QWidget *parent );
    ~AddressTypeDialog();

    KABC::Address::Type type() const;

  private:
    QButtonGroup *mGroup;

    KABC::Address::TypeList mTypeList;
};


AddressSelectionWidget::AddressSelectionWidget( QWidget *parent )
  : KComboBox( parent )
{
  connect( this, SIGNAL(activated(int)), SLOT(selected(int)) );
}

AddressSelectionWidget::~AddressSelectionWidget()
{
}

void AddressSelectionWidget::setAddresses( const KABC::Address::List &addresses )
{
  mAddresses = addresses;
  updateView();
}

void AddressSelectionWidget::setCurrentAddress( const KABC::Address &address )
{
  const int index = mAddresses.indexOf( address );
  if ( index != -1 )
    setCurrentIndex( index );
}

KABC::Address AddressSelectionWidget::currentAddress() const
{
  if ( currentIndex() != -1 && currentIndex() < mAddresses.count() )
    return mAddresses.at( currentIndex() );
  else
    return KABC::Address();
}

void AddressSelectionWidget::selected( int index )
{
  Q_ASSERT( index != -1 && index < mAddresses.count() );
  emit selectionChanged( mAddresses.at( index ) );
}

void AddressSelectionWidget::updateView()
{
  clear();
  for ( int i = 0; i < mAddresses.count(); ++i )
    addItem( KABC::Address::typeLabel( mAddresses.at( i ).type() ) );
}



AddressTypeCombo::AddressTypeCombo( QWidget *parent )
  : KComboBox( parent ),
    mType( KABC::Address::Home ),
    mLastSelected( 0 )
{
  for ( int i = 0; i < KABC::Address::typeList().count(); ++i )
    mTypeList.append( KABC::Address::typeList().at( i ) );
  mTypeList.append( -1 ); // Others...

  update();

  connect( this, SIGNAL(activated(int)),
           this, SLOT(selected(int)) );
}

AddressTypeCombo::~AddressTypeCombo()
{
}

void AddressTypeCombo::setType( KABC::Address::Type type )
{
  if ( !mTypeList.contains( (int)type ) ) {
    // insert at the end, but before the 'Others...' entry
    mTypeList.insert( mTypeList.at( mTypeList.count() - 1 ), (int)type );
  }

  mType = type;
  update();
}

KABC::Address::Type AddressTypeCombo::type() const
{
  return mType;
}

void AddressTypeCombo::update()
{
  bool blocked = signalsBlocked();
  blockSignals( true );

  clear();
  for ( int i = 0; i < mTypeList.count(); ++i ) {
    if ( mTypeList.at( i ) == -1 ) // "Other..." entry
      addItem( i18nc( "@item:inlistbox Category of contact info field", "Other..." ) );
    else
      addItem( KABC::Address::typeLabel( KABC::Address::Type( mTypeList.at( i ) ) ) );
  }

  setCurrentIndex( mLastSelected = mTypeList.indexOf( mType ) );

  blockSignals( blocked );
}

void AddressTypeCombo::selected( int pos )
{
  if ( mTypeList.at( pos ) == -1 )
    otherSelected();
  else {
    mType = KABC::Address::Type( mTypeList.at( pos ) );
    mLastSelected = pos;
  }
}

void AddressTypeCombo::otherSelected()
{
  AutoQPointer<AddressTypeDialog> dlg = new AddressTypeDialog( mType, this );
  if ( dlg->exec() ) {
    mType = dlg->type();
    if ( !mTypeList.contains( mType ) )
      mTypeList.insert( mTypeList.at( mTypeList.count() - 1 ), mType );
  } else {
    setType( KABC::Address::Type( mTypeList.at( mLastSelected ) ) );
  }

  update();
}


AddressEditWidget::AddressEditWidget( QWidget *parent )
  : QWidget( parent ), mReadOnly( false )
{
  QGridLayout *layout = new QGridLayout( this );
  layout->setSpacing( KDialog::spacingHint() );
  layout->setMargin( 0 );

  mAddressSelectionWidget = new AddressSelectionWidget( this );
  connect( mAddressSelectionWidget, SIGNAL(selectionChanged(KABC::Address)),
           SLOT(updateAddressView()) );
  layout->addWidget( mAddressSelectionWidget, 0, 0, 1, 3 );

  mAddressView = new QLabel( this );
  mAddressView->setFrameStyle( QFrame::Panel | QFrame::Sunken );
  mAddressView->setMinimumHeight( 20 );
  mAddressView->setAlignment( Qt::AlignTop );
  mAddressView->setTextFormat( Qt::PlainText );
  mAddressView->setTextInteractionFlags( Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse );
  layout->addWidget( mAddressView, 1, 0, 1, 3 );

  mCreateButton = new QPushButton( i18nc( "street/postal", "New..." ), this );
  connect( mCreateButton, SIGNAL(clicked()), this, SLOT(createAddress()) );
  mEditButton = new QPushButton( i18nc( "street/postal", "Edit..." ), this );
  connect( mEditButton, SIGNAL(clicked()), this, SLOT(editAddress()) );
  mDeleteButton = new QPushButton( i18nc( "street/postal", "Delete" ), this );
  connect( mDeleteButton, SIGNAL(clicked()), this, SLOT(deleteAddress()) );

  layout->addWidget( mCreateButton, 2, 0 );
  layout->addWidget( mEditButton, 2, 1 );
  layout->addWidget( mDeleteButton, 2, 2 );

  updateButtons();
}

AddressEditWidget::~AddressEditWidget()
{
}

void AddressEditWidget::setReadOnly( bool readOnly )
{
  mReadOnly = readOnly;
  updateButtons();
}

void AddressEditWidget::updateName( const QString &name )
{
  mName = name;
  updateAddressView();
}

void AddressEditWidget::createAddress()
{
  AutoQPointer<AddressEditDialog> dialog = new AddressEditDialog( this );
  if ( dialog->exec() ) {
    const KABC::Address address = dialog->address();
    fixPreferredAddress( address );
    mAddressList.append( address );
    mAddressSelectionWidget->setAddresses( mAddressList );
    mAddressSelectionWidget->setCurrentAddress( address );

    updateAddressView();
    updateButtons();
  }
}

void AddressEditWidget::editAddress()
{
  AutoQPointer<AddressEditDialog> dialog = new AddressEditDialog( this );
  dialog->setAddress( mAddressSelectionWidget->currentAddress() );
  if ( dialog->exec() ) {
    const KABC::Address address = dialog->address();
    fixPreferredAddress( address );
    mAddressList[ mAddressSelectionWidget->currentIndex() ] = address;
    mAddressSelectionWidget->setAddresses( mAddressList );
    mAddressSelectionWidget->setCurrentAddress( address );

    updateAddressView();
  }
}

void AddressEditWidget::deleteAddress()
{
  const int result = KMessageBox::questionYesNo( this, i18n( "Do you really want to delete this address?" ) );

  if ( result != KMessageBox::Yes )
    return;

  mAddressList.removeAt( mAddressSelectionWidget->currentIndex() );
  mAddressSelectionWidget->setAddresses( mAddressList );
  updateAddressView();
  updateButtons();
}

void AddressEditWidget::fixPreferredAddress( const KABC::Address &preferredAddress )
{
  // as the preferred address is mutual exclusive, we have to
  // remove the flag from all other addresses
  if ( preferredAddress.type() & KABC::Address::Pref ) {
    for ( int i = 0; i < mAddressList.count(); ++i ) {
      KABC::Address &address = mAddressList[ i ];
      address.setType( address.type() & ~KABC::Address::Pref );
    }
  }
}

void AddressEditWidget::updateAddressView()
{
  const KABC::Address address = mAddressSelectionWidget->currentAddress();

  if ( address.isEmpty() )
    mAddressView->setText( QString() );
  else
    mAddressView->setText( address.formattedAddress( mName ) );
}

void AddressEditWidget::updateButtons()
{
  mCreateButton->setEnabled( !mReadOnly );
  mEditButton->setEnabled( !mReadOnly && (mAddressList.count() > 0) );
  mDeleteButton->setEnabled( !mReadOnly && (mAddressList.count() > 0) );
}

void AddressEditWidget::loadContact( const KABC::Addressee &contact )
{
  mName = contact.realName();
  mAddressList = contact.addresses();

  mAddressSelectionWidget->setAddresses( mAddressList );

  // set the preferred address as the visible one
  for ( int i = 0; i < mAddressList.count(); ++i ) {
    if ( mAddressList.at( i ).type() & KABC::Address::Pref ) {
      mAddressSelectionWidget->setCurrentAddress( mAddressList.at( i ) );
      break;
    }
  }

  updateAddressView();
  updateButtons();
}

void AddressEditWidget::storeContact( KABC::Addressee &contact ) const
{
  // delete all previous addresses
  const KABC::Address::List oldAddresses = contact.addresses();
  for ( int i = 0; i < oldAddresses.count(); ++i )
    contact.removeAddress( oldAddresses.at( i ) );

  // insert the new ones
  for ( int i = 0; i < mAddressList.count(); ++i ) {
    const KABC::Address address( mAddressList.at( i ) );
    if ( !address.isEmpty() )
      contact.insertAddress( address );
  }
}


AddressEditDialog::AddressEditDialog( QWidget *parent )
  : KDialog(parent)
{
  setCaption( i18nc( "street/postal", "Edit Address" ) );
  setButtons( Ok | Cancel );
  setDefaultButton( Ok );
  showButtonSeparator( true );

  QWidget *page = new QWidget( this );
  setMainWidget( page );

  QGridLayout *topLayout = new QGridLayout( page );
  topLayout->setSpacing( spacingHint() );
  topLayout->setMargin( 0 );

  mTypeCombo = new AddressTypeCombo( page );
  topLayout->addWidget( mTypeCombo, 0, 0, 1, 2 );

  QLabel *label = new QLabel( i18nc( "<streetLabel>:", "%1:", KABC::Address::streetLabel() ), page );
  label->setAlignment( Qt::AlignTop | Qt::AlignLeft );
  topLayout->addWidget( label, 1, 0 );
  mStreetTextEdit = new KTextEdit( page );
  mStreetTextEdit->setAcceptRichText( false );
  label->setBuddy( mStreetTextEdit );
  topLayout->addWidget( mStreetTextEdit, 1, 1 );

  TabPressEater *eater = new TabPressEater( this );
  mStreetTextEdit->installEventFilter( eater );

  label = new QLabel( i18nc( "<postOfficeBoxLabel>:", "%1:", KABC::Address::postOfficeBoxLabel() ), page );
  topLayout->addWidget( label, 2 , 0 );
  mPOBoxEdit = new KLineEdit( page );
  label->setBuddy( mPOBoxEdit );
  topLayout->addWidget( mPOBoxEdit, 2, 1 );

  label = new QLabel( i18nc( "<localityLabel>:", "%1:", KABC::Address::localityLabel() ), page );
  topLayout->addWidget( label, 3, 0 );
  mLocalityEdit = new KLineEdit( page );
  label->setBuddy( mLocalityEdit );
  topLayout->addWidget( mLocalityEdit, 3, 1 );

  label = new QLabel( i18nc( "<regionLabel>:", "%1:", KABC::Address::regionLabel() ), page );
  topLayout->addWidget( label, 4, 0 );
  mRegionEdit = new KLineEdit( page );
  label->setBuddy( mRegionEdit );
  topLayout->addWidget( mRegionEdit, 4, 1 );

  label = new QLabel( i18nc( "<postalCodeLabel>:", "%1:", KABC::Address::postalCodeLabel() ), page );
  topLayout->addWidget( label, 5, 0 );
  mPostalCodeEdit = new KLineEdit( page );
  label->setBuddy( mPostalCodeEdit );
  topLayout->addWidget( mPostalCodeEdit, 5, 1 );

  label = new QLabel( i18nc( "<countryLabel>:", "%1:", KABC::Address::countryLabel() ), page );
  topLayout->addWidget( label, 6, 0 );
  mCountryCombo = new KComboBox( page );
  mCountryCombo->setEditable( true );
  mCountryCombo->setDuplicatesEnabled( false );

  QPushButton *labelButton = new QPushButton( i18n( "Edit Label..." ), page );
  topLayout->addWidget( labelButton, 7, 0, 1, 2 );
  connect( labelButton, SIGNAL(clicked()), SLOT(editLabel()) );

  fillCountryCombo();
  label->setBuddy( mCountryCombo );
  topLayout->addWidget( mCountryCombo, 6, 1 );

  mPreferredCheckBox = new QCheckBox( i18nc( "street/postal", "This is the preferred address" ), page );
  topLayout->addWidget( mPreferredCheckBox, 8, 0, 1, 2 );

  KSeparator *sep = new KSeparator( Qt::Horizontal, page );
  topLayout->addWidget( sep, 9, 0, 1, 2 );

  KHBox *buttonBox = new KHBox( page );
  buttonBox->setSpacing( spacingHint() );
  topLayout->addWidget( buttonBox, 10, 0, 1, 2 );

  KAcceleratorManager::manage( this );
}

AddressEditDialog::~AddressEditDialog()
{
}

void AddressEditDialog::editLabel()
{
  bool ok = false;
  QString result = KInputDialog::getMultiLineText( KABC::Address::labelLabel(),
                                                   KABC::Address::labelLabel(),
                                                   mLabel, &ok, this );
  if ( ok )
    mLabel = result;
}

void AddressEditDialog::setAddress( const KABC::Address &address )
{
  mAddress = address;

  mTypeCombo->setType( mAddress.type() );
  mStreetTextEdit->setPlainText( mAddress.street() );
  mRegionEdit->setText( mAddress.region() );
  mLocalityEdit->setText( mAddress.locality() );
  mPostalCodeEdit->setText( mAddress.postalCode() );
  mPOBoxEdit->setText( mAddress.postOfficeBox() );
  mLabel = mAddress.label();
  mPreferredCheckBox->setChecked( mAddress.type() & KABC::Address::Pref );

  if ( mAddress.isEmpty() )
    mCountryCombo->setItemText( mCountryCombo->currentIndex(),
                                KGlobal::locale()->countryCodeToName( KGlobal::locale()->country() ) );
  else
    mCountryCombo->setItemText( mCountryCombo->currentIndex(), mAddress.country() );

  mStreetTextEdit->setFocus();
}

KABC::Address AddressEditDialog::address() const
{
  KABC::Address address( mAddress );

  address.setType( mTypeCombo->type() );
  address.setLocality( mLocalityEdit->text() );
  address.setRegion( mRegionEdit->text() );
  address.setPostalCode( mPostalCodeEdit->text() );
  address.setCountry( mCountryCombo->currentText() );
  address.setPostOfficeBox( mPOBoxEdit->text() );
  address.setStreet( mStreetTextEdit->toPlainText() );
  address.setLabel( mLabel );

  if ( mPreferredCheckBox->isChecked() ) {
    address.setType( address.type() | KABC::Address::Pref );
  } else
    address.setType( address.type() & ~(KABC::Address::Pref) );

  return address;
}

void AddressEditDialog::fillCountryCombo()
{
  QStringList countries;

  foreach ( const QString &cc, KGlobal::locale()->allCountriesList() )
    countries.append( KGlobal::locale()->countryCodeToName( cc ) );

  qSort( countries.begin(), countries.end(), LocaleAwareLessThan() );

  mCountryCombo->addItems( countries );
  mCountryCombo->setAutoCompletion( true );
  mCountryCombo->completionObject()->setItems( countries );
  mCountryCombo->completionObject()->setIgnoreCase( true );

  const QString currentCountry = KGlobal::locale()->countryCodeToName( KGlobal::locale()->country() );
  mCountryCombo->setCurrentIndex( mCountryCombo->findText( currentCountry ) );
}


AddressTypeDialog::AddressTypeDialog( KABC::Address::Type type, QWidget *parent )
  : KDialog( parent)
{
  setCaption( i18nc( "street/postal", "Edit Address Type" ) );
  setButtons( Ok | Cancel );
  setDefaultButton( Ok );

  QWidget *page = new QWidget(this);
  setMainWidget( page );
  QVBoxLayout *layout = new QVBoxLayout( page );
  layout->setSpacing( KDialog::spacingHint() );
  layout->setMargin( 0 );

  QGroupBox *box  = new QGroupBox( i18nc( "street/postal", "Address Types" ), page );
  layout->addWidget( box );
  mGroup = new QButtonGroup( box );
  mGroup->setExclusive ( false );

  QGridLayout *buttonLayout = new QGridLayout( box );

  mTypeList = KABC::Address::typeList();
  mTypeList.removeAll( KABC::Address::Pref );

  KABC::Address::TypeList::ConstIterator it;
  int i = 0;
  int row = 0;
  for ( it = mTypeList.constBegin(); it != mTypeList.constEnd(); ++it, ++i ) {
    QCheckBox *cb = new QCheckBox( KABC::Address::typeLabel( *it ), box );
    cb->setChecked( type & mTypeList[ i ] );
    buttonLayout->addWidget( cb, row, i%3 );

    if( i%3 == 2 )
        ++row;
    mGroup->addButton( cb );
  }
}

AddressTypeDialog::~AddressTypeDialog()
{
}

KABC::Address::Type AddressTypeDialog::type() const
{
  KABC::Address::Type type;
  for ( int i = 0; i < mGroup->buttons().count(); ++i ) {
    QCheckBox *box = dynamic_cast<QCheckBox*>( mGroup->buttons().at( i ) );
    if ( box && box->isChecked() )
      type |= mTypeList[ i ];
  }

  return type;
}

#include "addresseditwidget.moc"
