package com.fp.bpm.query;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.NoResultException;

import com.fp.common.logger.APPLogger;
import com.fp.dto.AbstractDataTransport;
import com.fp.dto.query.DtoQuery;
import com.fp.dto.query.Filter;
import com.fp.dto.query.QueryRequest;
import com.fp.dto.query.SubQuery;
import com.fp.persistence.commondb.PersistenceHelper;
import com.fp.persistence.commondb.PersistenceManager;

/**
 * Clase que se encarga de realizar consultas genericas a la base de datos.
 * 
 * @author Jorge Vaca.
 * @version 2.1
 */
public class QueryJsf {
    /** Map con el orden campos a aplicar a la consulta */
    protected final Map<String, Object> mparameter = new HashMap<String, Object>();

    /** Parte de la setencia que tiene la instruccion for de la cosnualta. */
    private StringBuffer sql;

    /** Almacena las restriciones de la consulta. */
    protected StringBuffer where;

    private boolean issubquery = false;

    /**
     * Metodo que se encarga de realizar consulta de tablas que llegan en el request.<br>
     * La cosnsulta se ejecuta por tabla.
     * 
     * @param queryRequest Request de consultya que almacena la lista de tablas a consultar.
     * @throws Exception
     */
    public void execute(QueryRequest queryRequest) throws Exception {
        Map<String, DtoQuery> mtablas = queryRequest.getQueryTables();
        if (mtablas == null) {
            return;
        }
        Set<String> s = mtablas.keySet();
        for (String key : s) {
            // Crea nuevos objetos por cada tabla que se consulta.
            sql = new StringBuffer(46);
            where = new StringBuffer(46);
            mparameter.clear();
            issubquery = false;
            // Ejecuta consulta de la tabla.
            DtoQuery dtoqry = mtablas.get(key);
            if (dtoqry.getBeanname() == null) {
                continue;
            }
            Object resp = queryByBean(dtoqry);
            queryRequest.getResponse().put(key, resp);

        }
    }

    private Object queryByBean(DtoQuery dtoquey) throws Exception {
        buildSentence(dtoquey);
        APPLogger.getLogger().debug(sql);
        return executeByDtoQuery(dtoquey);
    }

    private void buildSentence(DtoQuery dtoquery) throws Exception {
        buildSql(dtoquery);
        buildCriteria(dtoquery);
        sql.append(where);
        if (dtoquery.getOrderby() != null) {
            sql.append(" order by " + dtoquery.getOrderby());
        }
    }

    private Object executeByDtoQuery(DtoQuery dtoquery) throws Exception {
        if (dtoquery.isMultirecord()) {
            List<Object> lresp = queryList(dtoquery);
            if (issubquery) {
                return buildlistresponse(dtoquery, lresp);
            } else {
                return lresp;
            }
        }
        Object resp = queryObject(dtoquery);
        if ((resp != null) && issubquery) {
            return buildobjectresponse(dtoquery, resp);
        }
        return resp;
    }

    private Object buildobjectresponse(DtoQuery dtoquery, Object object) throws Exception {
        List<SubQuery> lsubquery = dtoquery.getlSubquery();
        Object[] obj = (Object[]) object;
        AbstractDataTransport bean = (AbstractDataTransport) obj[0];
        for (int i = 1; i <= lsubquery.size(); i++) {
            SubQuery s = lsubquery.get(i - 1);
            Object valor = obj[i];
            bean.put(s.getAlias(), valor);
        }
        return bean;
    }

    private Object buildlistresponse(DtoQuery dtoquery, List<Object> lresp) throws Exception {
        List<Object> l = new ArrayList<Object>();
        List<SubQuery> lsubquery = dtoquery.getlSubquery();
        for (Object object : lresp) {
            Object[] obj = (Object[]) object;
            AbstractDataTransport bean = (AbstractDataTransport) obj[0];
            for (int i = 1; i <= lsubquery.size(); i++) {
                SubQuery s = lsubquery.get(i - 1);
                Object valor = obj[i];
                bean.put(s.getAlias(), valor);
            }
            l.add(bean);
        }
        return l;
    }

    /**
     * Metodo que se encarga de construir y adicionar campos a consultar de una tabla.
     * 
     * @throws Exception
     */
    protected void buildSql(DtoQuery dtoquery) throws Exception {
        sql.append("select t ");
        List<SubQuery> lsubquery = dtoquery.getlSubquery();
        for (SubQuery subQuery : lsubquery) {
            issubquery = true;
            sql.append("," + subQuery.getSubQuery());
        }
        // Adiciona subqueries.
        sql.append(" from " + dtoquery.getBeanname() + " t");

    }

    protected void buildCriteria(DtoQuery dtoquery) throws Exception {
        List<Filter> lfiltro = dtoquery.getLfilter();
        if ((lfiltro == null) || lfiltro.isEmpty()) {
            return;
        }
        boolean first = true;
        for (Filter filtro : lfiltro) {
            if (filtro.getSql() != null) {
                this.addFiltersql(filtro.getSql(), first);
                continue;
            }
            if (filtro.getValue() == null) {
                continue;
            }
            addFilter(dtoquery.getBeanname(), filtro, first);
            first = false;
        }
    }

    private void addFilter(String bean, Filter filtro, boolean first) throws Exception {
        Object valor = filtro.getValue();
        String campo = filtro.getField();
        String condicion = filtro.getCondition() == null ? "=" : filtro.getCondition();
        valor = PersistenceManager.changeType(bean, campo, valor);
        if (String.valueOf(valor).indexOf("%") >= 0) {
            condicion = "like";
        }
        String aux = campo.substring(campo.indexOf(".") + 1, campo.length());
        String cond = "t." + campo + " " + condicion + " " + ":" + aux;
        mparameter.put(aux, valor);
        if (where == null) {
            where = new StringBuffer(46);
        }
        if (first) {
            where.append(" where " + cond);
        } else {
            where.append(" and " + cond);
        }
    }

    private void addFiltersql(String sql, boolean first) throws Exception {
        if (first) {
            where.append(" where " + sql);
        } else {
            where.append(" and " + sql);
        }
    }

    /**
     * Metodo que entrega una lista de objetos relacionados a la tabla que se consulta.
     * 
     * @return List<Object>
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    private List<Object> queryList(DtoQuery dtoquery) throws Exception {
        javax.persistence.Query query = createQuery(dtoquery);
        return query.getResultList();
    }

    /**
     * Metodo que entrega un objeto, de una tabla relacionado a la consulta.
     * 
     * @return Object.
     * @throws Exception
     */
    private Object queryObject(DtoQuery dtoquery) throws Exception {
        Object obj = null;
        javax.persistence.Query query = createQuery(dtoquery);
        try {
            obj = query.getSingleResult();
        } catch (NoResultException e) {
            // Si no existe datos no hacer nada.
        }
        return obj;
    }

    private javax.persistence.Query createQuery(DtoQuery dtoquery) throws Exception {
        javax.persistence.Query query = PersistenceHelper.getEntityManager().createQuery(sql.toString());
        this.setParameters(dtoquery, query);
        return query;
    }

    /**
     * Fija parametros, primer registro y ultimo registro de comsulta.
     * 
     * @param dtoquery
     * @param qry
     * @throws Exception
     */
    public void setParameters(DtoQuery dtoquery, javax.persistence.Query qry) throws Exception {
        Integer pagina = dtoquery.getPage();
        Integer numreg = dtoquery.getRecperpage() == null ? 10 : dtoquery.getRecperpage();
        // Control de paginacion.
        if (dtoquery.isMultirecord()) {
            qry.setFirstResult(pagina);
            qry.setMaxResults(numreg);
        }
        // Fija parametros a la sentencia
        Set<String> s = mparameter.keySet();
        for (String key : s) {
            Object valor = mparameter.get(key);
            qry.setParameter(key, valor);
        }
    }
}
