package +zoccolo+.+rail+;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.Query;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Transactional;

import +zoccolo+.jpa.Filter;
import +zoccolo+.jpa.Order;
import +zoccolo+.jpa.OrderBuilder.OrderType;
import +zoccolo+.jpa.PaginatedFilter;
import +zoccolo+.jpa.SearchParameter;
import +zoccolo+.jpa.dao.GenericDao;

/**
 * Implementation of the interface for dao management.<br>
  * <b>IMPORTANT</b> - Reference to the persistence context JPA is set in this implementation.
 *
 * @param <T>
 */
public abstract class Generic+Rail+DaoImpl<T> implements GenericDao<T> {
	
	public static final Logger logger = LogManager.getLogger(Generic+Rail+DaoImpl.class);
	
	@PersistenceContext(unitName="+rail+EntityManagerFactory")
	protected EntityManager em;
	
	protected String generateDebugParameters(List<SearchParameter<String>> parameters) {
		if (parameters == null || parameters.size() == 0) {
			return "[]";
		}
		StringBuffer paramsDebug = new StringBuffer("[");
		for (SearchParameter<String> parameter : parameters) {
			paramsDebug.append(" ").append(parameter.getName()).append("=").append(parameter.getValue());
		}
		paramsDebug.append("]");
		
		return paramsDebug.toString(); 
	}
	
	protected List<javax.persistence.criteria.Order> createOrderClauseList(CriteriaBuilder cb, Root<T> root, Order[] order) {
		List<javax.persistence.criteria.Order> orderList = new ArrayList<javax.persistence.criteria.Order>();
		if (order == null) return orderList;
		
		for (Order ord : order) {
			if (OrderType.ASC.toString().equals(ord.getAsc())) {
				orderList.add(cb.asc(root.get(ord.getOrder())));
			} else {
				orderList.add(cb.desc(root.get(ord.getOrder())));
			}
		}
		return orderList;
	}

	@Transactional(rollbackFor=PersistenceException.class)
	public T create(T entity) {
		if (logger.isDebugEnabled()) logger.debug("Create entity: " + entity);
		em.persist(entity);
		em.flush();
		return entity;
	}

	@Transactional(rollbackFor=PersistenceException.class)
	public T update(T entity) {
		// Check if instance is detached
		if (!em.contains(entity)) {
			if (logger.isDebugEnabled()) logger.debug("Update entity: " + entity);
			entity = em.merge(entity);
		}
		em.flush();
		return entity;
	}
	
	@Transactional(rollbackFor=PersistenceException.class)
	public void remove(T entity) {
		if (logger.isDebugEnabled()) logger.debug("Remove entity: " + entity);
		// check if instance is detached
		if (!em.contains(entity)) {
			entity = em.merge(entity);
		}
		em.remove(entity);
		em.flush();
	}

	public T findUniqueById(Class<T> entityClass, Integer id) {
		if (logger.isDebugEnabled()) logger.debug("findUniqueById(" + entityClass.getName() + ", " + id + ")");
		T result = em.find(entityClass, id);
		return result;
	}

	public T findUniqueById(Class<T> entityClass, Long id) {
		if (logger.isDebugEnabled()) logger.debug("findUniqueById(" + entityClass.getName() + ", " + id + ")");
		T result = em.find(entityClass, id);
		return result;
	}
	
	public T findUniqueById(Class<T> entityClass, String id) {
		if (logger.isDebugEnabled()) logger.debug("findUniqueById(" + entityClass.getName() + ", '" + id + "')");
		T result = em.find(entityClass, id);
		return result;
	}
	
	public Collection<T> findListBy(Class<T> entityClass, String fieldName,
			Class<?> fieldClass, Object fieldValue) {
		
		CriteriaBuilder cb = em.getCriteriaBuilder();
		
		CriteriaQuery<T> cq = cb.createQuery(entityClass);
		Root<T> root = cq.from(entityClass);
		
		Predicate predicate = cb.equal(
				root.get(fieldName).as(fieldClass), 
				fieldValue);
		
		cq.where(predicate);
		
		TypedQuery<T> tq = em.createQuery(cq);
		
		if (logger.isDebugEnabled()) logger.debug("Start of findListBy(" + entityClass + ","
					+ " '" + fieldName + "'" + " " + fieldClass + "" + " '" + fieldValue + "')");
		Collection<T> results = tq.getResultList();
		
		return results;
	}
	
	public List<T> like(Class<T> fieldClazz, String fieldName,
			Class<String> valueClazz, String fieldValue) {

		CriteriaBuilder cb = em.getCriteriaBuilder();

		CriteriaQuery<T> cq = cb.createQuery(fieldClazz);
		Root<T> root = cq.from(fieldClazz);

		
		Predicate predicate = cb.like(
				(Expression<String>)cb.upper( root.get(fieldName).as(valueClazz)), "%"+
						fieldValue.toUpperCase()+"%");
		
		cq.where(predicate);
		
		TypedQuery<T> tq = em.createQuery(cq);

		List<T> results = tq.getResultList();
		
		return results;
	}
	
	public Collection<T> findAll(Class<T> entityClass, Order ... order) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		
		CriteriaQuery<T> cq = cb.createQuery(entityClass);
		Root<T> root = cq.from(entityClass);
		
		cq.orderBy(createOrderClauseList(cb, root, order));
		
		TypedQuery<T> tq = em.createQuery(cq);
		
		if (logger.isDebugEnabled()) logger.debug("Start of findAll(" + entityClass + ") method");
		Collection<T> result = tq.getResultList();
		
		return result;
	}
	
	public Boolean exists(Class<T> entityClass, Integer id) {
		if (logger.isDebugEnabled()) logger.debug("Start of exists(" + entityClass + ", " + id + ") method");
		T result = em.find(entityClass, id);
		return result == null ? false : true;
	}
	
	public Boolean exists(Class<T> entityClass, Long id) {
		if (logger.isDebugEnabled()) logger.debug("Start of exists(" + entityClass + ", " + id + ") method");
		T result = em.find(entityClass, id);
		return result == null ? false : true;
	}
	
	public Boolean exists(Class<T> entityClass, String id) {
		if (logger.isDebugEnabled()) logger.debug("Start of exists(" + entityClass + ", " + id + ") method");
		T result = em.find(entityClass, id);
		return result == null ? false : true;
	}
	
	/**
	 * Metodo da implementare per effettuare un'inizializzazione di una entity
	 * con caricamenti di dati lazy. Per default restituisce la entity
	 * inalterata.
	 * 
	 * la <code>HibernateUtils.initialize()</code>.
	 * 
	 * @param entity
	 * @return
	 */
	protected T load(T entity) {
		return entity;
	}

	protected void setPagination(TypedQuery<Tuple> tq, Filter filter) {
		if (filter instanceof PaginatedFilter) {
			PaginatedFilter pfilter = (PaginatedFilter) filter;

			int firstResult = pfilter.getFirstResult();
			int pageSize = pfilter.getPageSize();
			
			if (logger.isTraceEnabled()) logger.trace("Range (" + firstResult + ", " + pageSize + ")");
			tq.setFirstResult(firstResult);
			tq.setMaxResults(pageSize);
		}
	}

	public Long count(Class<T> entityClass) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Long> cq = cb.createQuery(Long.class);
		Root<T> root = cq.from(entityClass);
		cq.select(cb.count(root));

		TypedQuery<Long> tq = em.createQuery(cq);
		Long result = tq.getSingleResult();
		return result;
	}

	public Long count(Class<T> entityClass, Filter filter) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Long> cq = cb.createQuery(Long.class);
		Root<T> root = cq.from(entityClass);

		List<Predicate> predicates = new ArrayList<Predicate>();
		if (!filter.getParams().isEmpty()) {
			predicates.add(getLikePredicate(cb, root, filter.getParams()));
		}
		if (predicates.size() > 0) {
			cq.where(cb.and(predicates.toArray(new Predicate[] {})));
		}

		cq.select(cb.count(root));

		TypedQuery<Long> tq = em.createQuery(cq);
		Long result = tq.getSingleResult();
		return result;
	}

	
	@SuppressWarnings("unchecked")
	public List<T> fetch(Class<T> entityClass, Filter filter, Order... order) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Tuple> cq = cb.createTupleQuery();
		Root<T> root = cq.from(entityClass);

		List<Predicate> predicates = new ArrayList<Predicate>();
		if (!filter.getParams().isEmpty()) {
			predicates.add(getLikePredicate(cb, root, filter.getParams()));
		}
		if (predicates.size() > 0) {
			cq.where(cb.and(predicates.toArray(new Predicate[] {})));
		}

		cq.select(cb.tuple(root));

		cq.distinct(true);

		cq.orderBy(createOrderClauseList(cb, root, order));

		TypedQuery<Tuple> tq = em.createQuery(cq);

		setPagination(tq, filter);
		List<Tuple> results = tq.getResultList();
		List<T> loadedResults = new ArrayList<T>();
		for (Tuple result : results) {
			loadedResults.add(load((T) result.get(0)));
		}
		return loadedResults;
	}

	
	public boolean existsByFilename(Class<T> entityClass, SearchParameter<String> fileName) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Long> cq = cb.createQuery(Long.class);
		Root<T> root = cq.from(entityClass);

		Predicate p = cb.equal(root.get(fileName.getName()).as(String.class), fileName.getValue());
		cq.where(p);

		cq.select(cb.count(root));

		TypedQuery<Long> tq = em.createQuery(cq);
		Long result = tq.getSingleResult();
		return result > 0;
	}

	private Predicate getLikePredicate(CriteriaBuilder cb, Root<T> root, List<SearchParameter<String>> parameters) {
		List<Predicate> orPredicates = new ArrayList<Predicate>();

		for (SearchParameter<String> parameter : parameters) {
			orPredicates.add(cb.like(cb.upper(root.get(parameter.getName()).as(String.class)), "%" + parameter.getValue().toUpperCase() + "%"));
		}

		return cb.or(orPredicates.toArray(new Predicate[] {}));
	}

	@SuppressWarnings("unchecked")
	
	public List<Object> oneColumnQuery(final String string) {
		Query qu = em.createQuery(string);
		List<Object> sE = qu.getResultList();
		return sE;
	}
	
	public List<Object[]> manyColumnQuery(final String string) {
		Query qu = em.createQuery(string);
		List<Object[]> sE = qu.getResultList();
		return sE;
	}
	
	public Object findByPrimary(Class<T> entityClass, Object chiave) {
		Object x = em.find(entityClass, chiave);
		return x;
	}
	/* screwdriver_knife */
}
