/*
  This file is part of libkldap.
  Copyright (c) 2004-2006 Szombathelyi György <gyurco@freemail.hu>

  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 "ldapsearch.h"
#include "ldapdn.h"
#include "ldapdefs.h"

#include <QtCore/QEventLoop>
#include <QtCore/QTimer>

#include <kdebug.h>
#include <KLocale>
using namespace KLDAP;

//blocking the GUI for xxx milliseconds
#define LDAPSEARCH_BLOCKING_TIMEOUT 10

class LdapSearch::Private
{
  public:
    Private( LdapSearch *parent )
      : mParent( parent )
    {
    }

    void result();
    bool connect();
    void closeConnection();
    bool startSearch( const LdapDN &base, LdapUrl::Scope scope,
                      const QString &filter, const QStringList &attributes,
                      int pagesize, int count );

    LdapSearch *mParent;
    LdapConnection *mConn;
    LdapOperation mOp;
    bool mOwnConnection, mAbandoned;
    int mId, mPageSize;
    LdapDN mBase;
    QString mFilter;
    QStringList mAttributes;
    LdapUrl::Scope mScope;

    QString mErrorString;
    int mError;
    int mCount, mMaxCount;
    bool mFinished;
};

void LdapSearch::Private::result()
{
  if ( mAbandoned ) {
    mOp.abandon( mId );
    return;
  }
  int res = mOp.waitForResult( mId, LDAPSEARCH_BLOCKING_TIMEOUT );

  kDebug() << "LDAP result:" << res;

  if ( res != 0 &&
       ( res == -1 ||
         ( mConn->ldapErrorCode() != KLDAP_SUCCESS &&
           mConn->ldapErrorCode() != KLDAP_SASL_BIND_IN_PROGRESS ) ) ) {
    //error happened, but no timeout
    mError = mConn->ldapErrorCode();
    mErrorString = mConn->ldapErrorString();
    emit mParent->result( mParent );
    return;
  }

  //binding
  if ( res == LdapOperation::RES_BIND ) {

    QByteArray servercc;
    servercc = mOp.serverCred();

    kDebug() << "LdapSearch RES_BIND";
    if ( mConn->ldapErrorCode() == KLDAP_SUCCESS ) { //bind succeeded
      kDebug() << "bind succeeded";
      LdapControls savedctrls = mOp.serverControls();
      if ( mPageSize ) {
        LdapControls ctrls = savedctrls;
        LdapControl::insert( ctrls, LdapControl::createPageControl( mPageSize ) );
        mOp.setServerControls( ctrls );
      }

      mId = mOp.search( mBase, mScope, mFilter, mAttributes );
      mOp.setServerControls( savedctrls );
    } else { //next bind step
      kDebug() << "bind next step";
      mId = mOp.bind( servercc );
    }
    if ( mId < 0 ) {
      if ( mId == KLDAP_SASL_ERROR ) {
        mError = mId;
        mErrorString = mConn->saslErrorString();
      } else {
        mError = mConn->ldapErrorCode();
        mErrorString = mConn->ldapErrorString();
      }
      emit mParent->result( mParent );
      return;
    }
    QTimer::singleShot( 0, mParent, SLOT(result()) );
    return;
  }

  //End of entries
  if ( res == LdapOperation::RES_SEARCH_RESULT ) {
    if ( mPageSize ) {
      QByteArray cookie;
      int estsize = -1;
      for ( int i = 0; i < mOp.controls().count(); ++i ) {
        estsize = mOp.controls()[i].parsePageControl( cookie );
        if ( estsize != -1 ) {
          break;
        }
      }
      kDebug() << " estimated size:" << estsize;
      if ( estsize != -1 && !cookie.isEmpty() ) {
        LdapControls ctrls, savedctrls;
        savedctrls = mOp.serverControls();
        ctrls = savedctrls;
        LdapControl::insert( ctrls, LdapControl::createPageControl ( mPageSize, cookie ) );
        mOp.setServerControls( ctrls );
        mId = mOp.search( mBase, mScope, mFilter, mAttributes );
        mOp.setServerControls( savedctrls );
        if ( mId == -1 ) {
          mError = mConn->ldapErrorCode();
          mErrorString = mConn->ldapErrorString();
          emit mParent->result( mParent );
          return;
        }
        //continue with the next page
        QTimer::singleShot( 0, mParent, SLOT(result()) );
        return;
      }
    }
    mFinished = true;
    emit mParent->result( mParent );
    return;
  }

  //Found an entry
  if ( res == LdapOperation::RES_SEARCH_ENTRY ) {
    emit mParent->data( mParent, mOp.object() );
    mCount++;
  }

  //If not reached the requested entries, continue
  if ( mMaxCount <= 0 || mCount < mMaxCount ) {
    QTimer::singleShot( 0, mParent, SLOT(result()) );
  }
  //If reached the requested entries, indicate it
  if ( mMaxCount > 0 && mCount == mMaxCount ) {
    kDebug() << mCount << " entries reached";
    emit mParent->result( mParent );
  }
}

bool LdapSearch::Private::connect()
{
  int ret = mConn->connect();
  if ( ret != KLDAP_SUCCESS ) {
    mError = ret;
    mErrorString = mConn->connectionError();
    closeConnection();
    return false;
  }
  return true;
}

void LdapSearch::Private::closeConnection()
{
  if ( mOwnConnection && mConn ) {
    delete mConn;
    mConn = 0;
  }
}

//This starts the real job
bool LdapSearch::Private::startSearch( const LdapDN &base, LdapUrl::Scope scope,
                                       const QString &filter,
                                       const QStringList &attributes, int pagesize, int count )
{
  kDebug() << "search: base=" << base.toString() << "scope=" << (int)scope
           << "filter=" << filter << "attributes=" << attributes
           << "pagesize=" << pagesize;
  mAbandoned = false;
  mError = 0;
  mErrorString.clear();
  mOp.setConnection( *mConn );
  mPageSize = pagesize;
  mBase = base;
  mScope = scope;
  mFilter = filter;
  mAttributes = attributes;
  mMaxCount = count;
  mCount = 0;
  mFinished = false;

  LdapControls savedctrls = mOp.serverControls();
  if ( pagesize ) {
    LdapControls ctrls = savedctrls;
    mConn->setOption( 0x0008, NULL ); // Disable referals or paging won't work
    LdapControl::insert( ctrls, LdapControl::createPageControl( pagesize ) );
    mOp.setServerControls( ctrls );
  }

  mId = mOp.bind();
  if ( mId < 0 ) {
    if ( mId == KLDAP_SASL_ERROR ) {
      mError = mId;
      mErrorString = mConn->saslErrorString();
    } else {
      mError = mConn->ldapErrorCode();
      mErrorString = mConn->ldapErrorString();
      if ( mError == -1 && mErrorString.isEmpty() ) {
        mErrorString = i18n( "Cannot access to server. Please reconfigure it." );
      }
    }
    return false;
  }
  kDebug() << "startSearch msg id=" << mId;

   //maybe do this with threads?- need thread-safe client libs!!!
  QTimer::singleShot( 0, mParent, SLOT(result()) );

  return true;
}

///////////////////////////////////////////////

LdapSearch::LdapSearch()
 : d( new Private( this ) )
{
  d->mOwnConnection = true;
  d->mConn = 0;
}

LdapSearch::LdapSearch( LdapConnection &connection )
 : d( new Private( this ) )
{
  d->mOwnConnection = false;
  d->mConn = &connection;
}

LdapSearch::~LdapSearch()
{
  d->closeConnection();
  delete d;
}

void LdapSearch::setConnection( LdapConnection &connection )
{
  d->closeConnection();
  d->mOwnConnection = false;
  d->mConn = &connection;
}

void LdapSearch::setClientControls( const LdapControls &ctrls )
{
  d->mOp.setClientControls( ctrls );
}

void LdapSearch::setServerControls( const LdapControls &ctrls )
{
  d->mOp.setServerControls( ctrls );
}

bool LdapSearch::search( const LdapServer &server,
                         const QStringList &attributes, int count )
{
  if ( d->mOwnConnection ) {
    d->closeConnection();
    d->mConn = new LdapConnection( server );
    if ( !d->connect() ) {
      return false;
    }
  }
  return d->startSearch( server.baseDn(), server.scope(), server.filter(),
                         attributes, server.pageSize(), count );
}

bool LdapSearch::search( const LdapUrl &url, int count )
{
  if ( d->mOwnConnection ) {
    d->closeConnection();
    d->mConn = new LdapConnection( url );
    if ( !d->connect() ) {
      return false;
    }
  }
  bool critical;
  int pagesize = url.extension( QLatin1String( "x-pagesize" ), critical ).toInt();
  return d->startSearch( url.dn(), url.scope(), url.filter(),
                         url.attributes(), pagesize, count );
}

bool LdapSearch::search( const LdapDN &base, LdapUrl::Scope scope,
                         const QString &filter, const QStringList &attributes,
                         int pagesize, int count )
{
  Q_ASSERT( !d->mOwnConnection );
  return d->startSearch( base, scope, filter, attributes, pagesize, count );
}

void LdapSearch::continueSearch()
{
  Q_ASSERT( !d->mFinished );
  d->mCount = 0;
  QTimer::singleShot( 0, this, SLOT(result()) );
}

bool LdapSearch::isFinished()
{
  return d->mFinished;
}

void LdapSearch::abandon()
{
  d->mAbandoned = true;
}

int LdapSearch::error() const
{
  return d->mError;
}

QString LdapSearch::errorString() const
{
  return d->mErrorString;
}

#include "ldapsearch.moc"
