package com.sap.mentors.lemonaid.odata;

import javax.persistence.EntityManagerFactory;

import org.apache.olingo.odata2.api.ODataCallback;
import org.apache.olingo.odata2.api.ODataService;
import org.apache.olingo.odata2.api.ODataServiceFactory;
import org.apache.olingo.odata2.api.edm.provider.EdmProvider;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.processor.ODataContext;
import org.apache.olingo.odata2.api.processor.ODataErrorCallback;
import org.apache.olingo.odata2.api.processor.ODataSingleProcessor;
import org.apache.olingo.odata2.jpa.processor.api.ODataJPAContext;
import org.apache.olingo.odata2.jpa.processor.api.ODataJPATransaction;
import org.apache.olingo.odata2.jpa.processor.api.OnJPAWriteContent;
import org.apache.olingo.odata2.jpa.processor.api.exception.ODataJPAErrorCallback;
import org.apache.olingo.odata2.jpa.processor.api.exception.ODataJPARuntimeException;
import org.apache.olingo.odata2.jpa.processor.api.factory.ODataJPAAccessFactory;
import org.apache.olingo.odata2.jpa.processor.api.factory.ODataJPAFactory;

import com.sap.mentors.lemonaid.odata.util.ODataContextUtil;
import com.sap.mentors.lemonaid.odata.util.SpringContextsUtil;

public class JPAServiceFactory extends ODataServiceFactory {

	private ODataJPAContext oDataJPAContext;
	private ODataContext oDataContext;
	private boolean setDetailErrors = true;
	private OnJPAWriteContent onJPAWriteContent = null;
	private ODataJPATransaction oDataJPATransaction = null;

	public static final String DEFAULT_ENTITY_UNIT_NAME = "Model";
	public static final String ENTITY_MANAGER_FACTORY_ID = "entityManagerFactory";

	public ODataJPAContext initializeODataJPAContext() throws ODataJPARuntimeException {
		ODataJPAContext oDataJPAContext = getODataJPAContext();

		EntityManagerFactory factory = (EntityManagerFactory) SpringContextsUtil.getBean(ENTITY_MANAGER_FACTORY_ID);

		oDataJPAContext.setEntityManagerFactory(factory);
		oDataJPAContext.setPersistenceUnitName(DEFAULT_ENTITY_UNIT_NAME);
		oDataJPAContext.setJPAEdmExtension(new JPAEdmExtension());
		ODataContextUtil.setODataContext(oDataJPAContext.getODataContext());

		return oDataJPAContext;
	}

	/**
	 * Creates an OData Service based on the values set in
	 * {@link org.apache.olingo.odata2.jpa.processor.api.ODataJPAContext} and
	 * {@link org.apache.olingo.odata2.api.processor.ODataContext}.
	 */
	@Override
	public final ODataService createService(final ODataContext ctx) throws ODataException {

		oDataContext = ctx;

		// Initialize OData JPA Context
		oDataJPAContext = initializeODataJPAContext();

		validatePreConditions();

		ODataJPAFactory factory = ODataJPAFactory.createFactory();
		ODataJPAAccessFactory accessFactory = factory.getODataJPAAccessFactory();

		// OData JPA Processor
		if (oDataJPAContext.getODataContext() == null) {
			oDataJPAContext.setODataContext(ctx);
		}

		ODataSingleProcessor odataJPAProcessor = new ODataJPAProcessor(oDataJPAContext);

		// OData Entity Data Model Provider based on JPA
		EdmProvider edmProvider = accessFactory.createJPAEdmProvider(oDataJPAContext);

		return createODataSingleProcessorService(edmProvider, odataJPAProcessor);
	}

	/**
	 * @return an instance of type {@link ODataJPAContext}
	 * @throws ODataJPARuntimeException
	 */
	public final ODataJPAContext getODataJPAContext() throws ODataJPARuntimeException {
		if (oDataJPAContext == null) {
			oDataJPAContext = ODataJPAFactory.createFactory().getODataJPAAccessFactory().createODataJPAContext();
		}
		if (oDataContext != null) {
			oDataJPAContext.setODataContext(oDataContext);
		}
		return oDataJPAContext;

	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends ODataCallback> T getCallback(final Class<T> callbackInterface) {
		if (setDetailErrors == true) {
			if (callbackInterface.isAssignableFrom(ODataErrorCallback.class)) {
				return (T) new ODataJPAErrorCallback();
			}
		}
		if (onJPAWriteContent != null) {
			if (callbackInterface.isAssignableFrom(OnJPAWriteContent.class)) {
				return (T) onJPAWriteContent;
			}
		}
		if (oDataJPATransaction != null) {
			if (callbackInterface.isAssignableFrom(ODataJPATransaction.class)) {
				return (T) oDataJPATransaction;
			}
		}
		return null;
	}

	/**
	 * The methods sets the context with a callback implementation for JPA
	 * provider specific content. For details refer to
	 * {@link org.apache.olingo.odata2.jpa.processor.api.OnJPAWriteContent}
	 * 
	 * @param onJPAWriteContent
	 *            is an instance of type
	 *            {@link org.apache.olingo.odata2.jpa.processor.api.OnJPAWriteContent}
	 */
	protected void setOnWriteJPAContent(final OnJPAWriteContent onJPAWriteContent) {
		this.onJPAWriteContent = onJPAWriteContent;
	}

	/**
	 * The methods sets the context with a callback implementation for JPA
	 * transaction specific content. For details refer to
	 * {@link ODataJPATransaction}
	 * 
	 * @param oDataJPATransaction
	 *            is an instance of type
	 *            {@link org.apache.olingo.odata2.jpa.processor.api.ODataJPATransaction}
	 */
	protected void setODataJPATransaction(final ODataJPATransaction oDataJPATransaction) {
		this.oDataJPATransaction = oDataJPATransaction;
	}

	/**
	 * The method sets the context whether a detail error message should be
	 * thrown or a less detail error message should be thrown by the library.
	 * 
	 * @param setDetailErrors
	 *            takes
	 *            <ul>
	 *            <li>true - to indicate that library should throw a detailed
	 *            error message</li>
	 *            <li>false - to indicate that library should not throw a
	 *            detailed error message</li>
	 *            </ul>
	 * 
	 */
	protected void setDetailErrors(final boolean setDetailErrors) {
		this.setDetailErrors = setDetailErrors;
	}

	private void validatePreConditions() throws ODataJPARuntimeException {

		if (oDataJPAContext.getEntityManagerFactory() == null) {
			throw ODataJPARuntimeException.throwException(ODataJPARuntimeException.ENTITY_MANAGER_NOT_INITIALIZED,
					null);
		}

	}
		
}