/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package org.teiid.translator.ldap;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;

import javax.naming.NamingException;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.ldap.LdapContext;

import org.teiid.language.Argument;
import org.teiid.translator.DataNotAvailableException;
import org.teiid.translator.ExecutionContext;
import org.teiid.translator.ProcedureExecution;
import org.teiid.translator.TranslatorException;

public class LDAPDirectCreateUpdateDeleteQueryExecution implements ProcedureExecution {
	private static final String ATTRIBUTES = "attributes"; //$NON-NLS-1$
	private List<Argument> arguments;
	protected LdapContext ldapConnection;
	protected LDAPExecutionFactory executionFactory;
	protected ExecutionContext executionContext;
	private int updateCount = -1;
	private boolean returnsArray = true;
	private String query;
	
	public LDAPDirectCreateUpdateDeleteQueryExecution(List<Argument> arguments, LDAPExecutionFactory factory, ExecutionContext executionContext, LdapContext connection, String query, boolean returnsArray) {
		this.arguments = arguments;
		this.executionFactory = factory;
		this.executionContext = executionContext;
		this.ldapConnection = connection;
		this.query = query;
		this.returnsArray = returnsArray;
	}
	
	@Override
	public void execute() throws TranslatorException {
		String firstToken = null;
		
		StringTokenizer st = new StringTokenizer(query, ";"); //$NON-NLS-1$
		if (st.hasMoreTokens()) {
			firstToken = st.nextToken();
		}
		if (firstToken == null || (!firstToken.equalsIgnoreCase("create") && !firstToken.equalsIgnoreCase("update") && !firstToken.equalsIgnoreCase("delete"))) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12009));
		}		
		LdapContext ldapCtx = null;
		try {
			ldapCtx = (LdapContext)this.ldapConnection.lookup("");  //$NON-NLS-1$
		} catch (NamingException ne) {
			throw new TranslatorException(ne, LDAPPlugin.Util.getString("LDAPUpdateExecution.createContextError",ne.getExplanation()));//$NON-NLS-1$
		}		

		if (firstToken.equalsIgnoreCase("delete")) { // //$NON-NLS-1$
			String theDN = getDN(st); // the token after the marker is always DN
			if (st.hasMoreTokens()) {
				throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12013, st.nextToken()));
			}
			try {
				ldapCtx.destroySubcontext(theDN);
				this.updateCount = 1;
			} catch (NamingException ne) {
				throw new TranslatorException(ne, LDAPPlugin.Util.getString("LDAPUpdateExecution.deleteFailed",theDN,ne.getExplanation()));//$NON-NLS-1$
			} catch (Exception e) {
				throw new TranslatorException(e, LDAPPlugin.Util.getString("LDAPUpdateExecution.deleteFailedUnexpected",theDN));//$NON-NLS-1$
			}			
		}
		else if (firstToken.equalsIgnoreCase("create")) { //$NON-NLS-1$
			String theDN = getDN(st); // the token after the marker is always DN
			ArrayList<BasicAttribute> attributes = getAttributes(st);
			if (st.hasMoreTokens()) {
				throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12013, st.nextToken()));
			}
			BasicAttributes attrs = new BasicAttributes();
			for (BasicAttribute ba:attributes) {
				attrs.put(ba);
			}
			try {
				ldapCtx.createSubcontext(theDN, attrs);
				this.updateCount = 1;
			} catch (NamingException ne) {
				throw new TranslatorException(ne, LDAPPlugin.Util.getString("LDAPUpdateExecution.insertFailed", theDN, ne.getExplanation()));//$NON-NLS-1$
			} catch (Exception e) {
				throw new TranslatorException(e,LDAPPlugin.Util.getString("LDAPUpdateExecution.insertFailedUnexpected", theDN));//$NON-NLS-1$
			}			
		}
		else if (firstToken.equalsIgnoreCase("update")) { //$NON-NLS-1$
			String theDN = getDN(st); // the token after the marker is always DN
			ArrayList<BasicAttribute> attributes = getAttributes(st);
			if (st.hasMoreTokens()) {
				throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12013, st.nextToken()));
			}
			ModificationItem[] updateMods = new ModificationItem[attributes.size()];
			int i=0;
			for (BasicAttribute ba:attributes) {
				updateMods[i++] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, ba);
			}		
			try {
				ldapCtx.modifyAttributes(theDN, updateMods);
				this.updateCount = 1;
			} catch (NamingException ne) {
				throw new TranslatorException(ne, LDAPPlugin.Util.getString("LDAPUpdateExecution.updateFailed", theDN, ne.getExplanation()));//$NON-NLS-1$
			} catch (Exception e) {
				throw new TranslatorException(e, LDAPPlugin.Util.getString("LDAPUpdateExecution.updateFailedUnexpected",theDN));//$NON-NLS-1$
			}			
		}
	}
	
	private String getDN(StringTokenizer st) throws TranslatorException {
		if (!st.hasMoreTokens()) {
			throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12010));
		}
		return st.nextToken();
	}

	private ArrayList<BasicAttribute> getAttributes(StringTokenizer st) throws TranslatorException {
		if (!st.hasMoreTokens()) {
			throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12011));
		}
		
		ArrayList<BasicAttribute> attributes = new ArrayList<BasicAttribute>();
		
		if(st.hasMoreElements()) {
			String var = st.nextToken();
			
			int index = var.indexOf('=');
			if (index == -1) {
				throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12011));
			}
			String key = var.substring(0, index).trim();
			String value = var.substring(index+1).trim();
			
			if (key.equalsIgnoreCase(ATTRIBUTES)) {
				StringTokenizer attrTokens = new StringTokenizer(value, ","); //$NON-NLS-1$
				int attrCount = 0;
				while(attrTokens.hasMoreElements()) {
					String name = attrTokens.nextToken().trim();
					if (arguments.size() <= attrCount) {
						throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12012, name));
					}
					Argument argument = arguments.get(attrCount++);
					Object  anObj = null;
					if (argument.getArgumentValue().getValue() != null) {
						anObj = IQueryToLdapSearchParser.getLiteralString(argument.getArgumentValue());
					}
					
					attributes.add(new BasicAttribute(name, anObj));
				}
			} else {
				throw new TranslatorException(LDAPPlugin.Util.gs(LDAPPlugin.Event.TEIID12011));
			}
		}
		return attributes;
	}

	@Override
	public List<?> getOutputParameterValues() throws TranslatorException {
		return null;
	}

	@Override
	public List<?> next() throws TranslatorException, DataNotAvailableException {
		if (this.updateCount != -1) {
			List<Object> row = Arrays.asList((Object)Integer.valueOf(this.updateCount));
			if (returnsArray) {
				Object[] columns = new Object[1];
				columns[0] = this.updateCount;
				row.set(0, columns);
			}
			this.updateCount = -1;
			return row;
		}
		return null;
	}

	@Override
	public void close() {
	}

	@Override
	public void cancel() throws TranslatorException {
	}
}
