// $Id: kaddressbookview.cpp,v 1.11.2.3 2002/02/14 23:00:26 mboyer Exp $

#include <stdlib.h> // for atoi

#include <qtabwidget.h>
#include <qlayout.h>
#include <qvbox.h>
#include <qgroupbox.h>
#include <qbuttongroup.h>
#include <qlistbox.h>
#include <qwidget.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qlineedit.h>
#include <qfile.h>
#include <qtextstream.h>
#include <qpixmap.h>
#include <qimage.h>
#include <qcombobox.h>
#include <qtooltip.h>
#include <qdialog.h>
#include <qheader.h>
#include <qapplication.h>
#include <qclipboard.h>
#include <qcheckbox.h>
#include <qradiobutton.h>
#include <qdragobject.h>
#include <qevent.h>
#include <qurl.h>

#include <kurl.h>
#include <kdebug.h>
#include <klocale.h>
#include <kconfig.h>
#include <kapplication.h>
#include <kcolorbutton.h>
#include <kglobal.h>
#include <kmessagebox.h>
#include <kiconloader.h>
#include <kurlrequester.h>
#include <klineedit.h>

#include "kaddressbookview.h"
#include "entryeditorwidget.h"
#include "viewoptions.h"
#include "selectfields.h"
#include "attributes.h"
#include "contactentry.h"
#include "contactentrylist.h"
#include "browserentryeditor.h"
#include "undocmds.h"
#include "contactlistview.h"
#include "ldapsearchdialogimpl.h"
#include "ldapoptionswidgetimpl.h"
#include "KDListBoxPair.h"

void KAddressBookView::selectNames( QStringList newFields )
{
  field.clear();
  fieldWidth.clear();
  if (newFields.count() == 0) {
    newFields += "X-FileAs";
    newFields += "EMAIL";
    newFields += "X-BusinessPhone";
    newFields += "X-HomePhone";
  }

  QStringList::Iterator it;
  for(it = newFields.begin(); it != newFields.end(); ++it)
  {
    field += *it;
    if (*it == "X-FileAs")
      fieldWidth += 180;
    else if (*it == "EMAIL")
      fieldWidth += 160;
    else
      fieldWidth += 120;
  }
}

ContactEntryList *KAddressBookView::contactEntryList()
{
  return cel;
}

ContactListView *KAddressBookView::listView()
{
  return mListView;
}

QStringList *KAddressBookView::fields()
{
  return &field;
}

void KAddressBookView::showSelectNameDialog()
{
  SelectFields* sel = new SelectFields( field, this, "select", TRUE );
  if (!sel->exec())
    return;
  // Can't undo this action maybe should show a warning.
  //1  UndoStack::instance()->clear();
  //1  RedoStack::instance()->clear();

  selectNames( sel->chosenFields() );
  reconstructListView();
  delete sel;
}

void KAddressBookView::slotOpenLDAPDialog()
{
  if( !ldapSearchDialog ) ldapSearchDialog = new LDAPSearchDialogImpl(this);
  ldapSearchDialog->exec();
}

void KAddressBookView::defaultSettings()
{
  selectNames( QStringList() );
  mListView->backPixmapOn = false;
  mListView->underline = true;
  mListView->autUnderline = true;
  mListView->tooltips_ = true;
  reconstructListView();
}

void KAddressBookView::reconstructListView()
{
  cbField->clear();
  for ( uint i = 0; i < field.count(); i++ )
    cbField->insertItem( Attributes::instance()->fieldToName( field[i]));

  QObject::disconnect( iSearch, SIGNAL( textChanged( const QString& )),
		       mListView, SLOT( incSearch( const QString& )));
  QObject::disconnect( cbField, SIGNAL( activated( int )),
		       mListView, SLOT( setSorting( int )));
  delete mListView;

  setupListView();
  mListView->setSorting( 0, true );

  QObject::connect( iSearch, SIGNAL( textChanged( const QString& )),
		    mListView, SLOT( incSearch( const QString& )));
  QObject::connect( cbField, SIGNAL( activated( int )),
		    mListView, SLOT( setSorting( int )));
  mainLayout->addWidget( mListView );
  mainLayout->activate();
  mListView->show();
}

KAddressBookView::KAddressBookView( ContactEntryList *cel,
		      QWidget *parent,
		      const char *name )
  : QWidget( parent, name ), cel( cel )
{
  readConfig();
  mainLayout = new QVBoxLayout( this, 2 );

  QBoxLayout *searchLayout = new QHBoxLayout( mainLayout, 2 );

  QLabel *liSearch = new QLabel( i18n( "&Incremental Search:" ), this );
  liSearch->resize( liSearch->sizeHint() );
  searchLayout->addWidget( liSearch, 0 );

  iSearch = new QLineEdit( this );
  iSearch->resize( iSearch->sizeHint() );
  searchLayout->addWidget( iSearch, 0 );
  liSearch->setBuddy( iSearch );

  // Create a non-editable Combobox and a label below...
  cbField = new QComboBox( FALSE, this );
  cbField->resize( iSearch->sizeHint() );
  searchLayout->addWidget( cbField, 0 );

  for ( uint i = 0; i < field.count(); i++ )
    cbField->insertItem( Attributes::instance()->fieldToName( field[i]));
  setupListView();
  mListView->resort();

  mainLayout->addWidget( mListView );
  mainLayout->activate();

  QObject::connect( iSearch, SIGNAL( textChanged( const QString& )),
		    mListView, SLOT( incSearch( const QString& )));
  QObject::connect( cbField, SIGNAL( activated( int )),
		    mListView, SLOT( setSorting( int )));
  QObject::connect( mListView, SIGNAL( returnPressed( QListViewItem *)),
		    this, SLOT( properties()));

  ldapSearchDialog = 0;
}

KAddressBookView::~KAddressBookView()
{
    kdDebug()<< "Destroying KAddressBookView\n";
}

void KAddressBookView::setupListView()
{
  mListView = new ContactListView( this );
  QObject::connect( mListView, SIGNAL( selectionChanged() ),
		    this, SLOT( selectionChanged() ) );
  QObject::connect( mListView, SIGNAL( doubleClicked( QListViewItem* ) ),
		    this, SLOT( itemSelected( QListViewItem* ) ) );
  repopulate();
}

void KAddressBookView::repopulate()
{
  mListView->clear();
  for ( uint i = 0; i < field.count(); i++ )
    mListView->addColumn( Attributes::instance()->fieldToName( field[i] ),
			 fieldWidth[i] );

  //xxx
  QStringList keys = cel->keys();
  for ( QStringList::Iterator it = keys.begin(); it != keys.end(); ++it )
    addEntry( *it );

  /*
  QDictIterator<ContactEntry> it(*cel);
  while (it.current()) {
    addEntry( it.currentKey() );
    ++it;
  }
  */
}

ContactListViewItem* KAddressBookView::addEntry( QString entryKey )
{
  ContactListViewItem *item = new ContactListViewItem( entryKey, mListView, &field );
  item->refresh();
  return item;
}

// Will have to insert into cel and save key
void KAddressBookView::addNewEntry( ContactEntry *ce )
{
  PwNewCommand *command = new PwNewCommand( this, ce );
  UndoStack::instance()->push( command );
  RedoStack::instance()->clear();
}

bool KAddressBookView::hasSelection() const
{
  bool result = false;
  QListViewItem *item = mListView->firstChild();
  for ( ; item; item = item->itemBelow() )
    if ( item->isSelected() )
    {
      result = true;
      break;
    }
  return result;
}

void KAddressBookView::selectionChanged()
{
  emit selectionAvailable( hasSelection() );
}

void KAddressBookView::selectAll()
{
  QListViewItem *item;
  for(item = mListView->firstChild(); item; item = item->itemBelow())
    mListView->setSelected( item, true );
}

void KAddressBookView::properties()
{
  itemSelected( mListView->currentItem() );
}

QString KAddressBookView::selectedEmails()
{
  bool first = true;
  QString emailAddrs;
  QListViewItem *item;
  for(item = mListView->firstChild(); item; item = item->itemBelow()) {
    if (!mListView->isSelected( item ))
      continue;
    ContactListViewItem *plvi = dynamic_cast< ContactListViewItem* >(item);
    if (!plvi)
      continue;
    QString entryKey = plvi->entryKey();
    ContactEntry *ce = plvi->getEntry();
    if (!ce)
      continue;
    if (!ce->find( "EMAIL" ))
      continue;
    QString email = *ce->find( "EMAIL" );
    if (email.isEmpty())
      continue;
    email.stripWhiteSpace();

    QString sFileAs;
    if (ce->find( "N" )) {
      sFileAs = *ce->find( "N" );
      sFileAs.stripWhiteSpace();
      sFileAs += " ";
    }

    if (!first)
      emailAddrs += ", ";
    else
      first = false;

    emailAddrs += sFileAs + "<" + email + ">";
  }
  return emailAddrs;
}


void KAddressBookView::updateContact( QString addr, QString name )
{
  ContactEntryList *cel = contactEntryList();
  QStringList keys = cel->keys();
  for ( QStringList::Iterator it = keys.begin(); it != keys.end(); ++it ) {
    ContactEntry *ce = cel->find( *it );
    if (ce)
      if (ce->find("EMAIL")  && ((*ce->find("EMAIL")).stripWhiteSpace() == addr)) {
	if (!name.isEmpty())
	  ce->replace( "N", new QString( name ) );
	QString title = i18n( "Address Book Entry Editor" );
	PabContactDialog *cd = new PabContactDialog( title, this, 0, *it, ce );
	QObject::connect( cd, SIGNAL( change( QString, ContactEntry* ) ),
			  this, SLOT( change( QString, ContactEntry* ) ));
	cd->show();
	return;
      }
  }

  ContactDialog *cd = new PabNewContactDialog( i18n( "Address Book Entry Editor" ), this, 0);
  ContactEntry *ce = cd->entry();
  if (!name.isEmpty())
    ce->replace( ".AUXCONTACT-N", new QString(name) );
  ce->replace( "EMAIL", new QString( addr ) );
  connect( cd, SIGNAL( add( ContactEntry* ) ),
	   this, SLOT( addNewEntry( ContactEntry* ) ));
  cd->parseName();
  cd->show();
}

void KAddressBookView::addEmail(const QString& aStr)
{
  int i, j, len;
  QString partA, partB, result;
  char endCh = '>';

  i = aStr.find('<');
  if (i<0)
  {
    i = aStr.find('(');
    endCh = ')';
  }
  if (i<0) {
    updateContact( aStr, "" );
    return;
  }
  partA = aStr.left(i).stripWhiteSpace();
  j = aStr.find(endCh,i+1);
  if (j<0) {
    updateContact( aStr, "" );
    return;
  }
  partB = aStr.mid(i+1, j-i-1).stripWhiteSpace();

  if (partA.find('@') >= 0 && !partB.isEmpty()) result = partB;
  else if (!partA.isEmpty()) result = partA;
  else result = aStr;

  len = result.length();
  if (result[0]=='"' && result[len-1]=='"')
    result = result.mid(1, result.length()-2);
  else if (result[0]=='<' && result[len-1]=='>')
    result = result.mid(1, result.length()-2);
  else if (result[0]=='(' && result[len-1]==')')
    result = result.mid(1, result.length()-2);

  updateContact( partB, result );
}


void KAddressBookView::updateByUid(QString key)
{
 kdDebug() << "KAddressBookView::editEntry. Text of key: " << key << endl;
 QString title = i18n( "Address Book Entry Editor" );
 QString entryKey = key;
 ContactEntry *ce = cel->find( key );
 if (!ce) {
  kdDebug()<< "KAddressBookView::itemSelected Associated entry not found\n" ;
  KMessageBox::sorry(this,i18n("Sorry, this contact was not found in your addressbook"),i18n("Not found"),false);
  return;
 }
 PabContactDialog *cd = new PabContactDialog( title, 0, 0, entryKey, ce );
 QObject::connect( cd, SIGNAL( change( QString, ContactEntry* ) ),
 this, SLOT( change( QString, ContactEntry* ) ));
 cd->show();
}


void KAddressBookView::sendMail()
{
  QString emailAddrs = selectedEmails();
  kapp->invokeMailer( emailAddrs, "" );
}

void KAddressBookView::itemSelected( QListViewItem *item )
{
  if ( item == 0 )
    return;

  ContactListViewItem *plvi = dynamic_cast< ContactListViewItem* >(item);

  if (plvi) {
    QString title = i18n( "Address Book Entry Editor" );
    QString entryKey = plvi->entryKey();
    ContactEntry *ce = cel->find( entryKey );
    if (!ce) { // Another process deleted it(!)
        kdDebug()<< "KAddressBookView::itemSelected Associated entry not found\n" ;
      return;
    }
    PabContactDialog *cd = new PabContactDialog( title, this, 0, entryKey, ce );
    QObject::connect( cd, SIGNAL( change( QString, ContactEntry* ) ),
		      this, SLOT( change( QString, ContactEntry* ) ));
    cd->show();
  }

  item->setSelected( TRUE );

  item->repaint();
}

void KAddressBookView::change( QString entryKey, ContactEntry *ce )
{
  ContactListViewItem *plvi = mListView->getItem( entryKey );
  ContactEntry *oldce = cel->find( entryKey );
  if (plvi && oldce) {
    PwEditCommand *command = new PwEditCommand( this, entryKey, oldce, ce );
    UndoStack::instance()->push( command );
    RedoStack::instance()->clear();
  }
  else {
    PwNewCommand *command = new PwNewCommand( this, ce );
    UndoStack::instance()->push( command );
    RedoStack::instance()->clear();
  }
}

void KAddressBookView::cut()
{
  PwCutCommand *command = new PwCutCommand( this );
  UndoStack::instance()->push( command );
  RedoStack::instance()->clear();
}

void KAddressBookView::copy()
{
  QListViewItem *item;
  QString clipText;
  QTextOStream clipStream( &clipText );
  for(item = mListView->firstChild(); item; item = item->itemBelow()) {
    if (!mListView->isSelected( item ))
      continue;
    ContactListViewItem *lvi = dynamic_cast< ContactListViewItem* >(item);
    if (lvi) {
      ContactEntry *ce = lvi->getEntry();
      if (ce)
	ce->save( clipStream );
    }
  }
  QClipboard *cb = QApplication::clipboard();
  cb->setText( clipText );
}

void KAddressBookView::paste()
{
  QClipboard *cb = QApplication::clipboard();
  PwPasteCommand *command = new PwPasteCommand( this, cb->text() );
  UndoStack::instance()->push( command );
  RedoStack::instance()->clear();
}

void KAddressBookView::clear()
{
    kdDebug()<< "clear\n";
  QListViewItem *item = mListView->currentItem();
  ContactListViewItem *lvi = dynamic_cast< ContactListViewItem* >(item);
  if (lvi) {
    QString entryKey = lvi->entryKey();
    ContactEntry *ce = lvi->getEntry();
    PwDeleteCommand *command = new PwDeleteCommand( this, entryKey, ce );
    UndoStack::instance()->push( command );
    RedoStack::instance()->clear();
  }
}

void KAddressBookView::saveConfig()
{
  KConfig *config = kapp->config();

  mListView->saveConfig();

  config->setGroup("Browser");

  QStringList actualFields;
  QStringList actualWidths;
  for(uint i = 0; i < field.count(); ++i) {
    int act = mListView->header()->mapToLogical( i );
    actualFields += field[act];
    int size = mListView->header()->cellSize( act );
    actualWidths += QString().setNum( size );
  }

  config->writeEntry("fields", actualFields );
  config->writeEntry("fieldWidths", actualWidths );
}

void KAddressBookView::readConfig()
{
  KConfig *config = kapp->config();

  config->setGroup("Browser");
  field = config->readListEntry("fields" );
  selectNames( field );
  QStringList fieldWidthStr = config->readListEntry("fieldWidths" );
  fieldWidth.clear();
  QStringList::Iterator it;
  for(it = fieldWidthStr.begin(); it != fieldWidthStr.end(); ++it)
    fieldWidth += atoi( (*it).ascii() );
  while (fieldWidth.count() < field.count())
    fieldWidth += 120;
}

void KAddressBookView::viewOptions()
{
  QTabDialog* dlg = new QTabDialog( this );  
  ViewOptions *vo = new ViewOptions( mListView->backPixmapOn,
				     mListView->backPixmap,
				     mListView->underline,
				     mListView->autUnderline,
				     mListView->cUnderline,
				     mListView->tooltips(),
				     0,
				     "ViewOptions" );
  LDAPOptionsWidgetImpl* ldw = new LDAPOptionsWidgetImpl();

  // Read LDAP information from KConfig object and write to config dialog.
  KConfig *config = kapp->config();
  config->setGroup("LDAP");

  // Populate the leftListBox and the ServersMap
  uint numHosts = config->readUnsignedNumEntry("NumHosts");
  if ( numHosts > 0 ) {
     QString host; 
      for ( int count = 0; count < numHosts; count++ ){
      // Set the key for search in the ServersMap
      host = config->readEntry( QString( "Host%1" ).arg( count ), "").stripWhiteSpace();
      // Populate the leftListBox 
      ldw->listBoxPair->leftListBox()->insertItem( host );

       // Create a host account ( in the ServersMap )
      ldw->server.setbase( config->readEntry( QString( "Base%1" ).arg( count ), "" ).stripWhiteSpace());
      ldw->server.setport( config->readUnsignedNumEntry( QString( "Port%1" ).arg( count ))); 
      // Register the host and its data in the map
      ldw->_ldapservers[host] = ldw->server;
      }
  }

    // Michel: Populate the rightListBox and the ServersMap

  uint numSelectedHosts =  config->readUnsignedNumEntry( "NumSelectedHosts");
  if (numSelectedHosts > 0 ) {
    QString selectedHost; 
    for ( int cnt = 0; cnt <  numSelectedHosts; cnt++ ){
      //Set the key for search in the ServersMap
      selectedHost = config->readEntry( QString( "SelectedHost%1").arg(cnt),"").stripWhiteSpace();
      //Populate the rightListBox 
      ldw->listBoxPair->rightListBox()->insertItem( selectedHost);

      //Create a host account ( in the ServersMap )
      ldw->server.setbase( config->readEntry( QString( "SelectedBase%1" ).arg( cnt ), "" ).stripWhiteSpace());
      ldw->server.setport( config->readUnsignedNumEntry( QString( "SelectedPort%1" ).arg( cnt ))); 
      //Register the host and its data in the map
      ldw->_ldapservers[selectedHost] = ldw->server;
    }
  }

  dlg->addTab( vo, i18n( "View Options" ) );
  dlg->addTab( ldw, i18n( "LDAP Options" ) );
  
  dlg->setOkButton( i18n( "&OK" ) );
  dlg->setCancelButton( i18n( "Cancel" ) );

  // Show config dialog
  if (!dlg->exec())
    return;

  // Read information from config dialog and write to KConfig object
  mListView->backPixmapOn = vo->ckBackPixmap->isChecked();
  mListView->backPixmap = vo->pixmapPath->lineEdit()->text();
  mListView->underline = vo->ckUnderline->isChecked();
  mListView->autUnderline = vo->ckAutUnderline->isChecked();
  mListView->cUnderline = vo->kcbUnderline->color();
  mListView->tooltips_ = vo->ckTooltips->isChecked();
  mListView->loadBackground();
  mListView->saveConfig();
  mListView->triggerUpdate();

  /*
   * Clean config. All the data registered is 
   * deleted.
   */
    config->setGroup("LDAP");
    config->deleteGroup("LDAP", true, false );

    /*
   * Save the hosts listed in the leftListBox 
   * and its associated data registered in the ServersMap
   * to config. This data will then be used to populate 
   * the leftListBox and the ServersMap
   */ 

    config->setGroup("LDAP");
  QString availableHosts;
  int numAvailHosts = ldw->listBoxPair->leftListBox()->count();
  if ( numAvailHosts > 0 ) { 
    ServersMap::Iterator it;
    for( int j = 0; j <= numAvailHosts ;j++ ) {
      availableHosts = ldw->listBoxPair->leftListBox()->text(j ).stripWhiteSpace();
      for ( it = ldw->_ldapservers.begin(); it != ldw->_ldapservers.end(); it++ ) {
	if ( availableHosts == it.key() ) {
	  config->writeEntry( QString( "Host%1" ).arg( j ), it.key());
	  if( it.data().port()) {
	    config->writeEntry( QString( "Port%1" ).arg(j ), it.data().port());
	  } else {
	    config->deleteEntry( QString( "Port%1" ).arg(j ), false, true );
	  }
	  config->writeEntry( QString( "Base%1" ).arg( j ), it.data().baseDN());
	}
      }
      config->writeEntry("NumHosts", ldw->listBoxPair->leftListBox()->count() );
    }
  } 

  /*
   * Save and register the hosts selected (rightListBox)
   * and their associated  data to config. The hosts will be then 
   * used to populate the rightListBox. 
   */
  QString selectedHosts;
  uint numSelHosts = ldw->listBoxPair->rightListBox()->count();
  if ( numSelHosts > 0 ) {
    ServersMap::Iterator it;
    for(int itr = 0; itr <= numSelHosts; itr++){
      selectedHosts = ldw->listBoxPair->rightListBox()->text( itr ).stripWhiteSpace(); 
      for ( it = ldw->_ldapservers.begin(); it != ldw->_ldapservers.end(); it++ ){
	if ( selectedHosts == it.key() ){
	   config->writeEntry( QString( "SelectedHost%1" ).arg( itr ), selectedHosts ); 
           if( it.data().port()) {
	    config->writeEntry( QString( "SelectedPort%1" ).arg( itr ), it.data().port());
	  } else {
	    config->deleteEntry( QString( "SelectedPort%1" ).arg( itr ), false, true );
	  }
	  config->writeEntry( QString( "SelectedBase%1" ).arg( itr ), it.data().baseDN());
	}
      }
      config->writeEntry("NumSelectedHosts", numSelHosts); 
    }
  }

  if( ldapSearchDialog ) ldapSearchDialog->rereadConfig();
  delete vo;
}


#include "kaddressbookview.moc"
