/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.    
 */
package org.apache.openjpa.das;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.sdo.SDOEntityManager;
import org.apache.openjpa.persistence.OpenJPAQuery;
import org.apache.tuscany.das.rdb.Command;
import org.apache.tuscany.das.rdb.DAS;
import org.apache.tuscany.sdo.AnyTypeDataObject;
import org.apache.tuscany.sdo.SDOFactory;

import commonj.sdo.DataObject;

/**
 * Implements Tuscany DAS through delegation to OpenJPA EntityManager.
 * 
 * @author Pinaki Poddar
 * @since 0.3.0
 * 
 */
class FluidDAS implements DAS {
	private static final Localizer _loc = Localizer.forPackage(FluidDAS.class);

	private final SDOEntityManager em;

	/**
	 * Enumeration of available Commands.
	 */
	public enum COMMANDS {
		INSERT, FIND, DELETE
	};

	/**
	 * Constructed by <em>any</em> FluidDASFactory.createDAS() methods.
	 * 
	 */
	FluidDAS(SDOEntityManager em) {
		this.em = em;
	}

	/**
	 * Apply the changes in the given DataObject.
	 */
	public void applyChanges(DataObject root) {
		em.begin();
		em.merge(root);
		em.commit();
	}

	/**
	 * Create a command.
	 * @param Permissible values are <code>"INSERT"</code>, <code>"FIND"</code>,
	 * <code>"SELECT"</code>, <code>"DELETE"</code>.
	 */
	public Command createCommand(String cmd) {
		if (cmd==null)
			return null;
		COMMANDS command = null;
		try {
			command = COMMANDS.valueOf(cmd);
		} catch (IllegalArgumentException ex) {
			if (cmd.trim().length()>"SELECT ".length() &&
				"SELECT ".equalsIgnoreCase(cmd.trim().substring(0, "SELECT ".length()))) 
				return new QueryCommand(em, cmd);
			throw new IllegalArgumentException(error("bad-command", cmd));
		}
		switch (command) {
		case INSERT:
			return new PersistCommand(em);
		case FIND:
			return new FindCommand(em);
		case DELETE:
			return new RemoveCommand(em);
		}
		return null;
	}

	/**
	 * Get a command.
	 * @param Permissible values are <code>"INSERT"</code>, <code>"FIND"</code>,
	 * <code>"SELECT"</code>, <code>"DELETE"</code>.
	 */
	public Command getCommand(String name) {
		return createCommand(name);
	}

	/**
	 * Releases the resources by closing the underlying EntityManager.
	 */
	public void releaseResources() {
		em.close();
	}
	
	static String error(String key, Object... values) {
		return _loc.get(key, values).toString();
	}


	static class AbstarctCommand implements Command {
		protected final SDOEntityManager em;

		protected Map<Integer, Object> params = new HashMap<Integer, Object>();

		AbstarctCommand(SDOEntityManager em) {
			this.em = em;
		}

		public void execute() {
			throw new AbstractMethodError();
		}

		public DataObject executeQuery() {
			throw new AbstractMethodError();
		}

		public void setParameter(int index, Object value) {
			params.put(index, value);
		}

		public Object getParameter(int index) {
			return params.get(index);
		}

		public int getGeneratedKey() {
			throw new AbstractMethodError();
		}

	}

	/**
	 * Command for persisting SDO DataObjects.
	 * <br>
	 * This command accepts any DataObject or PersistenceCapable instance 
	 * as parameter at any arbitray index.
	 * <br>
	 * This command is executed by invoking {@link #execute()} which 
	 * persists all the instances set via <code>setParameter()</code> 
	 * <br>
	 * This command does not support {@link #executeQuery()}.
	 */
	public static class PersistCommand extends FluidDAS.AbstarctCommand {
		PersistCommand(SDOEntityManager em) {
			super(em);
		}

		/**
		 * Persists all instances set via {@link #setParameter}.
		 * Commits them to the database.
		 * 
		 */
		public void execute() {
			em.begin();
			for (Object data : params.values()) {
				if (data == null) continue;
				em.persist(data);
			}
			em.commit();
			params.clear();
		}
	}

	/**
	 * Command for finding SDO DataObjects through primary key.
	 * <br>
	 * This command accepts a DataObject identitifer as parameters at 
	 * index 1.<br>
	 * DataObject identitifer is a String of the form <code>Type:id</code>
	 * where <code>Type</code> is the SDO Type name and <code>id</code> is 
	 * value of identifier property.
	 * <br>
	 * This command is executed by invoking {@link #executeQuery()} which 
	 * returns the instance of Type and id set via <code>setParameter()</code> 
	 * <br>
	 * This command does not support {@link #execute()}.
	 */
	public static class FindCommand extends FluidDAS.AbstarctCommand {
		FindCommand(SDOEntityManager em) {
			super(em);
		}

		public void setParameter(int index, Object value) {
			if (index != 1)
				throw new IllegalArgumentException(error("find-bad-index",
						index, value));
			if (!(value instanceof String))
				throw new IllegalArgumentException(error("find-bad-value",
						index, value));

			super.setParameter(index, value);
		}

		public DataObject executeQuery() {
			return em.find(DataObject.class, getParameter(1));
		}
	}

	/**
	 * Command for selecting SDO DataObjects through JPQL Query.
	 * <br>
	 * This query parameters can be set via index 1 or higher.<br>
	 * <br>
	 * This command is executed by invoking {@link #executeQuery()} which 
	 * returns a DataObject whose 0-th property contains a list of
	 * DataObjects that are the <em>actual</em> result of the query. 
	 * <br>
	 * This command supports {@link #execute()} whose semantics is to
	 * excute the JPQL as it is.
	 */
	public static class QueryCommand extends FluidDAS.AbstarctCommand {
		OpenJPAQuery query;

		QueryCommand(SDOEntityManager em, String jpql) {
			super(em);
			query = em.createQuery(jpql);
		}

		public void setParameter(int index, Object value) {
			if (index <= 0)
				throw new IllegalArgumentException(error("query-bad-query",
						index, value));
			super.setParameter(index, value);
		}

		public void execute() {
			for (Integer index : params.keySet()) {
				Object value = params.get(index);
				query.setParameter(index, value);
			}
			query.executeUpdate();
		}

		public DataObject executeQuery() {
			for (Integer index : params.keySet()) {
				Object value = params.get(index);
				query.setParameter(index, value);
			}
			List<DataObject> list = query.getResultList();
			return wrap(list);
		}

		DataObject wrap(List<DataObject> list) {
			AnyTypeDataObject any = SDOFactory.eINSTANCE
					.createAnyTypeDataObject();
			any.set("list", list);
			return any;
		}
	}

	/**
	 * Command for deleting SDO DataObjects.
	 * <br>
	 * This command accepts any DataObject or PersistenceCapable instance
	 * as parameters at any arbitray index.
	 * <br>
	 * This command is executed by invoking {@link #execute()} which 
	 * persists all the instances set via <code>setParameter()</code> 
	 * <br>
	 * This command does not support {@link #executeQuery()}.
	 */
	public static class RemoveCommand extends FluidDAS.AbstarctCommand {
		RemoveCommand(SDOEntityManager em) {
			super(em);
		}

		public void execute() {
			em.begin();
			for (Object data : params.values()) {
				if (data == null) continue;
				em.remove(data);
			}
			em.commit();
			params.clear();
		}
	}
}
