/*
 * Copyright 2000, OpenLDAP Foundation, All Rights Reserved.
 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
 */


#include "config.h"
#include "debug.h"
#include <ldap.h>
#include "LDAPMessageQueue.h"
#include "LDAPRequest.h"
#include "LDAPAsynConnection.h"
#include "LDAPMessage.h"
#include "LDAPResult.h"
#include "LDAPSearchReference.h"
#include "LDAPSearchRequest.h"
#include "LDAPUrl.h"
#include "LDAPUrlList.h"
#include "LDAPException.h"

using namespace std;

// TODO: How to handle unsolicited notifications, like notice of
//       disconnection

LDAPMessageQueue::LDAPMessageQueue(LDAPRequest *req){
    DEBUG(LDAP_DEBUG_CONSTRUCT, "LDAPMessageQueue::LDAPMessageQueue()" << endl);
	m_activeReq.push(req);
    m_issuedReq.push_back(req);
}

LDAPMessageQueue::~LDAPMessageQueue(){
    DEBUG(LDAP_DEBUG_DESTROY, "LDAPMessageQueue::~LDAPMessageQueue()" << endl);
    for(LDAPRequestList::iterator i=m_issuedReq.begin(); 
            i != m_issuedReq.end(); i++){
        delete *i;
    }
    m_issuedReq.clear();
}


LDAPMsg *LDAPMessageQueue::getNext(){
    DEBUG(LDAP_DEBUG_TRACE,"LDAPMessageQueue::getNext()" << endl);
    LDAPMessage *msg;
    LDAPRequest *req=m_activeReq.top();
    int msg_id = req->getMsgID();
    int res;
    const  LDAPAsynConnection *con=req->getConnection();
    res=ldap_result(con->getSessionHandle(),msg_id,0,0,&msg);
    if (res <= 0){
        if(msg != 0){
            ldap_msgfree(msg);
        }
	throw  LDAPException(con);
    }else{	
        const LDAPConstraints *constr=req->getConstraints();
        LDAPMsg *ret=0;
        //this can  throw an exception (Decoding Error)
        try{
            ret = LDAPMsg::create(req,msg);
            ldap_msgfree(msg);
        }catch(LDAPException e){
            //do some clean up
            delete req;
            m_activeReq.top();
            throw;   
        }
        switch (ret->getMessageType()) {
            case LDAPMsg::SEARCH_REFERENCE : 
                if (constr->getReferralChase() ){
                    //throws Exception (limit Exceeded)
                    LDAPRequest *refReq=chaseReferral(ret);
                    if(refReq != 0){
                        m_activeReq.push(refReq);
                        m_issuedReq.push_back(refReq);
                        delete ret;
                        return getNext();
                    }
                }
                return ret;
            break;
            case LDAPMsg::SEARCH_ENTRY :
                return ret;
            break;
            case LDAPMsg::SEARCH_DONE :
                if(req->isReferral()){
                    req->unbind();
                }
                switch ( ((LDAPResult*)ret)->getResultCode()) {
                    case LDAPResult::REFERRAL :
                        if(constr->getReferralChase()){
                            //throws Exception (limit Exceeded)
                            LDAPRequest *refReq=chaseReferral(ret);
                            if(refReq != 0){
                                m_activeReq.pop();
                                m_activeReq.push(refReq);
                                m_issuedReq.push_back(refReq);
                                delete ret;
                                return getNext();
                            }
                        }    
                        return ret;
                    break;
                    case LDAPResult::SUCCESS :
                        if(req->isReferral()){
                            delete ret;
                            m_activeReq.pop();
                            return getNext();
                        }else{
                            m_activeReq.pop();
                            return ret;
                        }
                    break;
                    default:
                        m_activeReq.pop();
                        return ret;
                    break;
                }
            break;
            //must be some kind of LDAPResultMessage
            default:
                if(req->isReferral()){
                    req->unbind();
                }
                LDAPResult* res_p=(LDAPResult*)ret;
                switch (res_p->getResultCode()) {
                    case LDAPResult::REFERRAL :
                        if(constr->getReferralChase()){
                            //throws Exception (limit Exceeded)
                            LDAPRequest *refReq=chaseReferral(ret);
                            if(refReq != 0){
                                m_activeReq.pop();
                                m_activeReq.push(refReq);
                                m_issuedReq.push_back(refReq);
                                delete ret;
                                return getNext();
                            }
                        }    
                        return ret;
                    break;
                    default:
                        m_activeReq.pop();
                        return ret;
                }
            break;
        }
    }	
}

// TODO Maybe moved to LDAPRequest::followReferral seems more reasonable
//there
LDAPRequest* LDAPMessageQueue::chaseReferral(LDAPMsg* ref){
    DEBUG(LDAP_DEBUG_TRACE,"LDAPMessageQueue::chaseReferra()" << endl);
    LDAPRequest *req=m_activeReq.top();
    LDAPRequest *refReq=req->followReferral(ref);
    if(refReq !=0){
        if(refReq->getConstraints()->getHopLimit() < refReq->getHopCount()){
            delete(refReq);
            throw LDAPException(LDAP_REFERRAL_LIMIT_EXCEEDED);
        }
        if(refReq->isCycle()){
            delete(refReq);
            throw LDAPException(LDAP_CLIENT_LOOP);
        }
        try {
            refReq->sendRequest();
            return refReq;
        }catch (LDAPException e){
            DEBUG(LDAP_DEBUG_TRACE,"   caught exception" << endl);
            return 0;
        }
    }else{ 
        return 0;
    }
}

LDAPRequestStack* LDAPMessageQueue::getRequestStack(){
    DEBUG(LDAP_DEBUG_TRACE,"LDAPMessageQueue::getRequestStack()" << endl);
    return &m_activeReq;
}

