package com.fp.general.helper;

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

import com.fp.common.exception.CommonException;
import com.fp.dto.data.SaveData;
import com.fp.dto.rules.TransactionRule;
import com.fp.general.exception.GeneralException;
import com.fp.persistence.commondb.PersistenceHelper;
import com.fp.persistence.pgeneral.acco.TgeneAccoNumStructure;
import com.fp.persistence.pgeneral.acco.TgeneAccountSequence;
import com.fp.persistence.pgeneral.acco.TgeneAccountSequenceKey;
import com.fp.persistence.pgeneral.acco.TgeneSequStructDetail;
import com.fp.persistence.pgeneral.acco.TgeneSequentialModule;

/**
 * Clase que se encarga de entregar el nuemro de cuenta asociado a un susbistema.<br>
 * La secuencia de cuenta se puede definir por modulo, producto, subproducto, branch, oficina y compania
 * La estructura de numero de cuenta esta definida en la tabla TGENEACCONUMSTRUCTURE
 * La estructura de secuencia esta definida en TGENESEQUSTRUCT Y TGENESEQUSTRUCTDETAIL
 * La relacion entre el modulo y el secuencial esta definida en la tabla TGENESEQUENTIALMODULE
 * La tabla TGENEACCOUNTSEQUENCE contiene el ultimo secuencial utilizado, sino existe un registro en esta tabla el sistema lo
 * crea automaticamente
 * Cuando se llama a la rutina esta se ejecuta en una session de base de datos diferente, obtiene el numero de cuenta y
 * graba el registro en la base de datos, con el fin de no tener bloqueos cuando se realiza multiples autalizacions de
 * cuenta.
 * 
 * @author Jorge Vaca
 * @version 2.1
 */
public abstract class AccountNumber extends TransactionRule {

    /**
     * Map en el cual se almacenan los parametros que llegan al metodo que obtien la cuenta, para posteriormente
     * utilizarlo en la generacion del numero de cuenta.
     */
    private Map<String, Object> mparam = new HashMap<String, Object>();

    /** Numero de caracteres de la secuencia de la cuenta. */
    private Integer sequencelength = 0;

    /**
     * Metodo que entrega el numero de cuenta a crear.
     * 
     * @param pModule Codigo de modulo.
     * @param pProduct Codigo de producto.
     * @param pSubproduct Codigo de subproducto.
     * @param pBranch Codigo de sucursal.
     * @param pOffice Codigo de oficina.
     * @param pCompany Codigo de compania.
     * @return String
     * @throws Exception
     */
    public String getAccount(String pModule, String pProduct, String pSubproduct, Integer pBranch, Integer pOffice, Integer pCompany)
            throws Exception {
        this.parametersToMap(pModule, pProduct, pSubproduct, pBranch, pOffice, pCompany);
        String accountnumber = "";
        try {
            List<TgeneAccoNumStructure> ldata = TgeneAccoNumStructure.find(PersistenceHelper.getEntityManager(), pModule);
            if (ldata.size() == 0) {
                throw new GeneralException("GENE-0039", "ESTRUCTURA DE NUMERO DE CUENTA NO DEFINIDA EN TGENEACCONUMSTRUCTURE MODULO: {0}", pModule);
            }           
            for (TgeneAccoNumStructure obj : ldata) {
                if (obj.getPk().getStructureaccountcatalog().compareTo("SEQUENCE") == 0) {
                    this.sequencelength = obj.getSizefield();
                    String sec = this.process(pModule, pProduct, pSubproduct, pBranch, pOffice, pCompany);
                    accountnumber = accountnumber + sec;
                    continue;
                }
                String data = this.mparam.get(obj.getPk().getStructureaccountcatalog()).toString();
                accountnumber = accountnumber + this.completeDataLeft(data, obj.getSizefield(), "0");
            }
            if (this.sequencelength == 0) {
                throw new GeneralException("GENE-0036", "NUMERO DE CARACTERES DEL SECUENCIAL NO DEFINIDO EN TGENEACCONUMSTRUCTURE MODULO: {0}", pModule);
            }                
            SaveData.getMap().put("ACCOUNT", accountnumber);
            return accountnumber;        	
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * Metodo que obtiene la secuencia de cuenta por modulo.
     * 
     * @param pModule Codigo de modulo.
     * @param pProduct Codigo de producto.
     * @param pSubproduct Codigo de subproducto.
     * @param pBranch Codigo de sucursal.
     * @param pOffice Codigo de oficina.
     * @param pCompany Codigo de compania.
     * @return String
     * @throws Exception
     */
    private String process(String pModule, String pProduct, String pSubproduct, Integer pBranch, Integer pOffice, Integer pCompany) throws Exception {
        TgeneAccountSequence tgas = null;
        try {
        	tgas = TgeneAccountSequence.findWithhold(PersistenceHelper.getEntityManager(), pModule, pProduct, pSubproduct, pBranch,
                pOffice, pCompany);	
        } catch (CommonException e) {
			if (e.getCode().compareTo("CORE-0025") == 0) {	        	
				TgeneSequentialModule sequByModule = TgeneSequentialModule.find(PersistenceHelper.getEntityManager(), pModule);
	            if (sequByModule == null) {
	                throw new GeneralException("GENE-0037", "ESTRUCTURA DE SECUENCIAL NO DEFINIDA EN TGENESEQUENTIALMODULE MODULO: {0}", pModule);
	            }
	            TgeneAccountSequence accountSequence = new TgeneAccountSequence();
	            Integer sequence = Integer.valueOf(TgeneAccountSequence.getAmountSequences(PersistenceHelper.getEntityManager(), pModule));
	            sequence++;
	            TgeneAccountSequenceKey accountSequenceKey = new TgeneAccountSequenceKey(pModule,sequence.toString());
	            accountSequence.setPk(accountSequenceKey);
	            accountSequence.setSequentialnumber("0");
	            List<TgeneSequStructDetail> sequStruct = TgeneSequStructDetail.find(PersistenceHelper.getEntityManager(), sequByModule.getSequcode());
	            if (sequStruct.size() == 0) {
	                throw new GeneralException("GENE-0038", "ESTRUCTURA DE SECUENCIAL NO DEFINIDA EN TGENESEQUESTRUCTDETAIL MODULO: {0}", pModule);
	            }	            
	            for (TgeneSequStructDetail obj : sequStruct) {
	                if (obj.getPk().getStructureaccountcatalog().compareTo("PRODUCT") == 0) {
	                	accountSequence.setProductcode(pProduct);
	                }
	                if (obj.getPk().getStructureaccountcatalog().compareTo("SUBPRODUCT") == 0) {
	                	accountSequence.setSubproductcode(pSubproduct);
	                }
	                if (obj.getPk().getStructureaccountcatalog().compareTo("COMPANY") == 0) {
	                	accountSequence.setCompanycode(pCompany);
	                }
	                if (obj.getPk().getStructureaccountcatalog().compareTo("BRANCH") == 0) {
	                	accountSequence.setBranchcode(pBranch);
	                }
	                if (obj.getPk().getStructureaccountcatalog().compareTo("OFFICE") == 0) {
	                	accountSequence.setOfficecode(pOffice);
	                }
	            }            
	            PersistenceHelper.saveOrUpdate(accountSequence);
	            tgas = accountSequence;
			} 
        }
        Integer sec = Integer.valueOf(tgas.getSequentialnumber());
        sec++;
        tgas.setSequentialnumber(sec.toString());
        PersistenceHelper.saveOrUpdate(tgas);

        return this.completeDataLeft(sec.toString(), this.sequencelength, "0");
    }

    /**
     * Metodo que completa o rellena los valores con la longitud exacta hacia el aldo izquierdo
     * 
     * @param data Valor a completar caracteres.
     * @param longitud Longitud total del caracteres.
     * @param fillcharacter Caracter de relleno.
     * @return String
     */
    public String completeDataLeft(String data, int longitud, String fillcharacter) {
        if (data == null) {
            data = "";
        }
        if (longitud == 0) {
            return data;
        }
        if (data.length() > longitud) {
            data = data.substring(0, longitud);
            return data;
        }
        int newlongitud = longitud - data.length();
        for (int i = 0; i < newlongitud; i++) {
            data = fillcharacter + data;
        }

        return data;
    }

    /**
     * Metodo que crea un map con los parametros de entrada, para luego generar el numero de cuenta.
     * 
     * @param pModule Codigo de modulo.
     * @param pProduct Codigo de producto.
     * @param pSubproduct Codigo de subproducto.
     * @param pBranch Codigo de sucursal.
     * @param pOffice Codigo de oficina.
     * @param pCompany Codigo de compania.
     * @throws Exception
     */
    private void parametersToMap(String pModule, String pProduct, String pSubproduct, Integer pBranch, Integer pOffice, Integer pCompany)
            throws Exception {
        this.mparam.put("MODULE", pModule);
        this.mparam.put("PRODUCT", pProduct);
        this.mparam.put("SUBPRODUCT", pSubproduct);
        this.mparam.put("BRANCH", pBranch);
        this.mparam.put("OFFICE", pOffice);
        this.mparam.put("COMPANY", pCompany);
    }
        
}
