package common.dao;

import java.io.BufferedReader;
import java.io.Reader;
import java.io.Serializable;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.jdbc.ReturningWork;
import org.hibernate.resource.transaction.spi.TransactionStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.ParameterizedType;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

@Transactional
public class GenericDAO<T, ID extends Serializable>
		// extends HibernateDaoSupport
		implements IDAO<T, ID> {

	public final static int K_ORDER_ASC = 0;
	public final static int K_ORDER_DESC = 1;
	public final static String K_ELIMINATO = "X";

	protected boolean transactionStartedByMe = false;

	@Autowired
	protected SessionFactory sessionFactory;
	protected Class<T> persistentClass;

	private String LastError = "";

	public Order getOrderBy(String orderByField) {
		if (orderByField.startsWith("-"))
			return Order.desc(orderByField.substring(1));
		else
			return Order.asc(orderByField);
	}

	public String getSQLOrderBy(String orderByField) {
		if (orderByField.startsWith("-"))
			return " ORDER BY " + orderByField.substring(1) + " DESC";
		else
			return " ORDER BY " + orderByField;
	}

	@SuppressWarnings("unchecked")
	@Autowired
	public GenericDAO(SessionFactory sessionFactory) {
		super();
		setFactory(sessionFactory);
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
				.getActualTypeArguments()[0];
	}

	@SuppressWarnings("unchecked")
	public GenericDAO() {
		transactionStartedByMe = false;
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
				.getActualTypeArguments()[0];
	}

	@SuppressWarnings("unchecked")
	public void setFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
				.getActualTypeArguments()[0];
	}

	protected boolean isInteger(String str) {
		try {
			Integer.parseInt(str);
		} catch (NumberFormatException e) {
			return false;
		} catch (NullPointerException e) {
			return false;
		}
		return true;
	}

	/*
	 * @Autowired public GenericDao(SessionFactory sessionFactory){
	 * this.sessionFactory = sessionFactory; this.persistentClass = (Class<T>)
	 * ((ParameterizedType)
	 * getClass().getGenericSuperclass()).getActualTypeArguments()[0]; }
	 */
	public Session currentSession() {
		Session session = sessionFactory.getCurrentSession(); // sessionFactory.openSession();
		// Session session = sessionFactory.openSession();
		/*
		 * ThreadLocalSessionContext.bind(session); if (!session.isOpen())
		 * session.beginTransaction();
		 */
		return session;

		// return getSession();
	}

	public T read(ID id) {
		boolean tx = startTransaction();
		T entity = read(id, false);
		if (tx)
			commit();
		return entity;
	}

	public T read(ID id, boolean forceLoadRelatedData) {
		//boolean tx = startTransaction();
		T entity = (T) sessionFactory.getCurrentSession().get(persistentClass, id);
		if (forceLoadRelatedData)
			Hibernate.initialize(entity);
		//if (tx)
		//	commit();
		return entity;
	}

	public T readNoTransaction(ID id) {
		T entity = (T) sessionFactory.getCurrentSession().get(persistentClass, id);
		return entity;
	}

	public T saveOrUpdate(T entity) throws Exception {
		boolean tx = startTransaction();
		try {
			currentSession().saveOrUpdate(entity);
			if (tx)
				commit();
		} catch (Exception ex) {
			setLastError(ex.getMessage());
			if (tx)
				rollback();
			throw ex;
		}
		return entity;
	}

	public T saveOrUpdateNoTransaction(T entity) throws Exception {
		currentSession().saveOrUpdate(entity);
		return entity;
	}

	public T merge(T entity) throws Exception {
		boolean tx = startTransaction();
		try {
			currentSession().merge(entity);
			if (tx)
				commit();
		} catch (Exception ex) {
			setLastError(ex.getMessage());
			if (tx)
				rollback();
			throw ex;
		}
		return entity;
	}

	public boolean startTransaction() {
		// if (currentSession().getTransaction().getStatus()==TransactionStatus.ACTIVE)
		// {
		// currentSession().getTransaction().rollback();
		// }
		// if (currentSession().getTransaction().getStatus()!=TransactionStatus.ACTIVE)
		// {
		// transactionStartedByMe = true;
		try {
			currentSession().getTransaction().begin();
		} catch (IllegalStateException ex) {
			Logger.getRootLogger().error(ex);
			ex.printStackTrace();
		}
		return true;
		// }
		// else
		// return false;
	}

	public boolean commit() {
		// if (transactionStartedByMe) {
		try {
			// if (currentSession().getTransaction().getStatus()==TransactionStatus.ACTIVE)
			// {
			currentSession().getTransaction().commit();
			transactionStartedByMe = false;
		} catch (IllegalStateException ex) {
			Logger.getRootLogger().error(ex);
			ex.printStackTrace();
		}
		return transactionStartedByMe;
	}

	public boolean rollback() {
		try {
			// if (transactionStartedByMe) {
			currentSession().getTransaction().rollback();
			transactionStartedByMe = false;
		} catch (IllegalStateException ex) {
			Logger.getRootLogger().error(ex);
			ex.printStackTrace();
		}
		return transactionStartedByMe;
	}

	public void delete(T entity) {
		startTransaction();
		currentSession().delete(entity);
		commit();
	}

	public void delete(ID id) {
		startTransaction();
		T entity = readNoTransaction(id);
		if (entity != null)
			currentSession().delete(entity);
		commit();
	}

	public void deleteNoTransaction(T entity) {
		currentSession().delete(entity);
	}

	public void flush() {
		currentSession().flush();
	}

	public List<T> listAll() {
		return listAll(false);
	}

	public List<T> listAll(boolean forceLoadRelatedData) {
		boolean tx = startTransaction();
		try {
			Criteria crit = currentSession().createCriteria(persistentClass, "t");
			@SuppressWarnings("unchecked")
			List<T> res = crit.list();
			if (forceLoadRelatedData)
				for (T elemento : res)
					Hibernate.initialize(elemento);
			return res;
		} finally {
			if (tx)
				commit();
		}
	}

	public List<T> listAll(String orderBy, int ascDesc) {
		boolean tx = startTransaction();
		try {
			Criteria crit = currentSession().createCriteria(persistentClass, "t");
			if (ascDesc == K_ORDER_ASC)
				crit.addOrder(Order.asc(orderBy));
			else
				crit.addOrder(Order.desc(orderBy));
			@SuppressWarnings("unchecked")
			List<T> res = crit.list();
			return res;
		} finally {
			if (tx)
				commit();
		}
	}

	public String getLastError() {
		return LastError;
	}

	public void setLastError(String lastError) {
		LastError = lastError;
	}

	public String ClobToString(Clob cl) {
		if (cl == null)
			return null;
		try {
			Reader stream = cl.getCharacterStream();
			BufferedReader reader = new BufferedReader(stream);
			StringBuffer sb = new StringBuffer();
			String line = null;
			while ((line = reader.readLine()) != null) {
				sb.append(line);
			}
			stream.close();

			if (sb.length() > 0) {
				return sb.toString();
			}

			return "";
		} catch (Exception ex) {
			return "Error retrieving data: " + ex.getMessage();
		}
	}

	protected Clob stringToClob(Connection connection, String str) throws SQLException {
		Clob clob = connection.createClob();
		clob.setString(1, str);
		return clob;
	}

	public int executeSQL(String sql) {
		SQLQuery q = sessionFactory.getCurrentSession().createSQLQuery(sql);
		return q.executeUpdate();
	}

	public Date getCurrentTimestamp() {
		java.util.Date now = new java.util.Date();
		return new java.sql.Timestamp(now.getTime());
	}

	public List<Object> executeGenericQuery(String sql) {
		boolean tx = startTransaction();
		try {
			Query q = currentSession().createSQLQuery(sql);
			List<Object> res = q.list();
			return res;
		} finally {
			if (tx)
				commit();
		}
	}

	public Object executeGenericQuerySingleRow(String sql) {
		boolean tx = startTransaction();
		Query q = currentSession().createSQLQuery(sql);
		@SuppressWarnings("unchecked")
		List<Object> res = q.list();
		if (tx)
			commit();
		if (res.size() > 0)
			return res.get(0);
		else
			return null;
	}

	public boolean isEmpty(String str) {
		return str == null || "".compareTo(str.trim()) == 0;
	}

	public Connection getConnection() {
		Connection connection = currentSession().doReturningWork(new ReturningWork<Connection>() {
			@Override
			public Connection execute(Connection conn) throws SQLException {
				return conn;
			}
		});
		return connection;
	}

	protected java.sql.Date dateToSqlDate(java.util.Date data) {
		if (data == null)
			return null;
		return new java.sql.Date(data.getTime());
	}

	protected java.sql.Timestamp dateToSqlTimestamp(java.util.Date data) {
		if (data == null)
			return null;
		return new java.sql.Timestamp(data.getTime());
	}

	protected boolean getBoolean(Boolean bool) {
		if (bool == null)
			return false;
		return bool;
	}

	protected int safeInt(Integer i) {
		if (i == null)
			return 0;
		else
			return i;
	}

	protected String safeIntToString(Integer i) {
		if (i == null)
			return "0";
		else
			return i.toString();
	}

	public String safeString(String str) {
		if (str == null)
			return "";
		else
			return str.replace("\"", "\"\"");
	}

	public String safeBoolean(Boolean b) {
		if (b == null)
			return "falso";
		else
			return b ? "vero" : "falso";
	}

	public String safeDate(Date date, SimpleDateFormat sdf) {
		if (date == null)
			return "";
		if (sdf == null)
			sdf = new SimpleDateFormat("yyyymmdd");

		return sdf.format(date);
	}

	public String exportCsvField(String fieldValue) {
		return "\"" + safeString(fieldValue) + "\";";
	}

	public String exportCsvField(Integer fieldValue) {
		return safeIntToString(fieldValue) + ";";
	}

	public String exportCsvField(Boolean fieldValue) {
		return safeBoolean(fieldValue) + ";";
	}

	public String exportCsvField(Date fieldValue) {
		return safeDate(fieldValue, null) + ";";
	}

	public String exportCsvField(Date fieldValue, SimpleDateFormat sdf) {
		return safeDate(fieldValue, sdf) + ";";
	}

	public Date startOfDay(Date data) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(data);
		cal.set(Calendar.HOUR, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);

		return cal.getTime();
	}

	public Date endOfDay(Date data) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(data);
		cal.set(Calendar.HOUR, 23);
		cal.set(Calendar.MINUTE, 59);
		cal.set(Calendar.SECOND, 59);
		cal.set(Calendar.MILLISECOND, 0);

		return cal.getTime();
	}
}
