package com.fp.persistence.commondb;

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.Map;
import java.util.Set;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Parameter;
import javax.persistence.Query;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;

import com.fp.common.exception.CommonException;
import com.fp.common.logger.APPLogger;
import com.fp.dto.hb.Log;
import com.fp.persistence.commondb.exception.CommondbException;

/**
 * @author jorge
 * 
 */
public class HqlStatement {

    /**
     * Map auxiliar empleado para el manejo de Parametros
     */
    private Map<String, Object> map;

    /**
     * Sentencia HQL a ejecutar
     */
    private String sentence;

    /**
     * EntityManager de hibernate empleada en la consulta
     */
    private EntityManager em;

    /**
     * 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;

    /**
     * Pagina en la que se encuentra la consulta
     */
    private Integer page = null;

    /**
     * Numero de registros por pagina
     */
    private Integer recordperpage = null;

    private Integer i = null;

    /**
     * Referencia al Log
     */
    private Logger log = APPLogger.getLogger();

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

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

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

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

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

    /**
     * Crea una nueva instancia con una sentencia a ejecutar.
     * 
     * @param pSentence Sentencia a ejecutar
     * @param pLockMode Tipo de Bloqueo Pesismista
     * @param pLocktime Tiempo de espera para un bloqueo pesimista.
     * @throws CommonException
     * @throws Exception
     */
    public HqlStatement(String pSentence) throws CommondbException {
        this.sentence = pSentence;
        this.map = new HashMap<String, Object>();
        this.em = PersistenceHelper.getEntityManager();
    }

    /**
     * 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) {
        this.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) {
        this.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) {
        this.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) {
            this.map.put(pParameter, Integer.class);
        } else {
            this.map.put(pParameter, pInteger);
        }
    }

    
    /**
     * Fija un criterio Long a la sentencia. El nombre del parametro debe ser igual al del where de la sentencia.
     * 
     * @param pParameter
     * @param pLong
     */
    public void setLong(String pParameter, Long pLong) {
        if (pLong == null) {
            this.map.put(pParameter, Long.class);
        } else {
            this.map.put(pParameter, pLong);
        }
    }

    /**
     * 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) {
            this.map.put(pParameter, BigDecimal.class);
        } else {
            this.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
     */
    public void setParametersValue(Map<String, Object> pMparamters) {
        Iterator<String> itr = pMparamters.keySet().iterator();
        while (itr.hasNext()) {
            String key = itr.next();
            Object value = pMparamters.get(key);
            if (value instanceof String) {
                this.setString(key, (String) value);
            }
            if (value instanceof Integer) {
                this.setInteger(key, (Integer) value);
            }
            if (value instanceof Long) {
                this.setLong(key, (Long) 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, Exception {
        Object obj = null;
        try {
            obj = this.execute().getSingleResult();
        } catch (NoResultException e) {
            return obj;
        }
        if (this.readonly) {
            this.em.detach(obj);
        }
        return obj;
    }

    @SuppressWarnings("unchecked")
    public <T> T getObject(@SuppressWarnings("unused")
    Class<T> pType) throws HibernateException, CommondbException, ParseException, SQLException, Exception {
        T obj = null;
        try {
            obj = (T) this.execute().getSingleResult();
        } catch (NoResultException e) {
            return obj;
        }
        if (this.readonly) {
            this.em.detach(obj);
        }
        return obj;
    }

    /**
     * Ejecuta una sentencia.
     * 
     * @return
     * @throws CommonException
     * @throws ParseException
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    public Query execute() throws CommondbException, ParseException, Exception {
        if (this.sentence == null) {
            throw new CommondbException("COMMONDB-0007", "SENTENCIA INVALIDA");
        }
        // Query qry=(QueryImpl)em.createQuery(sentence);
        APPLogger.getLogger().debug("Sentencia ==> " + this.sentence);
        Query qry = this.em.createQuery(this.sentence);
        Set<Parameter<?>> param = qry.getParameters();
        // String[]param=qry.getNamedParameters();
        for (Parameter par : param) {
            String parameter = par.getName();
            Object obj = this.map.get(parameter);
            if (obj == null) {
                throw new CommondbException("COMMONDB-0008", "VALOR NO ENVIADO  {0}", parameter);
            }
            qry.setParameter(parameter, obj);
        }
        if ((this.page != null) && (this.recordperpage != null) && (this.page >= 0) && (this.recordperpage > 0)) {
            if (this.page > 1) {
                qry.setFirstResult((this.page - 1) * this.recordperpage);
            }
            qry.setMaxResults(this.recordperpage);
        }
        return qry;
    }

    /**
     * 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 this.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 this.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 this.readonly;
    }

    /**
     * Entrega el valor de: i
     * 
     * @return Integer
     */
    public Integer getI() {
        return this.i;
    }

    /**
     * Fija el valor de: i
     * 
     * @param i value to set
     */
    public void setI(Integer i) {
        this.i = i;
    }

}
