package com.fitbank.installment;

import java.math.BigDecimal;
import java.sql.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.fitbank.common.ApplicationDates;
import com.fitbank.common.Helper;
import com.fitbank.common.QuotaBean;
import com.fitbank.common.RequestData;
import com.fitbank.common.exception.FitbankException;
import com.fitbank.common.helper.CalculationBase;
import com.fitbank.common.helper.Constant;
import com.fitbank.common.helper.Dates;
import com.fitbank.fin.helper.FinancialParameters;
import com.fitbank.hb.persistence.gene.Tsystemparametercompany;
import com.fitbank.hb.persistence.gene.TsystemparametercompanyKey;

/**
 * Clase que se encarga de generar tabla de pagos manual.
 * 
 * @author Soft WareHouse S.A.
 * 
 */
public class ManualInstallment extends AbstractQuota {

    private BigDecimal capitalinquotas = Constant.BD_ZERO;
    /** Interese acumulado de cuotas deudoras. */
    private BigDecimal debtorinterest = Constant.BD_ZERO;
    /**
     * Valor de cuota fija ajustada.
     */
    private BigDecimal fixquota = Constant.BD_ZERO;
    /**
     * Indica si se realizo o no el ajuste de una cuota fija.
     */
    private boolean adjustquota = true;

    private boolean manualQuota = false;

    @Override
    public void calculate(InstallmentTable pQuotaTable) throws Exception {
        quotaTable = pQuotaTable;
        reducedcapital = quotaTable.getAmount();
        quotanumber = pQuotaTable.getBegincalculationperiod();
        fixquota = quotaTable.getFixquota();
        if (!quotaTable.isFixedQuota()) {
            // calcula el numero de cuotas, cuando se conoce el plazo de la
            // operacion.
            super.calculateTotalperiod(pQuotaTable);
        }
        // Calulate grace quotas.
        this.generateGrace();
        boolean end = false;

        if (!quotaTable.isFixedQuota()) {
            this.obtainFixquota(end);

            // Ajuste de valor de cuota fija si es una sola cuota no se ajusta
            if (adjustquota
                    && pQuotaTable.isAdjustquota()
                    && (quotanumber - quotaTable.getBegincalculationperiod()) != 0) {
                this.adjustQuote();
            }
        } else {
            while (!end) {
                super.globalIntrest = Constant.BD_ZERO;
                super.calculatePayDate(quotaTable);
                if (quotaTable.getAmount().subtract(capitalinquotas)
                        .compareTo(fixquota) < 0) {
                    end = true;
                }
                // calcula intereses, comiones,seguros para la cuota.
                super.processByCategory(debtorinterest, false);
                this.calculateCuota(end);
                quotanumber++;
            }
            /* Se resta 1 al numero de cuotas */
            quotaTable.setTotalperiod(--quotanumber);
        }
    }

    /**
     * Calcula tabla de pago dado una cuota fija.
     * 
     * @throws Exception
     */
    protected void calculateCuota(boolean pEnd) throws Exception {
        BigDecimal capital = Constant.BD_ZERO;
        // Si el valor total de intereses y comisiones excede del valor de la
        // cuota fija
        if (super.globalIntrest.compareTo(this.fixquota) > 0) {
            // Se acumula el valor de intereses y comisiones excedente de la
            // cuota y se asume como el
            // valor de intereses y comisiones de la cuota el valor de la cuota
            this.debtorinterest = this.debtorinterest.add(this.globalIntrest
                    .subtract(this.fixquota));
            this.globalIntrest = this.fixquota;
            // Si se tiene un valor acumulado de intereses y comisiones
            // proveniente de cuotas anteriores
        } else if (this.debtorinterest.compareTo(Constant.BD_ZERO) > 0) {
            BigDecimal aux = this.fixquota.subtract(this.globalIntrest);
            BigDecimal value;
            if (this.debtorinterest.compareTo(aux) > 0) {
                value = aux;
            } else {
                value = this.debtorinterest;
            }
            this.debtorinterest = this.debtorinterest.subtract(value);
            this.globalIntrest = this.globalIntrest.add(value);
        }
        // Distribuye el interes deudor si exitiera.
        this.distributeDebtorInterest(pEnd);
        // Si se trata de la ultima cuota
        if (pEnd) {
            // Define el valor de Intereses mas Comisiones de la cuota por si
            // hay un residuo en la ultima cuota
            // Carga todo el capital reducido al capital de la cuota
            capital = this.reducedcapital;
            // this.reducedcapital = this.reducedcapital.subtract(this.capital);
        } else {
            // Obtiene el valor de capital de la cuota
            capital = this.fixquota.subtract(this.globalIntrest);
        }
        // validar limite de dia inicial
        if (super.globalIntrest.compareTo(capital) > 0) {
            validateInitialDay(getParameter());
        }
        super.addQuota(this.quotanumber, this.reducedcapital, capital, true);
        if (!pEnd) {
            reducedcapital = reducedcapital.subtract(capital);
        }
        capitalinquotas = capitalinquotas.add(capital);
    }

    /**
     * Distribuye el interes, comision, seguros hasta completar el valor de la
     * cuota.
     * 
     * @param pEnd
     *            Indica si es la ultinma cuota de la tabla de pagos.
     * @throws Exception
     */
    private void distributeDebtorInterest(boolean pEnd) throws Exception {
        BigDecimal intrestToDistribute = this.globalIntrest;
        BigDecimal totalIntrest;
        Iterator<List<CategoryRate>> itr = this.mCategoryRates.values()
                .iterator();
        while (itr.hasNext()) {
            for (CategoryRate categoryRate : itr.next()) {
                totalIntrest = categoryRate.getIntrest().add(
                        categoryRate.getDebtorinterest());
                // Si el valor total de interes o comision de la categoria
                // excede el valor total definido de interes a distribuir y no
                // se trata de la ultima cuota
                if (totalIntrest.compareTo(intrestToDistribute) > 0 && !pEnd) {
                    // Se acumula el valor de interes excedente de la categoria
                    // y se asume como el valor de interes de la categoria el
                    // valor total definido de interes a distribuir
                    categoryRate.setDebtorinterest(totalIntrest
                            .subtract(intrestToDistribute));
                    categoryRate.setIntrest(intrestToDistribute);
                } else {
                    categoryRate.setIntrest(totalIntrest);
                    categoryRate.setDebtorinterest(totalIntrest
                            .subtract(categoryRate.getIntrest()));
                }
                // Se obtiene el nuevo valor de interes a distribuir
                intrestToDistribute = intrestToDistribute.subtract(categoryRate
                        .getIntrest());
            }
        }// end while

    }

    /**
     * Genera cuotas de interes por el numero de periodos de gracia definidos
     * para la tabla.
     * 
     * @throws Exception
     */
    private void generateGrace() throws Exception {
        if (quotaTable.getGraceperiod() > 0) {
            for (int i = 0; i < quotaTable.getGraceperiod(); i++) {
                super.calculatePayDate(quotaTable);
                // this.calculateIntrestCommission(quotaTable,reducedcapital);
                super.processByCategory(false);
                super.addQuota(quotanumber, reducedcapital, Constant.BD_ZERO,
                        true);
                quotanumber++;
            }
        }
    }

    /**
     * Si el valor de la ultima cuota es diferente del valor de la cuota fija,
     * se obtiene un nuevo valor de cuota fija con aproimaciones sucesivas.
     * 
     * @throws Exception
     */
    private void adjustQuote() throws Exception {
        if (this.getLastquote().compareTo(quotaTable.getFixquota()) != 0) {
            BigDecimal quotavalue = null;
            if (FinancialParameters.getInstance()
                    .getBooleanValue("nominalrate")) {
                AdjustFixedInstallment adjustFixedQuota = new AdjustFixedInstallment();
                quotavalue = adjustFixedQuota.calculate(quotaTable);
                // quotavalue =
                // quotavalue.add(quotaTable.getOtherChargesQuota());
            } else {
                AdjustFixedInstallmentCapitalize adjustFixedQuota = new AdjustFixedInstallmentCapitalize();
                quotavalue = adjustFixedQuota.calculate(quotaTable);
                // quotavalue =
                // quotavalue.add(quotaTable.getOtherChargesQuota());
            }
            quotaTable.setFixquota(quotavalue);
            quotaTable.getQuotasBean().clear();
            quotaTable.getQuotaCategoryBean().clear();
            super.setPreviouspaydate(null);
            super.setNextpaydate(null);
            super.totaldays = 0;
            this.adjustquota = false;
            quotanumber = quotaTable.getBegincalculationperiod();
            this.calculate(quotaTable);
        }
    }

    /**
     * Entrega el valor de la ultima cuota.
     * 
     * @return
     * @throws Exception
     */
    private BigDecimal getLastquote() throws Exception {
        QuotaBean quotaBean = null;
        for (QuotaBean quota : quotaTable.getQuotasBean()) {
            quotaBean = quota;
        }
        BigDecimal valuequota = quotaBean.getCapital()
                .add(quotaBean.getInteres())
                .add(quotaBean.getComision().add(quotaBean.getCargo()));
        valuequota = valuequota.add(quotaBean.getAcciones());
        return valuequota;
    }

    /**
     * Cambia los parametros de las cuota, para generar una tabla especial.
     * 
     * @throws Exception
     */
    private void changeParameters() throws Exception {
        Map<Integer, Map<String, Object>> mdata = quotaTable.getMspecial();
        if (mdata == null) {
            return;
        }
        Map<String, Object> mquota = mdata.get(this.quotanumber);
        if (mquota == null) {
            return;
        }
        this.manualQuota = true;
        this.fixquota = (BigDecimal) mquota.get("cuota");
        super.nextpaydate = (Date) mquota.get("fvencimiento");
        super.totaldays = super.totaldays - super.daysperquota;
        Dates dates = null;
        if (quotaTable.isFixday()
                && !(quotaTable.getPayday() != null && quotaTable.getPayday()
                        .compareTo(31) == 0)) {
            dates = new Dates(this.nextpaydate, CalculationBase.B360360);
        } else {
            dates = new Dates(this.nextpaydate, quotaTable.getCalculationBase());
        }
        Dates inicialday = new Dates(this.previouspaydate,
                quotaTable.getCalculationBase());
        int days = dates.substract(inicialday);
        this.setDaysperquota(days);
    }

    /**
     * Entrega el valor de la cuota fija, si no tiene una la calcula.
     * 
     * @throws Exception
     */
    private void calculateFixquota() throws Exception {
        FixedInstallment fixCuota = new FixedInstallment(
                quotaTable.getCalculationBase());
        Integer quotas = quotaTable.getTotalperiod() - quotanumber
                + Constant.BD_ONE_INTEGER;
        this.fixquota = fixCuota
                .calculate(this.reducedcapital, quotaTable.getFinalrate(),
                        quotaTable.getTotalFixedValue(),
                        quotaTable.getDaysperperiod(), quotas,
                        quotaTable.getCurrency());
    }

    /**
     * Recupera valor de cuota Fija
     * 
     * @param end
     * @return
     * @throws Exception
     */
    private boolean obtainFixquota(boolean end) throws Exception {
        int size = quotaTable.getTotalperiod() - quotaTable.getGraceperiod();
        size = size + quotaTable.getBegincalculationperiod() - 1;
        for (int i = quotaTable.getBegincalculationperiod(); i <= size; i++) {
            // BigDecimal quotavalue = this.fixquota;
            super.globalIntrest = Constant.BD_ZERO;
            super.calculatePayDate(quotaTable);
            if (manualQuota) {
                manualQuota = false;
                this.calculateFixquota();
            }
            // cambia condiciones de cuotas especiales.
            this.changeParameters();
            if (i == size) {
                end = true;
            }
            // calcula intereses, comiones,seguros para la cuota.
            super.processByCategory(debtorinterest, false);
            this.calculateCuota(end);
            // this.fixquota = quotavalue;
            if (super.stop) {
                break;
            }
            quotanumber++;
        }
        return end;
    }

    /**
     * Metodo para validar el limite de dias inicial
     * 
     * @param diasEx
     */
    private void validateInitialDay(boolean daysExceed) {
        if (daysExceed) {
            throw new FitbankException("COL047",
                    "DIAS DE PAGO INICIAL EXCEDE EL LIMITE PERMITIDO");
        }
    }

    /**
     * Recuperar Parametro del Sistema para controlar el limite de dia de pago
     * inicial
     * 
     * @param diasEx
     * @return
     */
    private boolean getParameter() {
        TsystemparametercompanyKey key = new TsystemparametercompanyKey(
                RequestData.getDetail().getCompany(), "DIAPAGOEXCEDE",
                ApplicationDates.DEFAULT_EXPIRY_TIMESTAMP);
        Tsystemparametercompany tsystemparametercompany = (Tsystemparametercompany) Helper
                .getSession().get(Tsystemparametercompany.class, key);
        return tsystemparametercompany != null
                && tsystemparametercompany.getValortexto() != null
                && tsystemparametercompany.getValortexto().compareTo("1") == 0;
    }

}
