package railo.runtime.net.ldap;

import java.io.IOException;
import java.security.Security;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;

import railo.commons.lang.ClassException;
import railo.commons.lang.ClassUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.exp.PageException;
import railo.runtime.op.Caster;
import railo.runtime.type.Collection;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Query;
import railo.runtime.type.QueryImpl;
import railo.runtime.type.util.ListUtil;

import com.sun.net.ssl.internal.ssl.Provider;


/**
 * Ldap Client
 */
public final class LDAPClient {

	/**
	 * Field <code>SECURE_NONE</code>
	 */
	public static final short SECURE_NONE=0;
	/**
	 * Field <code>SECURE_CFSSL_BASIC</code>
	 */
	public static final short SECURE_CFSSL_BASIC=1;
	/**
	 * Field <code>SECURE_CFSSL_CLIENT_AUTH</code>
	 */
	public static final short SECURE_CFSSL_CLIENT_AUTH=2;
	
	/**
	 * Field <code>SORT_TYPE_CASE</code>
	 */
	public static final int SORT_TYPE_CASE = 0;
	/**
	 * Field <code>SORT_TYPE_NOCASE</code>
	 */
	public static final int SORT_TYPE_NOCASE = 1;

	/**
	 * Field <code>SORT_DIRECTION_ASC</code>
	 */
	public static final int SORT_DIRECTION_ASC = 0;
	/**
	 * Field <code>SORT_DIRECTION_DESC</code>
	 */
    public static final int SORT_DIRECTION_DESC = 1;
    
	Hashtable env=new Hashtable();
	
	
	/**
	 * constructor of the class
	 * @param server
	 * @param port
	 * @param binaryColumns
	 */
	public LDAPClient(String server, int port,String[] binaryColumns) {

			env.put("java.naming.factory.initial", "com.sun.jndi.ldap.LdapCtxFactory");
			env.put("java.naming.provider.url", "ldap://" + server+":"+port);
      
       // rEAD AS bINARY
            for(int i = 0; i < binaryColumns.length; i++)env.put("java.naming.ldap.attributes.binary", binaryColumns[i]);

       // Referral
            env.put("java.naming.referral", "ignore");
	}
    
    /**
     * sets username password for the connection
     * @param username
     * @param password
     */
    public void setCredential(String username, String password) {
        if(username != null) {
            env.put("java.naming.security.principal", username);
            env.put("java.naming.security.credentials", password);
        } 
        else {
            env.remove("java.naming.security.principal");
            env.remove("java.naming.security.credentials");
        }
    }
    
    /**
     * sets the secure Level
     * @param secureLevel [SECURE_CFSSL_BASIC, SECURE_CFSSL_CLIENT_AUTH, SECURE_NONE]
     * @throws ClassNotFoundException 
     * @throws ClassException 
     */
    public void setSecureLevel(short secureLevel) throws ClassException {
        // Security
        if(secureLevel==SECURE_CFSSL_BASIC) {
            env.put("java.naming.security.protocol", "ssl");
            env.put("java.naming.ldap.factory.socket", "javax.net.ssl.SSLSocketFactory");
            //Class.orName("com.sun.net.ssl.internal.ssl.Provider");
            ClassUtil.loadClass("com.sun.net.ssl.internal.ssl.Provider");
            
            Security.addProvider(new Provider());
            
        } 
        else if(secureLevel==SECURE_CFSSL_CLIENT_AUTH) {
            env.put("java.naming.security.protocol", "ssl");
            env.put("java.naming.security.authentication", "EXTERNAL");
        }
        else {
            env.put("java.naming.security.authentication", "simple");
            env.remove("java.naming.security.protocol");
            env.remove("java.naming.ldap.factory.socket");
        } 
    }
    
    /**
     * sets thr referral
     * @param referral
     */
    public void setReferral(int referral) {
        if(referral > 0) {
            env.put("java.naming.referral", "follow");
            env.put("java.naming.ldap.referral.limit", Caster.toString(referral));
        } 
        else {
            env.put("java.naming.referral", "ignore");
            env.remove("java.naming.ldap.referral.limit");
        }
    }
    


	/**
	 * adds LDAP entries to LDAP server
	 * @param dn
	 * @param attributes
	 * @param delimiter 
	 * @throws NamingException 
	 * @throws PageException 
	 */
	public void add(String dn, String attributes, String delimiter, String seperator) throws NamingException, PageException {
		DirContext ctx = new InitialDirContext(env);
	    ctx.createSubcontext(dn, toAttributes(attributes,delimiter,seperator));
	    ctx.close();
	}
	
    /**
     * deletes LDAP entries on an LDAP server
     * @param dn
     * @throws NamingException
     */
    public void delete(String dn)  throws NamingException{
        DirContext ctx = new InitialDirContext(env);
        ctx.destroySubcontext(dn);
        ctx.close();
    }
    
    /**
     *  modifies distinguished name attribute for LDAP entries on LDAP server
     * @param dn
     * @param attributes
     * @throws NamingException 
     */
    public void modifydn(String dn, String attributes)  throws NamingException{
    	DirContext ctx = new InitialDirContext(env);
	    ctx.rename(dn, attributes);
	    ctx.close();
    }
    
    public void modify(String dn, int modifytype, String strAttributes, String delimiter, String separator) throws NamingException, PageException {

            DirContext context = new InitialDirContext(env);
            String strArrAttributes[] = toStringAttributes(strAttributes,delimiter);
            
            int count = 0;
            for(int i=0; i<strArrAttributes.length; i++) {
                String[] attributesValues = getAttributesValues(strArrAttributes[i], separator);
                if(attributesValues == null)count++;
                else count+=attributesValues.length;
            }
            
            ModificationItem modItems[] = new ModificationItem[count];
            BasicAttribute basicAttr = null;
            int k = 0;
            for(int i = 0; i < strArrAttributes.length; i++) {
                String attribute = strArrAttributes[i];
                String type = getAttrValueType(attribute);
                String values[] = getAttributesValues(attribute,separator);
                
                if(modifytype==DirContext.REPLACE_ATTRIBUTE) {
                    if(values == null) basicAttr = new BasicAttribute(type);
                    else basicAttr = new BasicAttribute(type, values[0]);
                    
                    modItems[k] = new ModificationItem(modifytype, basicAttr);
                    k++;
                    if(values != null && values.length > 1) {
                        for(int j = 1; j < values.length; j++) {
                            basicAttr = new BasicAttribute(type, values[j]);
                            modItems[k] = new ModificationItem(DirContext.ADD_ATTRIBUTE, basicAttr);
                            k++;
                        }
                    }
                } 
                else {
                    for(int j = 0; j < values.length; j++) {
                        if(type != null || modifytype==DirContext.ADD_ATTRIBUTE)
                             basicAttr = new BasicAttribute(type, values[j]);
                        else basicAttr = new BasicAttribute(values[j]);
                        modItems[k] = new ModificationItem(modifytype, basicAttr);
                        k++;
                    }
                }
            }

            context.modifyAttributes(dn, modItems);
            context.close();

    }
    
    
    
    /**
     * @param dn
     * @param strAttributes
     * @param scope
     * @param startrow
     * @param maxrows
     * @param timeout
     * @param sort
     * @param sortType
     * @param sortDirection
     * @param start
     * @param separator
     * @param filter
     * @return
     * @throws NamingException
     * @throws PageException
     * @throws IOException 
     */
    public Query query(String strAttributes,int scope, int startrow, int maxrows, int timeout, 
            String[] sort, int sortType, int sortDirection, 
            String start, String separator, String filter) 
        throws NamingException, PageException, IOException {
        //strAttributes=strAttributes.trim();
        boolean attEQAsterix=strAttributes.trim().equals("*");
        String[] attributes = attEQAsterix?new String[]{"name","value"}:toStringAttributes(strAttributes,",");
        
        
        
        // Control
        SearchControls controls = new SearchControls();
        controls.setReturningObjFlag(true);
        controls.setSearchScope(scope);
        if(!attEQAsterix)controls.setReturningAttributes(toStringAttributes(strAttributes,","));
        if(maxrows>0)controls.setCountLimit(startrow + maxrows + 1);
        if(timeout>0)controls.setTimeLimit(timeout);
        

        InitialLdapContext context = new InitialLdapContext(env, null);
        
        // Search
        Query qry=new QueryImpl(attributes,0,"query");
        try {
            NamingEnumeration results = context.search(start, filter, controls);
            
            // Fill result
            int row=1;
            if(!attEQAsterix) {
                while(results.hasMoreElements()) {
                    SearchResult resultRow = (SearchResult)results.next();
                    if(row++<startrow)continue;
                    
                    int len=qry.addRow();
                    NamingEnumeration rowEnum = resultRow.getAttributes().getAll();
                    String dn = resultRow.getNameInNamespace(); 
                    qry.setAtEL("dn",len,dn);
                    while(rowEnum.hasMore()) {
                        Attribute attr = (Attribute)rowEnum.next();
                        Collection.Key key = KeyImpl.init(attr.getID());
                        Enumeration values = attr.getAll();
                        Object value;
                        String existing,strValue;
                        while(values.hasMoreElements()) {
                            value = values.nextElement();
                            strValue=Caster.toString(value,null);
                            existing=Caster.toString(qry.getAt(key, len,null),null);
                            if(!StringUtil.isEmpty(existing) && !StringUtil.isEmpty(strValue)) {
                            	value = existing + separator + strValue;
                         	}
                            else if(!StringUtil.isEmpty(existing))
                            	value = existing;
                            
                         	qry.setAtEL(key,len,value);
                        }            
                    }
                    if(maxrows>0 && len>=maxrows)break;
                }
            }
            else {
                
                outer:while(results.hasMoreElements()) {
                    SearchResult resultRow = (SearchResult)results.next();
                    if(row++<startrow)continue;
                    
                    Attributes attributesRow = resultRow.getAttributes();
                    NamingEnumeration rowEnum = attributesRow.getIDs();
                    while(rowEnum.hasMoreElements()) {
                        int len=qry.addRow();
                        String name = Caster.toString(rowEnum.next());
                        Object value=null;
                        
                        try {
                            value=attributesRow.get(name).get();
                        }catch(Exception e) {}
                        
                        qry.setAtEL("name",len,name);
                        qry.setAtEL("value",len,value);
                        if(maxrows>0 && len>=maxrows)break outer;
                    }
                    qry.setAtEL("name",qry.size(),"dn");
                }
            }
        }
        finally {
            context.close();
        }
        // Sort
        if(sort!=null && sort.length>0) {
            int order = sortDirection==SORT_DIRECTION_ASC ? Query.ORDER_ASC : Query.ORDER_DESC;
            for(int i=sort.length-1;i>=0;i--) {
                String item=sort[i];
                if(item.indexOf(' ')!=-1)item=ListUtil.first(item," ",true);
                qry.sort(KeyImpl.getInstance(item),order);
                //keys[i] = new SortKey(item);
            }
        }    
        return qry;
    }

    private static String[] toStringAttributes(String strAttributes,String delimiter) throws PageException {
		return ListUtil.toStringArrayTrim(ListUtil.listToArrayRemoveEmpty(strAttributes,delimiter));		
	}
	
	private static Attributes toAttributes(String strAttributes,String delimiter, String separator) throws PageException {
		String[] arrAttr = toStringAttributes(strAttributes,delimiter);
		
		
		BasicAttributes attributes = new BasicAttributes();
        for(int i=0; i<arrAttr.length; i++) {
            String strAttr = arrAttr[i];
            
            // Type
            int eqIndex=strAttr.indexOf('=');
            Attribute attr = new BasicAttribute((eqIndex != -1)?strAttr.substring(0, eqIndex).trim():null);
            
            // Value
            String strValue = (eqIndex!=-1)?strAttr.substring( eqIndex+ 1):strAttr;
            String[] arrValue=ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(strValue,separator));
            
            // Fill
            for(int y=0; y<arrValue.length; y++) {
                attr.add(arrValue[y]);
            }
            attributes.put(attr);
        }
        return attributes;
		
	}
    
    private String getAttrValueType(String attribute) {
        int eqIndex=attribute.indexOf("=");
        if(eqIndex != -1) return attribute.substring(0, eqIndex).trim();
        return null;
    }
    
    private String[] getAttributesValues(String attribute, String separator) throws PageException {
        String strValue = attribute.substring(attribute.indexOf("=") + 1);
        if(strValue.length() == 0) return null;
        return ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(strValue,separator.equals(", ") ? "," : separator));
    }
		
}