package com.orientechnologies.orient.object.jpa;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;

import javax.persistence.PersistenceException;
import javax.persistence.SharedCacheMode;
import javax.persistence.ValidationMode;
import javax.persistence.spi.ClassTransformer;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.sql.DataSource;

import com.orientechnologies.orient.object.jpa.parsing.JPAVersion;

/**
 * An implementation of PersistenceUnit for parsed persistence unit metadata
 * 
 */
public class OJPAPersistenceUnitInfo implements PersistenceUnitInfo {
	/**
	 * the name of the persistence unit
	 */
	private final String													unitName;

	/**
	 * transaction type of the entity managers created by the EntityManagerFactory
	 */
	private final PersistenceUnitTransactionType	transactionType;

	/**
	 * The JAR file or directory whose META-INF directory contains persistence.xml is called the root of the persistence unit. The
	 * scope of the persistence unit is determined by the persistence unit's root.
	 */
	private final URL															unitRootUrl;
	/**
	 * the list of mapping file names that the persistence provider must load to determine the mappings for the entity classes
	 */
	private final List<String>										mappingFileNames				= new ArrayList<String>();

	/**
	 * the list of the names of the classes that the persistence provider must add to its set of managed classes
	 */
	private final List<String>										managedClassNames				= new ArrayList<String>();

	/**
	 * whether classes in the root of the persistence unit that have not been explicitly listed are to be included in the set of
	 * managed classes. When set to true then only listed classes and jars will be scanned for persistent classes, otherwise the
	 * enclosing jar or directory will also be scanned. Not applicable to Java SE persistence units.
	 * 
	 * @see 'Note' http://static.springsource.org/spring/docs/4.0.x/spring-framework-reference/html/orm.html#orm-jpa-setup-lcemfb The
	 *      exclude-unlisted-classes element always indicates that no scanning for annotated entity classes is supposed to occur, in
	 *      order to support the <exclude-unlisted-classes/> shortcut. This is in line with the JPA specification, which suggests that
	 *      shortcut, but unfortunately is in conflict with the JPA XSD, which implies false for that shortcut. Consequently,
	 *      <exclude-unlisted-classes> false </exclude-unlisted-classes/> is not supported. Simply omit the exclude-unlisted-classes
	 *      element if you want entity class scanning to occur.
	 */
	private boolean																excludeUnlistedClasses	= false;

	/**
	 * the second-level cache mode that must be used by the provider for the persistence unit
	 * 
	 */
	private SharedCacheMode												sharedCacheMode					= SharedCacheMode.UNSPECIFIED;

	/**
	 * the validation mode to be used by the persistence provider for the persistence unit
	 */
	private ValidationMode												validationMode					= ValidationMode.AUTO;

	/**
	 * OrientDB Properties object
	 */
	private final Properties											properties							= new OJPAProperties();

	/**
	 * TODO: implement transformer provider-supplied transformer that the container invokes at class-(re)definition time
	 */
	private final Set<ClassTransformer>						classTransformers				= new HashSet<ClassTransformer>();

	private final List<URL>												jarFileUrls							= new ArrayList<URL>();

	private String																providerClassName;

	private final JPAVersion											xmlSchemaVersion;

	/**
	 * Create a new persistence unit with the given name, transaction type, location and defining bundle
	 * 
	 * @param unitName
	 *          must not be null
	 * @param transactionType
	 *          may be null
	 * @param unitRootUrl
	 *          root of the persistence unit
	 * @param schemaVersion
	 *          The version of the JPA schema used in persistence.xml
	 */
	public OJPAPersistenceUnitInfo(String unitName, String transactionType, URL unitRootUrl, String xmlSchemaVersion) {
		this.unitName = unitName;
		this.unitRootUrl = unitRootUrl;
		if (unitName == null || unitName.isEmpty()) {
			throw new IllegalStateException("PersistenceUnitName for entity manager should not be null or empty");
		}
		this.xmlSchemaVersion = JPAVersion.parse(xmlSchemaVersion);
		this.transactionType = initTransactionType(transactionType);
	}

	/**
	 * @param provider
	 */
	public void setProviderClassName(String providerClassName) {
		this.providerClassName = providerClassName;
	}

	/**
	 * @param jtaDataSource
	 */
	public void setJtaDataSource(String jtaDataSource) {
		// TODO: implement
	}

	/**
	 * @param nonJtaDataSource
	 */
	public void setNonJtaDataSource(String nonJtaDataSource) {
		// TODO: implement
	}

	/**
	 * @param mappingFileName
	 */
	public void addMappingFileName(String mappingFileName) {
		mappingFileNames.add(mappingFileName);
	}

	/**
	 * @param jarFileName
	 */
	public void addJarFileName(String jarFileName) {
		jarFileUrls.add(initJarFile(jarFileName));
	}

	/**
	 * @param className
	 */
	public void addClassName(String className) {
		managedClassNames.add(className);
	}

	/**
	 * @param exclude
	 */
	public void setExcludeUnlisted(boolean exclude) {
		excludeUnlistedClasses = exclude;
	}

	/**
	 * @param name
	 * @param value
	 */
	public void addProperty(String name, String value) {
		properties.setProperty(name, value);
	}

	/**
	 * @param sharedCacheMode
	 */
	public void setSharedCacheMode(String sharedCacheMode) {
		this.sharedCacheMode = initSharedCacheMode(sharedCacheMode);
	}

	/**
	 * @param validationMode
	 */
	public void setValidationMode(String validationMode) {
		this.validationMode = initValidationMode(validationMode);
	}

	@Override
	public String toString() {
		return "PersistenceUnit@" + unitName + " " + super.toString();
	}

	@Override
	public String getPersistenceUnitName() {
		return unitName;
	}

	@Override
	public String getPersistenceProviderClassName() {
		return providerClassName;
	}

	@Override
	public PersistenceUnitTransactionType getTransactionType() {
		return transactionType;
	}

	@Override
	public DataSource getJtaDataSource() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public DataSource getNonJtaDataSource() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public List<String> getMappingFileNames() {
		return mappingFileNames;
	}

	@Override
	public List<URL> getJarFileUrls() {
		return jarFileUrls;
	}

	@Override
	public URL getPersistenceUnitRootUrl() {
		return unitRootUrl;
	}

	@Override
	public List<String> getManagedClassNames() {
		return managedClassNames;
	}

	@Override
	public boolean excludeUnlistedClasses() {
		return excludeUnlistedClasses;
	}

	@Override
	public SharedCacheMode getSharedCacheMode() {
		return sharedCacheMode;
	}

	@Override
	public ValidationMode getValidationMode() {
		return validationMode;
	}

	@Override
	public Properties getProperties() {
		return properties;
	}

	@Override
	public String getPersistenceXMLSchemaVersion() {
		return xmlSchemaVersion.getVersion();
	}

	@Override
	public ClassLoader getClassLoader() {
		return ThreadLocal.class.getClassLoader();
	}

	@Override
	public void addTransformer(ClassTransformer transformer) {
		classTransformers.add(transformer);
	}

	@Override
	public ClassLoader getNewTempClassLoader() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int hashCode() {
		return unitName.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		return unitName.equals(((OJPAPersistenceUnitInfo) obj).getPersistenceUnitName());
	}

	// ------------- helpers
	/**
	 * TODO: init default value In a Java EE environment, if this element is not specified, the default is JTA. In a Java SE
	 * environment, if this element is not specified, a default of RESOURCE_LOCAL may be assumed.
	 * 
	 * @param elementContent
	 * @return
	 */
	public static PersistenceUnitTransactionType initTransactionType(String elementContent) {
		if (elementContent == null || elementContent.isEmpty()) {
			return null;
		}

		try {
			return PersistenceUnitTransactionType.valueOf(elementContent.toUpperCase());
		} catch (IllegalArgumentException ex) {
			throw new PersistenceException("Unknown TransactionType: " + elementContent, ex);
		}

	}

	public static ValidationMode initValidationMode(String validationMode) {
		if (validationMode == null || validationMode.isEmpty()) {
			return ValidationMode.AUTO;
		}

		try {
			return ValidationMode.valueOf(validationMode.toUpperCase());
		} catch (IllegalArgumentException ex) {
			throw new PersistenceException("Unknown ValidationMode: " + validationMode, ex);
		}
	}

	public static SharedCacheMode initSharedCacheMode(String sharedCacheMode) {
		if (sharedCacheMode == null || sharedCacheMode.isEmpty()) {
			return SharedCacheMode.UNSPECIFIED;
		}

		try {
			return SharedCacheMode.valueOf(sharedCacheMode.toUpperCase());
		} catch (IllegalArgumentException ex) {
			throw new PersistenceException("Unknown ValidationMode: " + sharedCacheMode, ex);
		}
	}

	public static URL initJarFile(String jarFileName) {
		try {
			return new URL("file://" + jarFileName);
		} catch (MalformedURLException e) {
			throw new PersistenceException("Unknown jar file name: " + jarFileName, e);
		}
	}
}
