package com.fp.hbm.helper;

import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Query;
import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;

import com.fp.common.helper.BeanManager;
import org.apache.log4j.Logger;
import com.fp.persistence.commondb.exception.CommondbException;
import com.fp.common.logger.APPLogger;


/**
 * Clase utilitaria para armar sentencias y obtener resultados a consultas a  la base de datos con Hibernate.
 * @version 2.1
 */
public class HqlStatementHQL {


	/**
	 * Map auxiliar empleado para el manejo de Parametros
	 */
	private Map<String,Object> map;
  /**
   * Sentencia HQL a ejecutar
   */
	private String sentence;
  /**
   * Session de hibernate empleada en la consulta
   */
	private Session s;
  /**
   * Indicador de si la consulta requiere manejar Cache
   */
	private boolean cacheAbled=false;
  /**
   * Indicador de si la consulta es de Lectura solamente
   */
	private boolean readonly=false;
  /**
   * Tipo de Bloqueo pesimista que debe aplicarse a la consulta
   */
	private LockMode lockMode;
  /**
   * Alias de la variable a bloquear
   */
	private String alias;
  /**
   * P�gina en la que se encuentra la consulta
   */
  private Integer page = null;
  /**
   * N�mero de registros por p�gina
   */
  private Integer recordperpage = null;
  /**
   * Referencia al Log
   */
  private Logger log = APPLogger.getLogger();

	/**
   * Entrega el valor de page.
   * @return page.
   */
  public Integer getPage() {
    return page;
  }

  /**
   * Fija el valor de page.
   * @param pPage.
   */
  public void setPage(Integer pPage) {
    page = pPage;
  }

  /**
   * Entrega el valor de recordperpage.
   * @return recordperpage.
   */
  public Integer getRecordperpage() {
    return recordperpage;
  }

  /**
   * Fija el valor de recordperpage.
   * @param pRecordperpage.
   */
  public void setRecordperpage(Integer pRecordperpage) {
    recordperpage = pRecordperpage;
  }

  /**Crea una nueva instancia de UtilHB
   * @throws CommonException
	 *
	 */
	public HqlStatementHQL() throws CommondbException {
		this(null,null,null);
   }

	/**
	 * Crea una nueva instancia con una sentencia a ejecutar.
	 * @param pSentence
	 * @throws CommonException
	 * @throws Exception
	 */
	public HqlStatementHQL(String pSentence) throws CommondbException {
		this(pSentence,null,null);
	 }
  /**
   * Crea una nueva instancia con una sentencia a ejecutar.
   * @param pSentence Sentencia a ejecutar
   * @param pLockMode Tipo de Bloqueo Pesismista
   * @param pAlias Variable a bloquear
   * @throws CommonException
   * @throws Exception
   */
	public HqlStatementHQL(String pSentence,LockMode pLockMode,String pAlias) throws CommondbException {
		this.sentence = pSentence;
		this.map =  new HashMap<String,Object>();
		this.s = PersistenceHelperHQL.getSession();
		this.lockMode=pLockMode;
		this.alias=pAlias;
	 }

	/**
	 * Fija un criterio string de la sentencia. El nombre del parametro debe ser igual al del where de la sentencia.
	 * @param pParameter
	 * @param pString
	 */
	public void setString(String pParameter,String pString){
		map.put(pParameter,pString);
	}

	/**
	 * Fija un criterio Data a la sentencia. El nombre del parametro debe ser igual al del where de la sentencia.
	 * @param pParameter
	 * @param pDate
	 */
	public void setDate(String pParameter,Date pDate){
	  map.put(pParameter,pDate);
	}

  /**
	 * Fija un criterio Timestamp a la sentencia. El nombre del parametro debe ser igual al del where de la sentencia.
	 * @param pParameter
	 * @param pTimestamp
	 */
	public void setTimestamp(String pParameter,Timestamp pTimestamp){
		map.put(pParameter,pTimestamp);
	}

	/**
	 * Fija un criterio Integer a la sentencia. El nombre del parametro debe ser igual al del where de la sentencia.
	 * @param pParameter
	 * @param pInteger
	 */
	public void setInteger(String pParameter,Integer pInteger){
		if(pInteger == null){
			map.put(pParameter,Hibernate.INTEGER);
		}else{
			map.put(pParameter,pInteger);
		}
	}

	/**
	 * Fija un criterio BigDecimal a la sentencia. El nombre del parametro debe ser igual al del where de la sentencia.
	 * @param pParameter
	 * @param pBigdecimal
	 */
	public void setBigDecimal(String pParameter,BigDecimal pBigdecimal){
		if(pBigdecimal == null){
			map.put(pParameter,Hibernate.BIG_DECIMAL);
		}else{
			map.put(pParameter,pBigdecimal);
		}
	}

  /**
   * Setea valores de parametros definidos previamente en una sentencia.
   * @param pMparamters   Map con el nombre del parametro y el valor del mismo.
   * @throws Exception
   */
  @SuppressWarnings("unchecked")
public void setParametersValue(Map<String,Object> pMparamters) {
    Iterator itr = pMparamters.keySet().iterator();
    while(itr.hasNext()){
      String key = (String)itr.next();
      Object value = pMparamters.get(key);
      log.debug("parametro "+key+" valor "+value+" "+value.getClass());
      if(value instanceof String ){
        this.setString(key,(String)value);
      }
      if(value instanceof Integer ){
        this.setInteger(key,(Integer)value);
      }
      if(value instanceof BigDecimal ){
        this.setBigDecimal(key,(BigDecimal)value);
      }
      if(value instanceof Date ){
        this.setDate(key,(Date)value);
      }
      if(value instanceof Timestamp ){
        this.setTimestamp(key,(Timestamp)value);
      }

    }
  }


	/**
	 * Retorna: un entity si la sentencia retorna un entity, si es un conjunto de campos de una o mas tablas retorna un Array.
	 * <ol>
	 * <li>Objeto que contiene una entidad si la sentencia retorna un entity <br>
	 * 	sentencia ==>     from com.test.Test as t where t.pk.c1 = :valor1
	 * </li>
	 * <li>Array que contiene una entidad si la sentencia retorna mas de un campo <br>
	 *  sentencia ==>   select t.c1,t.c2...t.c3  from com.test.Test as t
	 * </li>
	 * <li>Array que contiene una entidades o campos cuando existe junturas.
	 *      sentencia ==>  from com.test.Test a,from com.test.Testuno  b where a.pk=b.pk and b.pk.lenguage = 'ES'
	 * </li>
	 * </ol>
	 * @return
	 * @throws CommonException
	 * @throws HibernateException
	 * @throws ParseException
	 * @throws Exception
	 */
  public  Object getObject() throws HibernateException, CommondbException, ParseException,SQLException {
  	 return this.execute().uniqueResult();
   }

	/**
	 * Retorna un entity  dada la clave primaria.
	 * @param pEntity
	 * @param key
	 * @return
	 * @throws Exception
	 */
  @SuppressWarnings("unchecked")
public  Object getObjectByKey(Class pEntity, Serializable key) {
		Object obj=(lockMode==null)?s.get(pEntity,key):s.get(pEntity,key,lockMode);
	if(!this.cacheAbled){
	 if(lockMode==null){
     if(obj!=null){
		   s.refresh(obj);
     }
	 }else{
	   s.refresh(obj,lockMode);
	 }
	}
   return obj;

  }

	/**
	 * Retorna una lista de entidades o arreglos.
	 * @return
	 * @throws CommonException
	 * @throws ParseException
	 * @throws HibernateException
	 * @throws Exception
	 */
  @SuppressWarnings("unchecked")
public  List getList() throws CommondbException, HibernateException, ParseException {
  	List result = this.execute().list();
  	if(result == null || result.size() == 0){
  		throw new CommondbException("COMMONDB-0006","CONSULTA NO TIENE REGISTROS");
  	}
  	return result;
   }

  /**
	 * Ejecuta una sentencia.
	 * @return
   * @throws CommonException
 * @throws ParseException
	 * @throws Exception
	 */
  private Query execute() throws CommondbException, ParseException {
		if(sentence == null){
		  throw new CommondbException("COMMONDB-0007","SENTENCIA INVALIDA");
		}
  	 Query qry=s.createQuery(sentence);
		 qry.setCacheable(this.cacheAbled);


		 String[]param=qry.getNamedParameters();
	  	 for (String parameter : param) {
				Object obj = map.get(parameter);
				if(obj == null ){
					throw new CommondbException("COMMONDB-0008","VALOR NO ENVIADO  {0}",parameter);
				}else {
					char dataType=(parameter.indexOf('_')>-1)?parameter.charAt(0):' ';
					switch (dataType) {
					case 'i':
						qry.setInteger(parameter,(Integer)BeanManager.convertObject(obj, Integer.class, false));
						break;
					case 'd':
						qry.setDate(parameter,(Date)BeanManager.convertObject(obj, Date.class, false));
						break;
					case 't':
						qry.setTimestamp(parameter,(Timestamp)BeanManager.convertObject(obj, Timestamp.class, false));
						break;
					default:
						qry.setParameter(parameter, obj);
						break;
					}
				}
	  	 }
		 qry.setReadOnly(this.readonly);
		 if(this.lockMode!=null){

		   qry.setLockMode(this.alias,this.lockMode);
		 }
     if(page != null && recordperpage != null && page > 0 &&  recordperpage > 0){
       if(page > 1){
         qry.setFirstResult((page-1)*recordperpage);
       }
       qry.setMaxResults(recordperpage);
     }
  	 return qry;
   }

	/**
	 * Retorna un resultset sobre el cual se puede iterar.
	 * @return
	 * @throws CommonException
	 * @throws HibernateException
	 * @throws ParseException
	 * @throws Exception
	 */
  public ScrollableResults getScroll() throws HibernateException, CommondbException, ParseException {
  	 return this.execute().scroll();
   }

  /**
	 * Retorna un resultset sobre el cual se puede iterar.
	 * @param pMode   Modo permitidos para la iteracion:  FORWARD_ONLY, SCROLL_SENSITIVE, SCROLL_INSENSITIVE
	 * @return
   * @throws CommonException
   * @throws HibernateException
 * @throws ParseException
	 * @throws Exception
	 */
  public ScrollableResults getScroll(ScrollMode pMode) throws HibernateException, CommondbException, ParseException {
  	 return this.execute().scroll(pMode);
   }

  /**
	 * Fija una sentencia  a ejecutar.
	 * @param pSentence
	 */
	public void setSentence(String pSentence) {
		this.map.clear();
		this.sentence = pSentence;
	}

  /**
	 * Entrega la sentencia a ejecutar.
	 * @return
	 */
	public String getSentence() {
		return sentence;
	}

  /**
	 * Marca la setencia para cache.
	 * @param cacheAbled
	 */
	public void setCacheAbled(boolean cacheAbled) {
		this.cacheAbled = cacheAbled;
	}

  /**
	 * Indica  si la sentencia esta marcada para cache.
	 * @return
	 */
	public boolean isCacheAbled() {
		return cacheAbled;
	}

  /**
	 * Fija la sentencia en readonly.
	 * @param readonly
	 */
	public void setReadonly(boolean readonly) {
		this.readonly = readonly;
	}

	/**
	 * Indica si la sentencia a ejecutar es solo de lectura.
	 * @return
	 */
	public boolean isReadonly() {
		return readonly;
	}

}
