/*
    This file is part of libkabc.
    Copyright (c) 2002 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., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/

#include <kdebug.h>
#include <kglobal.h>
#include <klineedit.h>
#include <klocale.h>
#include <kconfig.h>
#include <kstringhandler.h>

#include <stdlib.h>

#include "resourceldap.h"
#include "resourceldapconfig.h"

using namespace KABC;

void addModOp( LDAPMod ***pmods, const QString &attr, const QString &value );

ResourceLDAP::ResourceLDAP( const KConfig *config )
  : Resource( config ), mLdap( 0 )
{
  if ( config ) {
    QMap<QString, QString> attrList;
    QStringList attributes = config->readListEntry( "LdapAttributes" );
    for ( uint pos = 0; pos < attributes.count(); pos += 2 )
      attrList.insert( attributes[ pos ], attributes[ pos + 1 ] );

    init( config->readEntry( "LdapUser" ),
          KStringHandler::obscure( config->readEntry( "LdapPassword" ) ),
          config->readEntry( "LdapDn" ),
          config->readEntry( "LdapHost" ),
          config->readNumEntry( "LdapPort", 389 ),
          config->readEntry( "LdapFilter" ),
          config->readBoolEntry( "LdapAnonymous" ),
          attrList );
  } else {
    init( "", "", "", "", 389, "", true, QMap<QString, QString>() );
  }
}

ResourceLDAP::ResourceLDAP( const QString &user, const QString &passwd,
                            const QString &dn, const QString &host,
                            int port, const QString &filter, bool anonymous,
                            const QMap<QString, QString> &attributes )
  : Resource( 0 ), mLdap( 0 )
{
  init( user, passwd, dn, host, port, filter, anonymous, attributes );
}

void ResourceLDAP::init( const QString &user, const QString &passwd,
                         const QString &dn, const QString &host,
                         int port, const QString &filter, bool anonymous,
                         const QMap<QString, QString> &attributes )
{
  mUser = user;
  mPassword = passwd;
  mDn = dn;
  mHost = host;
  mPort = port;
  mFilter = filter;
  mAnonymous = anonymous;

  /**
    If you want to add new attributes, append them here, add a
    translation string in the ctor of AttributesDialog and
    handle them in the load() method below.
    These are the default values
   */
  if ( attributes.count() == 0 ) {
    mAttributes.insert( "commonName", "cn" );
    mAttributes.insert( "formattedName", "displayName" );
    mAttributes.insert( "familyName", "sn" );
    mAttributes.insert( "givenName", "givenName" );
    mAttributes.insert( "mail", "mail" );
    mAttributes.insert( "mailAlias", "" );
    mAttributes.insert( "phoneNumber", "telephoneNumber" );
    mAttributes.insert( "uid", "uid" );
  } else {
    mAttributes = attributes;
  }
}

void ResourceLDAP::writeConfig( KConfig *config )
{
  Resource::writeConfig( config );

  config->writeEntry( "LdapUser", mUser );
  config->writeEntry( "LdapPassword", KStringHandler::obscure( mPassword ) );
  config->writeEntry( "LdapDn", mDn );
  config->writeEntry( "LdapHost", mHost );
  config->writeEntry( "LdapPort", mPort );
  config->writeEntry( "LdapFilter", mFilter );
  config->writeEntry( "LdapAnonymous", mAnonymous );

  QStringList attributes;
  QMap<QString, QString>::Iterator it;
  for ( it = mAttributes.begin(); it != mAttributes.end(); ++it )
    attributes << it.key() << it.data();

  config->writeEntry( "LdapAttributes", attributes );
}

Ticket *ResourceLDAP::requestSaveTicket()
{
  if ( !addressBook() ) {
	  kdDebug(5700) << "no addressbook" << endl;
    return 0;
  }

  return createTicket( this );
}

void ResourceLDAP::releaseSaveTicket( Ticket *ticket )
{
  delete ticket;
}

bool ResourceLDAP::doOpen()
{
  if ( mLdap )
	  return false;

  if ( !mPort )
	  mPort = 389;

  mLdap = ldap_init( mHost.local8Bit(), mPort );
  if ( !mLdap ) {
	  addressBook()->error( i18n( "Unable to connect to server '%1' on port '%2'" ).arg( mHost ).arg( mPort ) );
	  return false;
  }

  if ( !mUser.isEmpty() && !mAnonymous ) {
	  if ( ldap_simple_bind_s( mLdap, mUser.local8Bit(), mPassword.local8Bit() ) != LDAP_SUCCESS ) {
	    addressBook()->error( i18n( "Unable to bind to server '%1'" ).arg( mHost ) );
      return false;
    }

    kdDebug(5700) << "ResourceLDAP: bind to server successfully" << endl;
  } else {
    if ( ldap_simple_bind_s( mLdap, NULL, NULL ) != LDAP_SUCCESS ) {
      addressBook()->error( i18n( "Unable to bind anonymously to server '%1'" ).arg( mHost ) );
      return false;
    }

    kdDebug( 5700 ) << "ResourceLDAP: bind anonymously to server successfully" << endl;
  }

  int deref = LDAP_DEREF_ALWAYS;
  if ( ldap_set_option( mLdap, LDAP_OPT_DEREF, (void *) &deref ) != LDAP_OPT_SUCCESS ) {
    kdDebug(5700) << "ResourceLDAP: can't set 'deref' option" << endl;
    return false;
  }

  if ( ldap_set_option( mLdap, LDAP_OPT_REFERRALS, LDAP_OPT_ON ) != LDAP_OPT_SUCCESS ) {
    kdDebug(5700) << "ResourceLDAP: can't set 'referrals' option" << endl;
    return false;
  }

  return true;
}

void ResourceLDAP::doClose()
{
  if ( ldap_unbind_s( mLdap ) != LDAP_SUCCESS ) {
    kdDebug(5700) << "ResourceLDAP: can't unbind from server" << endl;
    return;
  }

  mLdap = 0;
}

bool ResourceLDAP::load()
{
  LDAPMessage *res;
  LDAPMessage *msg;
  BerElement *track;
  char *names;
  char **values;

  char **LdapSearchAttr = new char*[ mAttributes.count() + 1 ];

  QMap<QString, QString>::Iterator it;
  int i = 0;
  for ( it = mAttributes.begin(); it != mAttributes.end(); ++it ) {
    if ( !it.data().isEmpty() ) {
      unsigned int len = it.data().utf8().length();
      LdapSearchAttr[ i ] = new char[ len+1 ];
      memcpy( LdapSearchAttr[ i ], it.data().utf8(), len );
      LdapSearchAttr[ i ][ len ] = 0;
      ++i;
    }
  }
  LdapSearchAttr[ i ] = 0;

  QString filter = mFilter;
  if ( filter.isEmpty() )
    filter = "cn=*";

  int result;
  if ( ( result = ldap_search_s( mLdap, mDn.local8Bit(), LDAP_SCOPE_SUBTREE, QString( "(%1)" ).arg( filter ).local8Bit(),
         LdapSearchAttr, 0, &res ) != LDAP_SUCCESS ) ) {
    addressBook()->error( i18n( "Unable to search on server '%1': %2" )
                          .arg( mHost )
                          .arg( ldap_err2string( result ) ) );

    for ( i = 0; LdapSearchAttr[ i ]; ++i )
      delete [] LdapSearchAttr[ i ];
    delete [] LdapSearchAttr;

    return false;
  }

  for ( msg = ldap_first_entry( mLdap, res ); msg; msg = ldap_next_entry( mLdap, msg ) ) {
    Addressee addr;
    addr.setResource( this );
    for ( names = ldap_first_attribute( mLdap, msg, &track ); names; names = ldap_next_attribute( mLdap, msg, track ) ) {
      values = ldap_get_values( mLdap, msg, names );
      for ( int i = 0; i < ldap_count_values( values ); ++i ) {
        QString name = QString::fromUtf8( names ).lower();
        QString value = QString::fromUtf8( values[ i ] );

        if ( name == mAttributes[ "commonName" ].lower() ) {
          if ( !addr.formattedName().isEmpty() ) {
            QString fn = addr.formattedName();
            addr.setNameFromString( value );
            addr.setFormattedName( fn );
          } else
            addr.setNameFromString( value );
        } else if ( name == mAttributes[ "formattedName" ].lower() ) {
          addr.setFormattedName( value );
        } else if ( name == mAttributes[ "givenName" ].lower() ) {
          addr.setGivenName( value );
        } else if ( name == mAttributes[ "mail" ].lower() ) {
          addr.insertEmail( value, true );
        } else if ( name == mAttributes[ "mailAlias" ].lower() ) {
          addr.insertEmail( value, false );
        } else if ( name == mAttributes[ "phoneNumber" ].lower() ) {
          PhoneNumber phone;
          phone.setNumber( value );
          addr.insertPhoneNumber( phone );
          break; // read only the home number
        } else if ( name == mAttributes[ "familyName" ].lower() ) {
          addr.setFamilyName( value );
        } else if ( name == mAttributes[ "uid" ].lower() ) {
          addr.setUid( value );
        }
      }
      ldap_value_free( values );
    }
    ber_free( track, 0 );

    addressBook()->insertAddressee( addr );
  }

  ldap_msgfree( res );

  for ( i = 0; LdapSearchAttr[ i ]; ++i )
    delete [] LdapSearchAttr[ i ];
  delete [] LdapSearchAttr;

  return true;
}

bool ResourceLDAP::asyncLoad()
{
  bool ok = load();
  if ( !ok )
    emit loadingError( this, i18n( "Loading resource '%1' failed!" )
                       .arg( resourceName() ) );
  else
    emit loadingFinished( this );

  return ok;
}

bool ResourceLDAP::save( Ticket* )
{
  AddressBook::Iterator it;
  for ( it = addressBook()->begin(); it != addressBook()->end(); ++it ) {
    if ( (*it).resource() == this && (*it).changed() ) {
      LDAPMod **mods = NULL;

      addModOp( &mods, "objectClass", "organizationalPerson" );
      addModOp( &mods, "objectClass", "person" );
      addModOp( &mods, "objectClass", "Top" );
      addModOp( &mods, mAttributes[ "commonName" ].utf8(), (*it).assembledName() );
      addModOp( &mods, mAttributes[ "formattedName" ].utf8(), (*it).formattedName() );
      addModOp( &mods, mAttributes[ "givenName" ].utf8(), (*it).givenName() );
      addModOp( &mods, mAttributes[ "familyName" ].utf8(), (*it).familyName() );
      addModOp( &mods, mAttributes[ "uid" ].utf8(), (*it).uid() );

      QStringList emails = (*it).emails();
      QStringList::ConstIterator mailIt;
      bool first = true;
      for ( mailIt = emails.begin(); mailIt != emails.end(); ++mailIt ) {
        if ( first ) {
          addModOp( &mods, mAttributes[ "mail" ].utf8(), (*mailIt) );
          first = false;
        } else
          addModOp( &mods, mAttributes[ "mailAlias" ].utf8(), (*mailIt) );
      }

      PhoneNumber number = (*it).phoneNumber( PhoneNumber::Home );
      addModOp( &mods, mAttributes[ "phoneNumber" ].utf8(), number.number() );

      QString dn = "cn=" + (*it).assembledName() + "," + mDn;

      int retval;
      if ( (retval = ldap_add_s( mLdap, dn.local8Bit(), mods )) != LDAP_SUCCESS )
         addressBook()->error( i18n( "Unable to modify '%1' on server '%2'" ).arg( (*it).uid() ).arg( mHost ) );

      ldap_mods_free( mods, 1 );

      // mark as unchanged
      (*it).setChanged( false );
    }
  }

  return true;
}

bool ResourceLDAP::asyncSave( Ticket *ticket )
{
  bool ok = save( ticket );
  if ( !ok )
    emit savingError( this, i18n( "Saving resource '%1' failed!" )
                      .arg( resourceName() ) );
  else
    emit savingFinished( this );

  return ok;
}

void ResourceLDAP::removeAddressee( const Addressee &addr )
{
  LDAPMessage *res;
  LDAPMessage *msg;

  QString filter = QString( "(&(uid=%1)(%2))" ).arg( addr.uid() ).arg( mFilter );

  kdDebug(5700) << "ldap:removeAddressee" << filter << endl;

  ldap_search_s( mLdap, mDn.local8Bit(), LDAP_SCOPE_SUBTREE, filter.local8Bit(),
      0, 0, &res );

  bool ok = true;
  for ( msg = ldap_first_entry( mLdap, res ); msg; msg = ldap_next_entry( mLdap, msg ) ) {
    char *dn = ldap_get_dn( mLdap, msg );
    kdDebug(5700) << "found " << dn << endl;
    if ( ldap_delete_s( mLdap, dn ) != LDAP_SUCCESS ) {
      addressBook()->error( i18n( "Unable to delete '%1' on server '%2'" ).arg( dn ).arg( mHost ) );
      ok = false;
    }

    ldap_memfree( dn );
  }

  ldap_msgfree( res );

  if ( ok )
    mAddrMap.erase( addr.uid() );
}

void ResourceLDAP::setUser( const QString &user )
{
  mUser = user;
}

QString ResourceLDAP::user() const
{
  return mUser;
}

void ResourceLDAP::setPassword( const QString &password )
{
  mPassword = password;
}

QString ResourceLDAP::password() const
{
  return mPassword;
}

void ResourceLDAP::setDn( const QString &dn )
{
  mDn = dn;
}

QString ResourceLDAP::dn() const
{
  return mDn;
}

void ResourceLDAP::setHost( const QString &host )
{
  mHost = host;
}

QString ResourceLDAP::host() const
{
  return mHost;
}

void ResourceLDAP::setPort( int port )
{
  mPort = port;
}

int ResourceLDAP::port() const
{
  return mPort;
}

void ResourceLDAP::setFilter( const QString &filter )
{
  mFilter = filter;
}

QString ResourceLDAP::filter() const
{
  return mFilter;
}

void ResourceLDAP::setIsAnonymous( bool value )
{
  mAnonymous = value;
}

bool ResourceLDAP::isAnonymous() const
{
  return mAnonymous;
}

void ResourceLDAP::setAttributes( const QMap<QString, QString> &attributes )
{
  mAttributes = attributes;
}

QMap<QString, QString> ResourceLDAP::attributes() const
{
  return mAttributes;
}

void addModOp( LDAPMod ***pmods, const QString &attr, const QString &value )
{
  if ( value.isNull() )
    return;

  LDAPMod	**mods;

  mods = *pmods;

  uint i = 0;
  if ( mods != 0 )
    for ( ; mods[ i ] != 0; ++i );

  if (( mods = (LDAPMod **)realloc( mods, (i + 2) * sizeof( LDAPMod * ))) == 0 ) {
    kdError() << "ResourceLDAP: realloc" << endl;
    return;
  }

  *pmods = mods;
  mods[ i + 1 ] = 0;

  mods[ i ] = new LDAPMod;

  mods[ i ]->mod_op = 0;
  mods[ i ]->mod_type = strdup( attr.utf8() );
  mods[ i ]->mod_values = new char*[ 2 ];
  mods[ i ]->mod_values[ 0 ] = strdup( value.utf8() );
  mods[ i ]->mod_values[ 1 ] = 0;
}

