package com.fp.general.rules.save;

import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Query;

import com.fp.common.helper.CalculationBase;
import com.fp.common.helper.Constant;
import com.fp.dto.rules.TransactionRule;
import com.fp.dto.save.SaveRequest;
import com.fp.general.exception.GeneralException;
import com.fp.persistence.commondb.PersistenceHelper;
import com.fp.persistence.commondb.db.DataHelper;
import com.fp.persistence.commondb.helper.APPDates;
import com.fp.persistence.pgeneral.date.TgeneAccountingDate;
import com.fp.persistence.pgeneral.date.TgeneAccountingDateBranch;
import com.fp.persistence.pgeneral.date.TgeneAccountingDateKey;
import com.fp.persistence.pgeneral.date.TgeneNotAccountingDay;
import com.fp.persistence.pgeneral.gene.TgeneBranch;
import com.fp.persistence.pgeneral.gene.TgeneBranchKey;

/**
 * Clase que se encarga de generar las fechas contables para un tiempo
 * determinado
 *
 * @author scastillo
 * @version 2.1
 */
public class GenerateAccountingDate extends TransactionRule {

    /**
     * Map que contiene los datos del request de los dias contables y no
     * contables
     */
    private Map<Integer, Map<String, Boolean>> mdays = new HashMap<Integer, Map<String, Boolean>>();
    /**
     * Lista de dias no contables por mes.
     */
    private List<TgeneNotAccountingDay> lnotaccountingdays;
    /**
     * Fecha de inicio de generacion del calendario.
     */
    private Date initdatedate;
    /**
     * Fecha de finalizacion de genercion del calendario.
     */
    private Date enddate;

    /**
     * Metodo que genra las fechas contables
     *
     * @param saveRequest Datos del request original.
     * @return SaveRequest
     * @throws Exception
     */
    public SaveRequest normalProcess(SaveRequest saveRequest) throws Exception {
        this.fillMapDay(saveRequest);
        lnotaccountingdays = TgeneNotAccountingDay.findAll(PersistenceHelper.getEntityManager());
        enddate = (Date) saveRequest.get("enddate");
        this.processBranches(saveRequest);
        return saveRequest;
    }

    /**
     * Metodo que genera fechas contables para la sucursal oficina de proceso.
     *
     * @param saveRequest Datos del request original.
     * @throws Exception
     */
    private void processBranches(SaveRequest saveRequest) throws Exception {
        Integer init = Integer.parseInt(saveRequest.get("branchcodemin").toString());
        Integer end = Integer.parseInt(saveRequest.get("branchcodemax").toString());
        while (init <= end) {
            TgeneBranchKey tgeneBranchKey = new TgeneBranchKey(init, saveRequest.getCompany());
            TgeneBranch tgeneBranch = TgeneBranch.find(PersistenceHelper.getEntityManager(), tgeneBranchKey);
            if (tgeneBranch != null) {
                //Obtiene la fecha de inicio por cada sucursal
                initdatedate = (Date) saveRequest.get("initdate");
                this.delete(init, saveRequest.getCompany());
                this.validateDatesByBranch(init, saveRequest.getCompany());
                this.processByDate(saveRequest, init);
            }
            init++;
        }
    }

    private void processByDate(SaveRequest saveRequest, Integer pBranch) throws Exception {
        APPDates d = new APPDates(initdatedate, CalculationBase.B365365);
        while (initdatedate.compareTo(enddate) <= 0) {
            this.generateCalendar(saveRequest, pBranch, d);
            d.addField(Calendar.DAY_OF_YEAR, 1);
            initdatedate = d.getDate();
        }
    }

    /**
     *
     * @param saveRequest Datos del request original.
     * @param pBranch Codigo de sucursal a generar la fecha contable.
     * @param d Fecha real con la cual se obtiene la fecha de trabajo y la
     * contable.
     * @throws Exception
     */
    private void generateCalendar(SaveRequest saveRequest, Integer pBranch, APPDates d) throws Exception {
        Map<String, Boolean> m = this.mdays.get(d.getField(Calendar.DAY_OF_WEEK));
        TgeneAccountingDateKey key = new TgeneAccountingDateKey();
        key.setCompanycode(saveRequest.getCompany());
        key.setBranchcode(pBranch);
        key.setRealdate(d.getDate());
        TgeneAccountingDate obj = new TgeneAccountingDate();
        obj.setPk(key);
        obj.setAccountingdate(this.getDate(d, "accounting", m));
        obj.setWorkingdate(this.getDate(d, "working", m));
        PersistenceHelper.save(obj);
    }

    /**
     * Entrega la fecha contable para una fecha real.
     *
     * @param d Fecha real con la cual se obtien la fecha de trabajo y la fecha
     * contable.
     * @param fieldname Nombre del campo, working o accounting
     * @param m Map que contien indica si la fecha es laborable y/o contable.
     * @return Date
     * @throws Exception
     */
    private Date getDate(APPDates d, String fieldname, Map<String, Boolean> m) throws Exception {
        if (m.get(fieldname)) {
            if (fieldname.compareTo("working") == 0) {
                return d.getDate();
            } else if (this.validateDayOfTheMonth(d)) {
                //Si es fecha contable verifica que el dia dentro del mes sea contable.
                return d.getDate();
            }
        }
        APPDates daux = new APPDates(d.getDate(), CalculationBase.B365365);
        int day = d.getField(Calendar.DAY_OF_WEEK);
        boolean cont = true;
        while (cont) {
            day++;
            if (day == 8) {
                day = 1;
            }
            daux.addField(Calendar.DAY_OF_YEAR, 1);
            if (fieldname.compareTo("accounting") == 0 && !this.validateDayOfTheMonth(daux)) {
                //Si el dia es no contable continua
                continue;
            }
            if (this.mdays.get(day).get(fieldname)) {
                cont = false;
            }
        }
        return daux.getDate();
    }

    /**
     * Valida si el dia es contable dentro del mes, ejemplo 1 de enero no es
     * contable.
     *
     * @param d Fecha verificar si el dia es contable.
     * @return boolean
     * @throws Exception
     */
    private boolean validateDayOfTheMonth(APPDates d) throws Exception {
        boolean isaccounting = true;
        String mes = String.valueOf(d.getField(Calendar.MONTH));
        if (mes.length() == 1) {
            mes = "0" + mes;
        }
        for (TgeneNotAccountingDay obj : lnotaccountingdays) {
            if (obj.getPk().getCatalog().compareTo(mes) != 0) {
                continue;
            }
            int day = d.getField(Calendar.DAY_OF_MONTH);
            if (obj.getPk().getDay().compareTo(day) == 0) {
                isaccounting = false;
                break;
            }
        }
        return isaccounting;
    }

    @SuppressWarnings("unchecked")
    private void fillMapDay(SaveRequest saveRequest) throws Exception {
        List<Object> lobj = saveRequest.getSaveBeanModifiedRecords("LISTACCOUNTINDATE");
        if(lobj == null || lobj.isEmpty()){
        	lobj = (List<Object>)saveRequest.get("LISTACCOUNTINDATE");
        }
        boolean isworking = false;
        boolean isaccounting = false;
        
        for (Object object : lobj) {
            Map<String, Object> m = (Map<String, Object>) object;
            Integer day = Integer.parseInt(m.get("day").toString());
            String working = m.get("teller").toString();
            String accounting = m.get("accounting").toString();
            Map<String, Boolean> maux = new HashMap<String, Boolean>();
            maux.put("working", Constant.ifYes(working));
            maux.put("accounting", Constant.ifYes(accounting));
            mdays.put(day, maux);
            if(!isworking){
            	isworking = Constant.ifYes(working);
            }
            if(!isaccounting){
            	isaccounting = Constant.ifYes(accounting);
            }            
        }
        if(!isaccounting || !isworking){
        	throw new GeneralException("GENE-0052", "INGRESE AL MENOS UN DÍA CONATBLE Y DE TRABAJO PARA GENERAR EL CALENDARIO");
        }
    }
    
    /**
     * Sentencia que se encarga de eliminar el calendario de fechas por sucursal
     */
    private static String DEL = "delete from TgeneAccountingDate t "
            + " where t.pk.branchcode = :branch "
            + " and t.pk.companycode = :company "
            + " and t.pk.realdate >= :datefrom ";

    /**
     * Elimna fechas del calendario por sucursal y mayor a la fecha a regenerar
     * el calendario.
     *
     * @param pBracnch Codigo de sucursal.
     * @param pCompany Codigo de compania.
     * @throws Exception
     */
    private void delete(Integer pBracnch, Integer pCompany) throws Exception {
        Query qry = PersistenceHelper.getEntityManager().createQuery(DEL);
        qry.setParameter("branch", pBracnch);
        qry.setParameter("company", pCompany);
        qry.setParameter("datefrom", this.initdatedate);
        qry.executeUpdate();
    }
    /**
     * Sentencia que se encarga de eliminar el calendario de fechas por sucursal
     */
    private static String SQL_VAL = "select max(t.pk.realdate) from TgeneAccountingDate t "
            + " where t.pk.branchcode = :branch "
            + " and t.pk.companycode = :company "
            + " and t.pk.realdate < :datefrom ";

    /**
     * Valida que en la generacion de del calendario no exista huecos de fechas
     * realres.
     *
     * @param pBracnch Codigo de sucursal.
     * @param pCompany Codigo de compania.
     * @throws Exception
     */
    private void validateDatesByBranch(Integer pBracnch, Integer pCompany) throws Exception {
        Query qry = PersistenceHelper.getEntityManager().createQuery(SQL_VAL);
        qry.setParameter("branch", pBracnch);
        qry.setParameter("company", pCompany);
        qry.setParameter("datefrom", this.initdatedate);
        Date d = (Date) qry.getSingleResult();
        if (d == null) {
            return;
        }
        APPDates appd = new APPDates(d, CalculationBase.B365365);
        appd.addField(Calendar.DAY_OF_YEAR, 1);
        if (appd.getDate().compareTo(initdatedate) < 0) {
        	 SimpleDateFormat fd = new SimpleDateFormat("dd-MM-yyyy");      	 
            throw new GeneralException("GENE-0019", "EL CALENDARIO NO PUEDE DEJAR HUECOS PARA LA SUCURSAL: {0} FCONTABLE ANTERIOR: {1}", pBracnch, fd.format(d));
        }
        TgeneAccountingDateBranch ad = DataHelper.getInstance().getTgeneAccountingDateBranch(pCompany, 0);
        if (ad.getRealdate().compareTo(initdatedate) > 0) {
            throw new GeneralException("GENE-0020", "FECHA DE INICIO PARA GENERAR CALENDARIO NO PUEDE SER MENOR A: {0}",
                    ad.getRealdate());
        }
    }

    /**
     * Metodo que se ejecuta en modo reverso
     *
     * @param saveRequest
     * @return SaveRequest
     * @throws Exception
     */
    public SaveRequest reverseProcess(SaveRequest saveRequest) throws Exception {
        //En este caso no hacer nada no existen reversos. 
        return saveRequest;
    }
}
