package com.rjconsultores.ventaboletos.dao.hibernate;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.engine.LoadQueryInfluencers;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.impl.SessionImpl;
import org.hibernate.loader.OuterJoinLoader;
import org.hibernate.loader.criteria.CriteriaLoader;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.rjconsultores.ventaboletos.dao.GenericDAO;

/**
 * 
 * @author gleimar
 */
@SuppressWarnings("unchecked")
public class GenericHibernateDAO<T, ID extends Serializable> extends HibernateDaoSupport implements GenericDAO<T, ID> {
	public long start = System.currentTimeMillis();
	public static final String ACTIVO = "activo";
	
	public GenericHibernateDAO() {
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	// Classe que será persistida.
	private Class<T> persistentClass;

	public Class<T> getPersistentClass() {
		return this.persistentClass;
	}

	@Override
	public void borrar(T entity) {
		try {
			this.getHibernateTemplate().delete(entity);
		} catch (final HibernateException ex) {
			throw convertHibernateAccessException(ex);
		}
	}

	@Override
	public T obtenerID(ID id) {
		try {
			return (T) this.getHibernateTemplate().get(getPersistentClass(), id);
		} catch (final HibernateException ex) {
			throw convertHibernateAccessException(ex);
		}
	}

	@Override
	public List<T> obtenerTodos() {
		try {
			return this.getHibernateTemplate().loadAll(getPersistentClass());
		} catch (final HibernateException ex) {

			throw convertHibernateAccessException(ex);
		}
	}

	@Override
	public T suscribir(T entity) {
		try {
			this.getHibernateTemplate().save(entity);
			return entity;
		} catch (final HibernateException ex) {

			throw convertHibernateAccessException(ex);
		}
	}

	protected List<T> findByCriteria(Criterion... criterion) {
		try {
			Criteria crit = this.getHibernateTemplate().getSessionFactory().getCurrentSession().createCriteria(getPersistentClass());
			for (Criterion c : criterion) {
				crit.add(c);
			}
			return crit.list();
		} catch (final HibernateException ex) {
			throw convertHibernateAccessException(ex);
		}
	}

	protected Criteria makeCriteria() {
		return this.getHibernateTemplate().getSessionFactory().getCurrentSession().createCriteria(getPersistentClass());
	}

	public T actualizacion(T entity) {
		return getHibernateTemplate().merge(entity);
	}

	public Long count(String campo, Object o) {
		Criteria c = this.makeCriteria();

		c.add(Restrictions.eq(campo, o));
		c.add(Restrictions.eq(ACTIVO, Boolean.TRUE));

		c.setProjection(Projections.rowCount());

		return HibernateFix.count(c.list());

	}

	public void suscribirTodos(Collection<T> entidades) {
		getHibernateTemplate().saveOrUpdateAll(entidades);
	}
	
	/*
	 * Metodo que retorna o sql do criteria passado
	 * para uso em debug e auxilio nas analises 
	 * */
	public String getCriteriaSql( Criteria c, Session se) {
		try {
			CriteriaImpl ci = (CriteriaImpl) c;
			SessionImpl s = (SessionImpl) se;
			SessionFactoryImplementor factory = (SessionFactoryImplementor) s.getSessionFactory();
			String[] implementors = factory.getImplementors(ci.getEntityOrClassName());
			LoadQueryInfluencers lqis = new LoadQueryInfluencers();
			CriteriaLoader loader = new CriteriaLoader((OuterJoinLoadable) factory.getEntityPersister(implementors[0]), factory, ci, implementors[0], lqis);			
			Field f = OuterJoinLoader.class.getDeclaredField("sql");
			f.setAccessible(true);
			String sql = (String) f.get(loader);
			return sql;
		} catch (Exception e) {
			return "exception "+e.getMessage();
		} 
	}
	
	/*
	 * Metodo adicionado para retornar em o tempo de execucao
	 * de cada metodo chamado, sendo chamado nas implementacoes
	 * e printado via log.debug, para que possa ser analizado 
	 * qualquer eventual demora na execução
	 * */
	public String printInfo(String method, long start) {
		long milliseconds = System.currentTimeMillis() - start;
		long minutes = (milliseconds / 1000) / 60;
		long seconds = (milliseconds / 1000) % 60;

		String info = String.format("[%s] %d minutos e %d segundos", method, minutes, seconds);
		
		if ((minutes + seconds) == 0) {
			info = String.format("[%s] %d milissegundos", method, milliseconds);
		}
		
		return info;
	}
	
	public static <T> List<T> castList(Class<? extends T> clazz, Collection<?> rawCollection) 
	  throws ClassCastException {
	    List<T> result = new ArrayList<T>(rawCollection.size());
	    for (Object o : rawCollection) {
	        result.add(clazz.cast(o));
	    }
	    return result;
	}
	
}
