/**
 * 
 */
package com.fitbank.installment;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import com.fitbank.common.QuotaBean;
import com.fitbank.common.helper.CalculationBase;
import com.fitbank.common.logger.FitbankLogger;
import com.fitbank.fin.helper.FinancialHelper;
import com.fitbank.hb.persistence.acco.loan.Tquotasaccount;
import com.fitbank.hb.persistence.gene.Tcurrencyid;
import com.fitbank.hb.persistence.soli.Tquotasolicitude;

/**
 * Ajuste de cuota fija con aproximaciones sucesivas.
 * @author BANTEC Inc.
 * @version 2.0
 */
public class AdjustFixedInstallment {
  /**
   * Base de calculo que se aplicado a la cuota.
   */
  private CalculationBase base ;
  /**
   * Numero de dias al anio de la base de calculo.
   */
  private BigDecimal basedays;
  /**
   * Tasa total del credito incluye comisiones.
   */
  private BigDecimal rate ;
  /**
   * Capital original del credito.
   */
  private BigDecimal capital ;
  /**
   * Valor de la cuota.
   */
  private BigDecimal quotavalue;
  /**
   * Datos generales de una tabla de pagos.
   */
  private InstallmentTable quotaTable;
  /**
   * Lista de cuotas no pagadas. 
   */
  private List<AuxiliarInstallment> auxquotas;
  /**
   * Numero de cuotas deudoras.
   */
  private Integer debtorquotes ;
  /**
   * Factor acumulativo utilizado en calculo de cuotas en aproximaciones sucesivas.
   */
  private BigDecimal totalfactor = BigDecimal.ZERO;
  /**
   * Datos de tmonedasid, tiene el numero de decimales de la manoneda.
   */
  private Tcurrencyid  tcurrencyid;

  
  
  /**
   * Ajusta el valor de la cuota fija, si el valor de la ultima cuota es mayor a la cuota fija.
   * @param pQuotaTable
   * @return
   * @throws Exception
   */
  public BigDecimal calculate(InstallmentTable pQuotaTable) throws Exception{
    quotaTable = pQuotaTable;
    quotavalue = quotaTable.getFixquota();
    
    capital = quotaTable.getAmount();
    rate = quotaTable.getTotalrate().divide(new BigDecimal(100),6,BigDecimal.ROUND_HALF_UP);
    base = quotaTable.getCalculationBase();
    basedays = new BigDecimal(base.getYearBase());
    debtorquotes = 0;
    tcurrencyid = FinancialHelper.getInstance().getTcurrencyid(quotaTable.getCurrency());
    this.fillAuxiliarQuota(pQuotaTable.getQuotasBean());
    //primera aproximacion sin interes deudor
    this.firstApproximation();
    //Busca cuotas que tengan interes deudor.
    this.findDebtorquotas();
    //segunda aproximacion considera interes deudor.
    this.secondAproximation();
    //aproximacion final 
    this.lastAproximation();
    return quotavalue;
  }
  
	/**
	 * Crea una lista de cuotas no pagadas y los factores que sumados me permiten hallar el valor presente de una anualidad que no toma en 
	 * consideraci�n per�odos de inter�s deudor.. 
	 * @param tquotas   Lista de cuotas original.
	 * @throws Exception
	 */
	private void fillAuxiliarQuota(List<QuotaBean> lQuotaBean) throws Exception{
		auxquotas = new ArrayList<AuxiliarInstallment>();
		int dayaccum = 0;
		int quotanumber = 1;
		int grace = quotaTable.getGraceperiod();
		for(QuotaBean quota: lQuotaBean){
			if(quota.getFpago() == null ){ //&& quota.getCapital().compareTo(new BigDecimal(0)) >= 0){
				if(quota instanceof Tquotasolicitude){
					if(grace >= ((Tquotasolicitude)quota).getPk().getSubcuenta()){
						continue;
					}
				}else{
					if(grace >= ((Tquotasaccount)quota).getPk().getSubcuenta()){
						continue;
					}
				}
				dayaccum += quota.getNumerodiasprovision();
				AuxiliarInstallment auxq = new AuxiliarInstallment(quota,dayaccum);
				auxq.setQuotanumber(quotanumber);
				auxquotas.add(auxq);
				quotanumber++;
			}
		}
	}
  
  /**
   * Calculo de la primera aproximacion sin tomar en cuenta el interes deudor.
   * @throws Exception
   */
  private  void firstApproximation()throws Exception{
    BigDecimal factor = BigDecimal.ONE;
    //totalfactor := nvl(factor,0) + totalfactor;
    for(AuxiliarInstallment quota :auxquotas){
      factor = this.getFactor(quota.getQuotaBean().getNumerodiasprovision(),factor);
      totalfactor = totalfactor.add(factor);
    }
    //quotavalue := capital/totalfactor; 
    quotavalue = capital.divide(totalfactor,12,BigDecimal.ROUND_HALF_UP);
    FitbankLogger.getLogger().debug("Cuota primera aproximacion "+this.quotavalue);
  }
  
  //Segunda aproximacion cuando tiene intereses dudores.
  private void secondAproximation() throws Exception{
    //Factor que me permite hallar el valor presente de una anualidad en el periodo que toma en cuenta periodos con interes deudor.
    FitbankLogger.getLogger().debug("Segunda aproximacion cuotas deudoras "+this.debtorquotes);
    BigDecimal factor = BigDecimal.ONE;
    int auxdebtorquotas = this.debtorquotes + 2;
    AuxiliarInstallment quota = this.getAuxiliarQuota(auxdebtorquotas);
    if(quota == null) {
        return;
    }
    factor = this.getFactor(quota.getQuotaBean().getNumerodiasprovision(),BigDecimal.ONE);
    totalfactor = factor;
    auxdebtorquotas = this.debtorquotes + 3;
    for (int i = (auxdebtorquotas - 1); i < auxquotas.size(); i++) {
      AuxiliarInstallment auxquota = auxquotas.get(i);
      factor = this.getFactor(auxquota.getQuotaBean().getNumerodiasprovision(),factor);
      totalfactor = totalfactor.add(factor);
    }
    quota = this.getAuxiliarQuota(this.debtorquotes + 1);
    this.calculateValuequota(quota.getAccumulateddays());
    FitbankLogger.getLogger().debug("Cuota segunda aproximacion "+this.quotavalue);
  }
  
  /**
   * Ultimaaproximaccion para encontrar el valor de una cuota fija.
   * @throws Exception
   */
  private void lastAproximation() throws Exception{
    int j,p = 0;
    BigDecimal factor = BigDecimal.ONE;
    while (quotavalue.multiply(totalfactor).subtract(capital).compareTo(BigDecimal.ZERO) > 0) {
      j = p;
      debtorquotes = debtorquotes + 2*j + 1;
      factor = this.getFactor(debtorquotes);
      totalfactor = BigDecimal.ZERO;
      for (int i = debtorquotes+2; i < auxquotas.size(); i++) {
        factor = this.getFactor(auxquotas.get(i).getQuotaBean().getNumerodiasprovision(),factor);
        totalfactor = totalfactor.add(factor);
      }
      AuxiliarInstallment quota = this.getAuxiliarQuota(debtorquotes+1);
      if(quota == null) {
        return;
      }
      this.calculateValuequota(quota.getQuotaBean().getNumerodiasprovision());
      if(quotavalue.multiply(totalfactor).subtract(capital).compareTo(BigDecimal.ZERO) > 0){
        debtorquotes = debtorquotes - (2*j + 2);
        factor = this.getFactor(debtorquotes);
        totalfactor = BigDecimal.ZERO;
        for (int i = debtorquotes+2; i < auxquotas.size(); i++) {
          factor = this.getFactor(auxquotas.get(i).getQuotaBean().getNumerodiasprovision(),factor);
          totalfactor = totalfactor.add(factor);
        }
        AuxiliarInstallment auxquota = this.getAuxiliarQuota(debtorquotes+1);
        this.calculateValuequota(auxquota.getAccumulateddays());
      }
      p++;
    }//end while
    quotavalue = quotavalue.divide(BigDecimal.ONE,this.tcurrencyid.getNumerodecimales(),BigDecimal.ROUND_HALF_UP);
    FitbankLogger.getLogger().debug("Cuota tercera aproximacion "+this.quotavalue);
  }

  /**
   * Cacula el valor de una cuota con periodos deudores.
   */
  private void calculateValuequota(Integer pQuotadays) throws Exception{
    //cuota := (capital*(1+(Vtasa/diasbase*Vnumdias))) / (1+Vcuotasdeudoras+VsumFactor);
    //cuota = capital *(i)/j
    BigDecimal i  = rate.divide(basedays,12,BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(pQuotadays));
    i = capital.multiply(i.add(BigDecimal.ONE)) ;
    BigDecimal j = new BigDecimal(1+this.debtorquotes).add(this.totalfactor);
    quotavalue = i.divide(j,12,BigDecimal.ROUND_HALF_UP);
  }
  
  /**
   * Entrega un factor para calcular valores de cuotas en aproximaciones sucesivas. 
   * @param pQuotadays    Numero de dias de una cuota.
   * @param pFactor       Factor previo.
   * @return auxfactor
   * @throws Exception
   */
  private BigDecimal getFactor(Integer pQuotadays,BigDecimal pFactor) throws Exception{
    BigDecimal auxfactor = BigDecimal.ZERO;
    //  factor     := (daysyear/(daysyear+pRate*cuotadays))*factor;
    //(daysyear+pRate*cuotadays)
    auxfactor = rate.multiply(new BigDecimal(pQuotadays)).add(this.basedays);
    auxfactor = basedays.divide(auxfactor,12,BigDecimal.ROUND_HALF_UP);
    auxfactor = auxfactor.multiply(pFactor);
    return auxfactor;
  }
  
  private BigDecimal getFactor(int pDebtorquotes) throws Exception{
    BigDecimal auxfactor = BigDecimal.ZERO;
    //factor := diasbase/(diasbase+(Vtasa*(Vcuotasdeudoras+2)));
    auxfactor = basedays.add(rate.multiply(new BigDecimal(pDebtorquotes+2)));
    auxfactor = basedays.divide(auxfactor,12,BigDecimal.ROUND_HALF_UP);
    return auxfactor;
  }
  /**
   * Entrega el numero de cuotas que tienen interes deudor.
   * @param pAuxquotas
   * @return
   * @throws Exception
   */
  private void findDebtorquotas() throws Exception{
    BigDecimal aux = BigDecimal.ZERO;
    int j = 1; 
    for (AuxiliarInstallment auxquota : auxquotas) {
      //if (((Vcapital*Vtasa/diasbase)*Vnumdiasj) - (quotanumber * Vcuota)) < 0 then
      aux = capital.multiply(rate).divide(new BigDecimal(base.getYearBase()),12,BigDecimal.ROUND_HALF_UP);
      aux = aux.multiply(new BigDecimal( auxquota.getAccumulateddays()));
      aux =aux.subtract(quotavalue.multiply(new BigDecimal(j) ));
      if(aux.compareTo(BigDecimal.ZERO) < 0){
        debtorquotes = j -1;
        break;
      }
      j++;
    }
    
  }
  
  
  
  /**
   * Entrega una cuota auxiliar dado un numero de cuota.
   * @param pAuxquotas     Lista de cuotas auxiliares no pagadas.
   * @param pQuotanumber   Numero de cuota a buscar.
   * @return
   * @throws Exception
   */
  private AuxiliarInstallment getAuxiliarQuota(Integer pQuotanumber) throws Exception{
    AuxiliarInstallment auxiliarQuota = null;
    for(AuxiliarInstallment quota : auxquotas){
      if(quota.getQuotanumber().compareTo(pQuotanumber) == 0){
        auxiliarQuota = quota;
        break;
      }
    }
    return auxiliarQuota;
  }
  
}
