package fr.univrennes1.cri.util.ldapUtils;

import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.novell.ldap.LDAPAttribute;
import com.novell.ldap.LDAPConnection;
import com.novell.ldap.LDAPEntry;
import com.novell.ldap.LDAPException;
import com.novell.ldap.LDAPReferralHandler;
import com.novell.ldap.LDAPSearchConstraints;
import com.novell.ldap.LDAPSearchResults;

import fr.univrennes1.cri.util.commonUtils.CommonUtils;
import fr.univrennes1.cri.util.exceptions.ConfigException;

/**
 * @author Thomas Bellembois
 * @version 1.0
 *
 * This class offers methods to access and modify LDAP entries.<br/>
 *  
 * Creation date : 22 nov. 2005
 * Last modification :
 */
public class LDAPUtils {

//	// for TESTS
	public static void main(String[] args) {
		 // (&(ur1TypeEntree=pers)(ur1sourcecreation=HARPUR1))
		 String ldapBaseDN = "ou=people,dc=univ-rennes1,dc=fr";
		 String ldapURL = "ldapglobal.univ-rennes1.fr";
		 String ldapPort = "389";
		 String ldapScope = LDAPUtils.SCOPE_SUBTREE_LEVEL;
		 String ldapPrincipal = "";
		 String ldapCredential = "";
		 String ldapMaxResults = "0";
		 String[] attributesToRetrieve = {"uid", "departmentnumber", "ur1prenom", "mailalternateaddress", "ur1mail"};
		 
		 LDAPUtils ldapUtils = new LDAPUtils();
		 try {
			boolean connected = ldapUtils.connect(ldapURL, ldapPort, ldapPrincipal, ldapCredential);
			System.out.println("main:connected="+connected);
					
			Vector LDAPResult = ldapUtils.getEntries("(&(ur1TypeEntree=pers)(ur1sourcecreation=HARPUR1))", ldapBaseDN, ldapMaxResults, attributesToRetrieve, ldapScope);
//			while (LDAPResult != null && LDAPResult.hasMoreElements()) {
//				_LDAPEntry ldapEntry = (_LDAPEntry) LDAPResult.nextElement();
//				ldapEntry.getLdapAttribute("ur1mail");
//				System.out.println(ldapEntry.getLdapAttribute("ur1mail").getValue());
//				//System.out.println(ldapEntry);
//			}// while (LDAPResult.hasMoreElements())
			
			boolean disconnected = ldapUtils.disconnect();
			System.out.println("main:disconnected="+disconnected);
		 } catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	// LDAP parameters
	private String ldapUrl;
	private String ldapPort;
	private String ldapBaseDn;
	private int ldapScope;
	private String ldapPrincipal;  // Authentication principal (if required)
	private String ldapCredential; // Authentication credential (if required)
	private String ldapMaxResults; // 0 = no limit
	private String[] attributesToRetrieve;
	private LDAPSearchConstraints ldapSearchConstraints; // LDAP request constraints
	private LDAPSearchResults ldapSearchResults; 		 // LDAP request result
	private LDAPConnection ldapConnection;
	
	// Constants
	public static final String SCOPE_SUBTREE_LEVEL = "SCOPE_SUBTREE_LEVEL";
	public static final String SCOPE_SUBTREE_LEVEL_ALT = "SUBTREE_SCOPE"; //alternate parameter often used in the LDAP world
	public static final String SCOPE_ONE_LEVEL = "SCOPE_ONE_LEVEL";
	public static final String SCOPE_ONE_LEVEL_ALT = "ONELEVEL_SCOPE"; //alternate parameter often used in the LDAP world
	public static final String SCOPE_OBJECT_LEVEL ="SCOPE_OBJECT_LEVEL";
	public static final String SCOPE_OBJECT_LEVEL_ALT ="OBJECT_SCOPE"; //alternate parameter often used in the LDAP world
	
	// Common parameters
	private static Log log;
	private boolean connected; // Specify if we are connected to an LDAP (if the connect method has been called)
	
	/**
	 * Constructor.<br/>
	 * Must be called first. 
	 */
	public LDAPUtils() {
		log = LogFactory.getLog(getClass().getName());
		connected = false;
	}// LDAPUtils
	
	/**
	 * Disconnects from the LDAP
	 * @return true if the deconnection is successfull
	 */
	public boolean disconnect() {
		log.info("disconnect");
		
		try {
			ldapConnection.disconnect();
			connected = false;
		} catch (LDAPException e) {
		}
		log.debug("disconnect:disconnected="+!connected);
		return !connected;
	}// disconnect
	
	/***
	 * Connects to the LDAP
	 * @param ldapUrl : LDAP URL - ex : ldap.univ.fr
	 * @param ldapPort : LDAP port - ex : 389
	 * @param ldapPrincipal : Principal name if required - leave empty if not
	 * @param ldapCredential : Credential if required - leave empty if not
	 * @return true if the connection is successfull
	 * @throws ConfigException
	 */
	public boolean connect(String ldapUrl, String ldapPort, String ldapPrincipal, String ldapCredential) throws ConfigException {
		log.info("connect");
		
		///////////////////////////////
		// Initializing LDAP parameters
		//
		this.ldapUrl = ldapUrl;
		this.ldapPort = ldapPort;
		this.ldapPrincipal = ldapPrincipal;
		this.ldapCredential = ldapCredential;
		
		log.debug("connect:ldapUrl="+ldapUrl);
		log.debug("connect:ldapPort="+ldapPort);
		log.debug("connect:ldapPrincipal="+ldapPrincipal);
		log.debug("connect:ldapCredential="+"****hidden****");
				
		/////////////////////////
		// Connecting to the LDAP
		//
		ldapConnection = new LDAPConnection();
		try {
			ldapConnection.connect(ldapUrl, Integer.parseInt(ldapPort));
			ldapConnection.bind(LDAPConnection.LDAP_V3, ldapPrincipal, ldapCredential.getBytes());
			connected = true;
		} catch (NumberFormatException e) {
			log.error("NumberFormatException handled !");
			if (log.isDebugEnabled()) {
				e.printStackTrace();
			} else log.error(e.toString());
		} catch (LDAPException e) {
			connected = false; // not necessary, just for comprehension
		}
		
		log.debug("connect:connected="+connected);
		return connected;
		
	}// connect
	
	private int processLDAPRequest (String LDAPFilter, 
			String ldapBaseDn,
			String ldapMaxResults,
			String[] attributesToRetrieve,
			String _ldapScope) throws LDAPException, ConfigException {
	
		log.debug("connect:ldapBaseDn="+ldapBaseDn);
		log.debug("connect:ldapMaxResults="+ldapMaxResults);
		log.debug("connect:attributesToRetrieve="+CommonUtils.convertArrayToString(attributesToRetrieve," "));
		
		this.ldapBaseDn = ldapBaseDn;
		this.ldapMaxResults = ldapMaxResults;
		this.attributesToRetrieve = attributesToRetrieve;
		String ldapScopeTemp =  _ldapScope;
		
		if (ldapScopeTemp.equals(SCOPE_SUBTREE_LEVEL) || ldapScopeTemp.equals(SCOPE_SUBTREE_LEVEL_ALT)){
			ldapScope = LDAPConnection.SCOPE_SUB;
			log.debug("connect:ldapScope=SUBTREE");
		}else if (ldapScopeTemp.equals(SCOPE_ONE_LEVEL) || ldapScopeTemp.equals(SCOPE_ONE_LEVEL_ALT)){
			ldapScope = LDAPConnection.SCOPE_ONE;
			log.debug("connect:ldapScope=ONE_LEVEL");
		}else if (ldapScopeTemp.equals(SCOPE_OBJECT_LEVEL) || ldapScopeTemp.equals(SCOPE_OBJECT_LEVEL_ALT)){
			ldapScope = LDAPConnection.SCOPE_BASE;
			log.debug("connect:ldapScope=OBJECT");
		}else{
			throw new ConfigException("LDAPUtils", "processLDAPRequest", "LDAP scope wrong");
		}// if (ldapScopeTemp.equals("SUBTREE"))
		
		// More specific request parameters
		int msLimit=0; 		 								// The maximum time in milliseconds to wait for results
		int serverTimeLimit=0; 								// The maximum time in seconds that the server should spend returning search results
		int dereference=LDAPSearchConstraints.DEREF_NEVER; 	// Specifies when aliases should be dereferenced
		int maxResults=Integer.parseInt(ldapMaxResults);	// The maximum number of search results to return for a search request
		boolean doReferrals=false; 				// Determines whether to automatically follow referrals or not
		int batchSize=0; 	 	 				// The number of results to return in a batch
		LDAPReferralHandler handler=null; 		// The custom authentication handler called when LDAPConnection needs to authenticate, typically on following a referral
		int hop_limit=0; 				 		// The maximum number of referrals to follow in a sequence during automatic referral following
		boolean typesOnly = false;				// If true, returns the names but not the values of the attributes found, if false, returns the names and values for attributes found
		
		// Building the request and requesting the LDAP
		log.info("processLDAPRequest:requesting the LDAP...");
		ldapSearchConstraints = new LDAPSearchConstraints(msLimit, serverTimeLimit, dereference, maxResults, doReferrals, batchSize, handler, hop_limit); 	           
		ldapSearchResults = ldapConnection.search(ldapBaseDn, ldapScope, LDAPFilter, attributesToRetrieve, typesOnly, ldapSearchConstraints); 
		
		return ldapSearchResults.getCount();
		
	}// processLDAPRequest
	
	/**
	 * Gets the number of entries matching the given LDAP filter
	 * @param LDAPFilter LDAP filter request
	 * @param ldapBaseDn : LDAP baseDN - ex : ou=people,dc=univ,dc=fr
	 * @param _ldapScope : LDAP scope, must be SCOPE_OBJECT_LEVEL, SCOPE_ONE_LEVEL or SCOPE_SUBTREE_LEVEL
	 * @return the number of entries as a int
	 * @throws LDAPException
	 * @throws ConfigException 
	 */
	public int getNumberOfEntries (String LDAPFilter, String ldapBaseDn, String _ldapScope) throws LDAPException, ConfigException {
		log.info("getNumberOfEntries");
		
		if (!connected) {
			log.error("getNumberOfEntries:Call the connect method first !");
			return 0;
		}// if (!connected)
		
		// Processing the LDAP request
		String[] attributesToRetrieve = {""};
		return processLDAPRequest(LDAPFilter, ldapBaseDn, "0", attributesToRetrieve, _ldapScope);
	}// getCount

//	/**
//	 * Retrieves entries matching the given LDAP filter
//	 * @param LDAPFilter LDAP filter request
//	 * @return the LDAP entries as an Enumeration of _LDAPEntry
//	 * @throws LDAPException
//	 */
//	public Enumeration getEntries (String LDAPFilter) throws LDAPException {
//		log.info("getEntries");
//		
//		if (!connected) {
//			log.error("getEntries:Call the connect method first !");
//			return null;
//		}// if (!connected)
//		
//		Enumeration entriesToRetrieve = null;
//		
//		// Processing the LDAP request
//		processLDAPRequest(LDAPFilter);
//		
//		// Building the response = Vector of _LDAPEntry
//		Vector _entriesToRetrieve = new Vector();
//		while (ldapSearchResults.hasMore()) {
//			// Getting the current entry
//			LDAPEntry ldapEntry = ldapSearchResults.next();
//			log.debug("getEntries:ldapEntry="+ldapEntry.getDN());
//			
//			// Building a new _LDAPEntry
//			_LDAPEntry _ldapEntry= new _LDAPEntry(ldapEntry.getDN());
//			
//			// Getting the attributes of the current entry 
//			Iterator ldapEntryAttributes = ldapEntry.getAttributeSet().iterator();
//			while (ldapEntryAttributes.hasNext()) {
//				LDAPAttribute ldapEntryAttribute = (LDAPAttribute) ldapEntryAttributes.next();
//				
//				String ldapEntryAttributeName = ldapEntryAttribute.getName();
//				Vector ldatEntryValues = CommonUtils.convertEnumerationToVector(ldapEntryAttribute.getStringValues());
//				log.debug("getEntries:ldapEntryAttribute="+ldapEntryAttributeName);	
//				// Setting attributes of the new _LDAPEntry
//				_ldapEntry.addAttribute(ldapEntryAttributeName, ldatEntryValues);
//			}// while (ldapEntryAttributes.hasNext())
//			
//			// Finally adding the new _LDAPEntry to the result = Vector of _LDAPEntry
//			_entriesToRetrieve.add(_ldapEntry);
//		}// while (ldapSearchResults.hasMore())
//		entriesToRetrieve = _entriesToRetrieve.elements();
//		
//		return entriesToRetrieve;
//	}// getEntries
	
	/**
	 * Retrieves entries matching the given LDAP filter
	 * @param LDAPFilter LDAP filter request
	 * @param ldapBaseDn : LDAP baseDN - ex : ou=people,dc=univ,dc=fr
	 * @param ldapMaxResults : maximum number of entries to retrieve - 0 for no limit
	 * @param attributesToRetrieve : attributes to retrieve as an array of Strings - ex : [{"uid"},{"displayName"}] 
	 * @param _ldapScope : LDAP scope, must be SCOPE_OBJECT_LEVEL, SCOPE_ONE_LEVEL or SCOPE_SUBTREE_LEVEL
	 * @return the LDAP entries as a Vector of _LDAPEntry
	 * @throws LDAPException
	 * @throws ConfigException 
	 */
	public Vector getEntries (String LDAPFilter, 
			String ldapBaseDn, 
			String ldapMaxResults,
			String[] attributesToRetrieve, 
			String _ldapScope) throws LDAPException, ConfigException {
		log.info("getEntries");
		
		if (!connected) {
			log.error("getEntries:Call the connect method first !");
			return null;
		}// if (!connected)
				
		// Processing the LDAP request
		processLDAPRequest(LDAPFilter, ldapBaseDn, ldapMaxResults, attributesToRetrieve, _ldapScope);
		
		// Building the response = Vector of _LDAPEntry
		Vector _entriesToRetrieve = new Vector();
		while (ldapSearchResults.hasMore()) {
			// Getting the current entry
			LDAPEntry ldapEntry = ldapSearchResults.next();
			log.debug("getEntries:ldapEntry="+ldapEntry.getDN());
			
			// Building a new _LDAPEntry
			ESUP_LDAPEntry _ldapEntry= new ESUP_LDAPEntry(ldapEntry.getDN());
			
			// Getting the attributes of the current entry 
			Iterator ldapEntryAttributes = ldapEntry.getAttributeSet().iterator();
			while (ldapEntryAttributes.hasNext()) {
				LDAPAttribute ldapEntryAttribute = (LDAPAttribute) ldapEntryAttributes.next();
				
				String ldapEntryAttributeName = ldapEntryAttribute.getName();
				Vector ldatEntryValues = CommonUtils.convertEnumerationToVector(ldapEntryAttribute.getStringValues());
				log.debug("getEntries:ldapEntryAttribute="+ldapEntryAttributeName);	
				// Setting attributes of the new _LDAPEntry
				_ldapEntry.addAttribute(ldapEntryAttributeName, ldatEntryValues);
			}// while (ldapEntryAttributes.hasNext())
			
			// Finally adding the new _LDAPEntry to the result = Vector of _LDAPEntry
			_entriesToRetrieve.add(_ldapEntry);
		}// while (ldapSearchResults.hasMore())
		
		return _entriesToRetrieve;
	}// getEntries

}
