/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.rjconsultores.ventaboletos.service.impl;

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;

import javax.persistence.Transient;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import com.rjconsultores.ventaboletos.dao.CorridaDAO;
import com.rjconsultores.ventaboletos.dao.EsquemaCorridaDAO;
import com.rjconsultores.ventaboletos.dao.TramoTiempoDAO;
import com.rjconsultores.ventaboletos.entidad.AsientoExclusivo;
import com.rjconsultores.ventaboletos.entidad.Ciudad;
import com.rjconsultores.ventaboletos.entidad.ClaseServicio;
import com.rjconsultores.ventaboletos.entidad.Cliente;
import com.rjconsultores.ventaboletos.entidad.Constante;
import com.rjconsultores.ventaboletos.entidad.Corrida;
import com.rjconsultores.ventaboletos.entidad.Corrida.Id;
import com.rjconsultores.ventaboletos.entidad.CorridaCtrl;
import com.rjconsultores.ventaboletos.entidad.CorridaInfo;
import com.rjconsultores.ventaboletos.entidad.CorridaTramo;
import com.rjconsultores.ventaboletos.entidad.DetDiagramaAutobus;
import com.rjconsultores.ventaboletos.entidad.DiagramaAutobus;
import com.rjconsultores.ventaboletos.entidad.Empresa;
import com.rjconsultores.ventaboletos.entidad.EsquemaAsiento;
import com.rjconsultores.ventaboletos.entidad.EsquemaCorrida;
import com.rjconsultores.ventaboletos.entidad.EsquemaCorridaInfo;
import com.rjconsultores.ventaboletos.entidad.EsquemaTramo;
import com.rjconsultores.ventaboletos.entidad.Estado;
import com.rjconsultores.ventaboletos.entidad.Marca;
import com.rjconsultores.ventaboletos.entidad.Moneda;
import com.rjconsultores.ventaboletos.entidad.Parada;
import com.rjconsultores.ventaboletos.entidad.ReservaEspecialCliente;
import com.rjconsultores.ventaboletos.entidad.Ruta;
import com.rjconsultores.ventaboletos.entidad.RutaCombinacion;
import com.rjconsultores.ventaboletos.entidad.RutaSecuencia;
import com.rjconsultores.ventaboletos.entidad.Tarifa;
import com.rjconsultores.ventaboletos.entidad.Tramo;
import com.rjconsultores.ventaboletos.entidad.TramoServicio;
import com.rjconsultores.ventaboletos.entidad.TramoTiempo;
import com.rjconsultores.ventaboletos.entidad.VigenciaTarifa;
import com.rjconsultores.ventaboletos.enums.StatusFlexBus;
import com.rjconsultores.ventaboletos.exception.BusinessException;
import com.rjconsultores.ventaboletos.service.AsientoExclusivoService;
import com.rjconsultores.ventaboletos.service.BoletoService;
import com.rjconsultores.ventaboletos.service.ClienteService;
import com.rjconsultores.ventaboletos.service.ConstanteService;
import com.rjconsultores.ventaboletos.service.CorridaCtrlService;
import com.rjconsultores.ventaboletos.service.CorridaInfoService;
import com.rjconsultores.ventaboletos.service.CorridaService;
import com.rjconsultores.ventaboletos.service.CorridaTramoService;
import com.rjconsultores.ventaboletos.service.DetDiagramaAutobusService;
import com.rjconsultores.ventaboletos.service.EsquemaAsientoService;
import com.rjconsultores.ventaboletos.service.EsquemaCorridaInfoService;
import com.rjconsultores.ventaboletos.service.EsquemaCorridaService;
import com.rjconsultores.ventaboletos.service.EsquemaTramoService;
import com.rjconsultores.ventaboletos.service.ReservaEspecialClienteService;
import com.rjconsultores.ventaboletos.service.RutaSecuenciaService;
import com.rjconsultores.ventaboletos.service.RutaService;
import com.rjconsultores.ventaboletos.service.TarifaService;
import com.rjconsultores.ventaboletos.service.TramoService;
import com.rjconsultores.ventaboletos.service.TramoServicioService;
import com.rjconsultores.ventaboletos.service.VigenciaTarifaService;
import com.rjconsultores.ventaboletos.utilerias.ActivoUtil;
import com.rjconsultores.ventaboletos.utilerias.DateUtil;
import com.rjconsultores.ventaboletos.utilerias.FeriadoCache;
import com.rjconsultores.ventaboletos.utilerias.HoraSistema;
import com.rjconsultores.ventaboletos.utilerias.UsuarioLogado;
import com.rjconsultores.ventaboletos.utilerias.UtiliteriasFiscal;

/**
 * 
 * @author rodrigo
 */
@Service("corridaService")
public class CorridaServiceImpl implements CorridaService {

	private static final Logger log = LogManager.getLogger(CorridaServiceImpl.class);
	private static int CANT_MAX_CORRIDA_GERA_MANUAL = 40;
	
	// Tipo de venda Reserva
	private static final Integer TPV_RESERVA_NORMAL = 15;

	@Autowired
	private CorridaDAO corridaDAO;
	@Autowired
	private EsquemaAsientoService esquemaAsientoService;
	@Autowired
	private CorridaCtrlService corridaCtrlService;
	@Autowired
	private RutaService rutaService;
	@Autowired
	private TramoServicioService tramoServicioService;
	@Autowired
	private TramoTiempoDAO tramoTiempoDAO;
	@Autowired
	private AsientoExclusivoService asientoExclusivoService;
	@Autowired
	private EsquemaTramoService esquemaTramoService;
	@Autowired
	private EsquemaCorridaService esquemaCorridaService;
	@Autowired
	private EsquemaCorridaDAO esquemaCorridaDAO;
	@Autowired
	private DetDiagramaAutobusService detDiagramaAutobusService;
	@Autowired
	private ConstanteService constanteService;
	@Autowired
	private RutaSecuenciaService rutaSecuenciaService;
	private PlatformTransactionManager transactionManager;
	@Transient
	private int cantCorridaGenerada;
	@Transient
	private int cantCorridaCommit;
	@Autowired
	private FeriadoCache feriadoCache;
	@Autowired
	private BoletoService boletoService;
	@Autowired
	private ReservaEspecialClienteService reservaEspecialClientService;	
	@Autowired
	private TarifaService tarifaService;
	@Autowired
	TramoService tramoService;
	@Autowired
	VigenciaTarifaService vigenciaTarifaService;
	@Autowired
	ClienteService clienteService;
	@Autowired
	private CorridaInfoService corridaInfoService;
	@Autowired
	private EsquemaCorridaInfoService esquemaCorridaInfoService;
	@Autowired
	private CorridaTramoService corridaTramosService;
	
	@Autowired
	public CorridaServiceImpl(@Qualifier("transactionManager") PlatformTransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

	@Override
	public List<Corrida> obtenerTodos() {
		return corridaDAO.obtenerTodos();
	}

	@Override
	public Corrida obtenerID(Corrida.Id id) {
		return corridaDAO.obtenerID(id);
	}

	@Override
	public List<Corrida> buscarPorFecCorrida(Date value) {
		return corridaDAO.buscarPorFecCorrida(value);
	}

	@Override
	public List<Corrida> buscarPorId(Integer idCorrida) {
		return corridaDAO.buscarPorId(idCorrida);
	}

	@Override
	public List<Corrida> verificarCorridaId(Integer corridaId) {
		return corridaDAO.verificarCorridaId(corridaId);
	}

	@Override
	public List<Corrida> buscarDistinct(Parada ori, Parada des, Date horaSalidaInicio, Date horaSalidaFin, ClaseServicio cs, Integer corridaId) {
		return corridaDAO.buscarDistinct(ori, des, (horaSalidaInicio == null) ? null : DateUtil.getFecInicio(horaSalidaInicio).getTime(), (horaSalidaFin == null) ? null : DateUtil.getFecInicio(horaSalidaFin).getTime(), cs, corridaId);
	}

	@Transactional
	public Corrida suscribir(Corrida entidad) {
		entidad.setUsuarioId(UsuarioLogado.getUsuarioLogado().getUsuarioId());
		entidad.setFecmodif(Calendar.getInstance().getTime());
		entidad.setActivo(ActivoUtil.ATIVO);

		return corridaDAO.suscribir(entidad);
	}

	@Transactional
	public Corrida actualizacion(Corrida entidad) {
		entidad.setUsuarioId(UsuarioLogado.getUsuarioLogado().getUsuarioId());
		entidad.setFecmodif(Calendar.getInstance().getTime());
		entidad.setActivo(ActivoUtil.ATIVO);

		return corridaDAO.actualizacion(entidad);
	}

	
	@Transactional
	public void borrar(Corrida entidad) {
		corridaDAO.borrar(entidad);
	}

	@Override
	public List<Corrida> buscarPorAutobusCorridasFuturas(DiagramaAutobus diagramaAutobus) {
		return corridaDAO.buscarPorAutobusCorridasFuturas(diagramaAutobus);
	}

	@Override
	public List<Corrida> buscarPorRolOperativoCorridasFuturas(DiagramaAutobus diagramaAutobus) {
		return corridaDAO.buscarPorRolOperativoCorridasFuturas(diagramaAutobus);
	}

	@Override
	public List<Corrida> buscarFiltro(Parada ori, Parada des, Date hora, ClaseServicio cs) {
		return corridaDAO.buscarFiltro(ori, des, hora, cs);
	}

	public Boolean existeCorrida(Long id){
		return corridaDAO.existeCorrida(id);
	}
	
	private Boolean geraDiaSemana(Integer diaSemana, EsquemaCorrida esquemaCorrida) {
		Boolean retorno = Boolean.FALSE;
		switch (diaSemana) {
		case 1: {
			// diaSemana = "Domingo";
			if (esquemaCorrida.getInddomingo() == null) {
				retorno = Boolean.FALSE;
			} else if (esquemaCorrida.getInddomingo()) {
				retorno = Boolean.TRUE;
			}
			break;
		}
		case 2: {
			// diaSemana = "Segunda";
			if (esquemaCorrida.getIndlunes() == null) {
				retorno = Boolean.FALSE;
			} else if (esquemaCorrida.getIndlunes()) {
				retorno = Boolean.TRUE;
			}
			break;
		}
		case 3: {
			// diaSemana = "Terca";
			if (esquemaCorrida.getIndmartes() == null) {
				retorno = Boolean.FALSE;
			} else if (esquemaCorrida.getIndmartes()) {
				retorno = Boolean.TRUE;
			}
			break;
		}
		case 4: {
			// diaSemana = "Quarta";
			if (esquemaCorrida.getIndmiercoles() == null) {
				retorno = Boolean.FALSE;
			} else if (esquemaCorrida.getIndmiercoles()) {
				retorno = Boolean.TRUE;
			}
			break;
		}
		case 5: {
			// diaSemana = "Quinta";
			if (esquemaCorrida.getIndjueves() == null) {
				retorno = Boolean.FALSE;
			} else if (esquemaCorrida.getIndjueves()) {

				retorno = Boolean.TRUE;
			}
			break;
		}
		case 6: {
			// diaSemana = "Sexta";
			if (esquemaCorrida.getIndviernes() == null) {
				retorno = Boolean.FALSE;
			} else if (esquemaCorrida.getIndviernes()) {
				retorno = Boolean.TRUE;
			}
			break;
		}
		case 7: {
			// diaSemana = "Sabado";
			if (esquemaCorrida.getIndsabado() == null) {
				retorno = Boolean.FALSE;
			} else if (esquemaCorrida.getIndsabado()) {
				retorno = Boolean.TRUE;
			}
			break;
		}
		}

		return retorno;
	}

	private boolean temCorridaDiaSemana(Integer diaSemana, EsquemaCorrida esquemaCorrida, Boolean isFeriado) {
		Boolean retorno = Boolean.FALSE;

		if (esquemaCorrida.getIndGeneraFeriado() != null) {
			if (esquemaCorrida.getIndGeneraFeriado().equals("S")) {
				retorno = geraDiaSemana(diaSemana, esquemaCorrida);
			} else if (isFeriado) {
				if (esquemaCorrida.getIndGeneraFeriado().equals("F")) {
					retorno = geraDiaSemana(diaSemana, esquemaCorrida);
				}
			} else if (esquemaCorrida.getIndGeneraFeriado().equals("N")) {
				retorno = geraDiaSemana(diaSemana, esquemaCorrida);
			}
		} else {
			retorno = geraDiaSemana(diaSemana, esquemaCorrida);
		}

		return retorno;
	}

	private Date calcularFechorllegada(EsquemaTramo esquemaTramo, ClaseServicio clase, Date dataGeracao, Date horaChegada) {
		GregorianCalendar gHoraChegada = new GregorianCalendar();
		gHoraChegada.setTime(horaChegada);
		HoraSistema hs = null;

		if (esquemaTramo.getExcepcionRecorrido() != null) {
			hs = new HoraSistema(esquemaTramo.getExcepcionRecorrido());

			gHoraChegada.add(Calendar.HOUR_OF_DAY, hs.getHora());
			gHoraChegada.add(Calendar.MINUTE, hs.getMinuto());
		} else {
			Tramo tramo = esquemaTramo.getTramo();
			List<TramoServicio> lsTramoServicio = tramoServicioService.buscarPorTramoServico(tramo, clase);

			for (TramoServicio ts : lsTramoServicio) {
				hs = new HoraSistema(ts.getTiemporecorrido());

				gHoraChegada.add(Calendar.HOUR_OF_DAY, hs.getHora());
				gHoraChegada.add(Calendar.MINUTE, hs.getMinuto());
			}
		}

		return gHoraChegada.getTime();
	}

	@SuppressWarnings("deprecation")
	private Date calcularFechorsalida(EsquemaTramo esquemaTramo, Date dataGeracao, Date horaChegadaAnterior, java.util.Date tiempoEstanciaAnterior) {
		Date horaSaida;

		if (horaChegadaAnterior == null) {
			horaSaida = truncarDataHora(dataGeracao, esquemaTramo.getEsquemaCorrida().getHorasalida());
		} else {
			GregorianCalendar gHoraSaidaProximo = new GregorianCalendar();
			gHoraSaidaProximo.setTime(horaChegadaAnterior);
			SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
			log.debug("Tempo Recorrido  : " + sdf.format(gHoraSaidaProximo.getTime()));

			if (tiempoEstanciaAnterior != null) {
				gHoraSaidaProximo.add(Calendar.MINUTE, tiempoEstanciaAnterior.getMinutes());
				gHoraSaidaProximo.add(Calendar.HOUR_OF_DAY, tiempoEstanciaAnterior.getHours());

				log.debug("Tempo Recorrido + Estancia: " + sdf.format(gHoraSaidaProximo.getTime()));
			}

			horaSaida = gHoraSaidaProximo.getTime();
		}

		GregorianCalendar gHoraSaida = new GregorianCalendar();
		gHoraSaida.setTime(horaSaida);

		return gHoraSaida.getTime();
	}

	/**
	 * Adiciciona as horas do parametro hora na data dataGeracao.
	 * 
	 * Alem disso eh zerado os campos segundos e milisegundos.
	 * 
	 * @param dataGeracao
	 * @param hora
	 * @return
	 */
	private Date truncarDataHora(Date dataGeracao, Date hora) {
		Date dataCorreta = new Date();

		GregorianCalendar gAux = new GregorianCalendar();
		gAux.setTime(hora);
		GregorianCalendar gcalendar = new GregorianCalendar();
		gcalendar.setTime(dataGeracao);
		gcalendar.set(Calendar.HOUR_OF_DAY, gAux.get(Calendar.HOUR_OF_DAY));
		gcalendar.set(Calendar.MINUTE, gAux.get(Calendar.MINUTE));
		gcalendar.set(Calendar.SECOND, 0);
		gcalendar.set(Calendar.MILLISECOND, 0);

		dataCorreta = gcalendar.getTime();

		return dataCorreta;
	}

	private boolean isFeriado(java.sql.Date data, EsquemaCorrida esquemaCorrida) {
		Empresa empresa = esquemaCorrida.getEmpresa();

		Estado estadoOrigem = null;
		List<RutaSecuencia> lsRutaSequencia = rutaSecuenciaService.buscarSecuenciaOrdenado(esquemaCorrida.getRuta());
		if (!lsRutaSequencia.isEmpty()) {
			Tramo tramoOrigem = lsRutaSequencia.get(0).getTramo();
			Parada origem = tramoOrigem.getOrigem();
			estadoOrigem = origem.getCiudad().getEstado();
		}

		return feriadoCache.ehFeriado(data, empresa, estadoOrigem);
	}

	/**
	 * Indica si el esquemaCorrida informado tiene una vigencia válida para la data de generacion
	 * 
	 * @param esquemaCorrida
	 * @param dataGeracao
	 * @return
	 */
	private boolean vigenciaValida(EsquemaCorrida esquemaCorrida, Date dataGeracao) {
		if ((DateUtil.compareOnlyDate(dataGeracao, esquemaCorrida.getEsquemaOperacional().getFecfinvigencia()) <= 0) &&
				(DateUtil.compareOnlyDate(dataGeracao, esquemaCorrida.getEsquemaOperacional().getFecniciovigencia()) >= 0)) {

			return true;
		}
		return false;
	}

	/**
	 * Genera una corrida para el esquemaCorrida informado en la fecha informada.<br/>
	 * 
	 * Cuando la corrida no es generada : <br/>
	 * - corrida ya existe : eso es checado por el numCorrida e dataGeneracion <br/>
	 * - la fecha de generacion no tiene una vigencia válida <br/>
	 * - el status del esquema corrida no es autorizado ("A") <br/>
	 * 
	 * 
	 * @param esquemaCorrida
	 * @param diaSemana
	 * @param dataGeracao
	 * @return - La corrida generada o null si no fue generada.
	 */
	@SuppressWarnings("deprecation")
	private Corrida generarCorrida(EsquemaCorrida esquemaCorrida, Date dataGeracao, boolean isPisoExtra, Boolean isFeriado, List<CorridaTramo> corridaTramoList, boolean isValidaActivo) {


		Corrida corrida = null;
		try {
			
			GregorianCalendar calendario = new GregorianCalendar();
			calendario.setTime(dataGeracao);
			Integer diaSemana = calendario.get(Calendar.DAY_OF_WEEK);
			Ruta ruta = esquemaCorrida.getRuta();
			ClaseServicio claseServicio = esquemaCorrida.getClaseServicio();
			Marca marca = esquemaCorrida.getMarca();
			Date horario = esquemaCorrida.getHorasalida();
			horario = DateUtil.normalizar(horario);
			Integer numCorrida = esquemaCorrida.getNumCorrida();
			Corrida.Id id = new Corrida.Id();
			id.setCorridaId(numCorrida);
			id.setFeccorrida(DateUtil.normalizarToFecha(dataGeracao));
			List<EsquemaTramo> lsEsquemaTramo = null;
			Parada origemCorrida = null;
			Parada destinoCorrida =null;
			boolean isValidaTrecho = true;
			if(corridaTramoList !=null && !corridaTramoList.isEmpty()) {
				origemCorrida = corridaTramoList.get(0).getOrigem();
				destinoCorrida = corridaTramoList.get(corridaTramoList.size()-1).getDestino();
				isValidaTrecho = false;
			}else {
				lsEsquemaTramo = esquemaTramoService.obtenerPorEsquemaCorridaOrderNumSec(esquemaCorrida);				
				origemCorrida = lsEsquemaTramo.get(0).getTramo().getOrigem();				
				destinoCorrida = lsEsquemaTramo.get(lsEsquemaTramo.size() - 1).getTramo().getDestino();
			}
			
			
			if(!realizaValidacoesGerais(esquemaCorrida, dataGeracao, diaSemana, isFeriado, ruta, id, numCorrida, 
					horario, claseServicio, marca, isPisoExtra, lsEsquemaTramo, isValidaActivo, isValidaTrecho)) {
				return null;
			}
			

			/* Como o numCorrida corresponde ao corrida_id do corridaCtrl, ou seja, é chave primaria, temos que buscar somente por esse campo
			Caso o registro já exista, atualizamos os dados dele. Sendo assim, o que vai indicar a "duplicidade" de uma corrida será apenas o número dela e não mais o conjunto : clase + marca + horario + ruta*/

			CorridaCtrl corridaCtrl = criaCorridaCtrl(isPisoExtra, ruta, claseServicio, marca, horario, numCorrida);

			corrida = criaCorrida(esquemaCorrida, dataGeracao, id, lsEsquemaTramo, origemCorrida, destinoCorrida,
					corridaCtrl, corridaTramoList, isValidaActivo);

			cantCorridaGenerada++;

			criaAssentoExclusivo(esquemaCorrida, corrida);

		} catch (Exception ex) {
			log.error("", ex);
		}

		return corrida;
	}

	private Corrida criaCorrida(EsquemaCorrida esquemaCorrida, Date dataGeracao, Corrida.Id id,
			List<EsquemaTramo> lsEsquemaTramo, Parada origemCorrida, Parada destinoCorrida, CorridaCtrl corridaCtrl, List<CorridaTramo> corridaTramoList, boolean isCorridaInativa) {
		Corrida corrida;
		corrida = new Corrida();
		corrida.setId(id);
		corrida.setEsquemaCorrida(esquemaCorrida);
		corrida.setCorridaCtrl(corridaCtrl);
		corrida.setAutobus(esquemaCorrida.getAutoBus()!= null ? esquemaCorrida.getAutoBus() : null);
		corrida.setClaseServicio(corridaCtrl.getClaseServicio());
		corrida.setConductor(null);
		if (esquemaCorrida.getIndCorridaExtra()) {
			corrida.setTipoServicio(new Short("2"));
		} else {
			corrida.setTipoServicio(new Short("1"));
		}
		corrida.setInfoCorrida(esquemaCorrida.getInfoCorrida());
		Integer cantparados = esquemaCorrida.getCantparados();
		if (cantparados != null) {
			corrida.setCantParados(cantparados.shortValue());
		}

		corrida.setEsquemaAgrupacionId(esquemaCorrida.getEsquemaAgrupacionId());

		corrida.setEmpresa(esquemaCorrida.getEmpresa());
		corrida.setEmpresa1(esquemaCorrida.getEmpresa1());

		corrida.setFechorsalida(truncarDataHora(dataGeracao, esquemaCorrida.getHorasalida()));
		corrida.setFechorSalidaOriginal(corrida.getFechorsalida());

		if (esquemaCorrida.getStatusCorrida().equals("A")) {
			corrida.setIndstatuscorrida("H");
			corrida.setActivo(ActivoUtil.ATIVO);
		} else if (esquemaCorrida.getStatusCorrida().equals("L")) {
			corrida.setIndstatuscorrida("P");
			corrida.setActivo(ActivoUtil.INATIVO);
		}

		corrida.setMarca(esquemaCorrida.getMarca());
		corrida.setNodo(corridaCtrl.getNodo());
		corrida.setDivision(esquemaCorrida.getDivision());
		corrida.setRuta(esquemaCorrida.getRuta());
		corrida.setRolOperativo(esquemaCorrida.getRolOperativo());
		corrida.setZona(null);
		corrida.setOrigem(origemCorrida);
		corrida.setDestino(destinoCorrida);
		
		validaFlexBus(esquemaCorrida, corrida);
		List<CorridaTramo> lsCorridaTramo = null;
		Date fechorSalidaOriginalH = null;
		if(corridaTramoList!=null && !corridaTramoList.isEmpty()) {
			lsCorridaTramo = corridaTramoList;
		}else {
			lsCorridaTramo = corridaTramosService.criaCorridaTramoList(esquemaCorrida.getAutoBus(), dataGeracao, fechorSalidaOriginalH, lsEsquemaTramo, corridaCtrl.getNodo(), corrida.getClaseServicio(), esquemaCorrida.getStatusCorrida());
			for (CorridaTramo corridaTramo : lsCorridaTramo) {
				corridaTramo.setCorrida(corrida);
			}
		}
		
		corrida.setCorridaTramoList(lsCorridaTramo);
		
		corrida.setFecmodif(Calendar.getInstance().getTime());
		
		if (UsuarioLogado.getUsuarioLogado() != null) {
			corrida.setUsuarioId(UsuarioLogado.getUsuarioLogado().getUsuarioId());
		} else {
			corrida.setUsuarioId(1);
		}
		corrida.setIndTipoVenta(null);
		corrida.setCodProdutoFiscal(UtiliteriasFiscal.defineCodigoProduto(corrida.getOrigem().getParadaId(), corrida.getDestino().getParadaId(), corrida.getRuta().getRutaId()));
		if(!isCorridaInativa) {
			corrida = corridaDAO.suscribir(corrida);			
		}else {
			corrida = corridaDAO.actualizacion(corrida);		
		}
		
		return corrida;
	}

	private CorridaCtrl criaCorridaCtrl(boolean isPisoExtra, Ruta ruta, ClaseServicio claseServicio, Marca marca,
			Date horario, Integer numCorrida) {
		CorridaCtrl corridaCtrl = corridaCtrlService.obtenerID(numCorrida);

		boolean novoRegistro = false;

		if (corridaCtrl == null) {
			corridaCtrl = new CorridaCtrl();
			novoRegistro = true;
		}

		corridaCtrl.setCorridaId(numCorrida);
		corridaCtrl.setMarca(marca);
		corridaCtrl.setRuta(ruta);
		corridaCtrl.setClaseServicio(claseServicio);
		corridaCtrl.setHorario(horario);

		// parada origem
		Parada origen = rutaService.getOrigem(ruta);
		if (origen == null) {
			log.info("origen nao encontrado para a ruta = " + ruta.getRutaId());
		}
		corridaCtrl.setNodo((origen == null) ? null : origen.getNodo());

		corridaCtrl.setActivo(Boolean.TRUE);
		corridaCtrl.setFecmodif(Calendar.getInstance().getTime());

		if (UsuarioLogado.getUsuarioLogado() != null) {
			corridaCtrl.setUsuarioId(UsuarioLogado.getUsuarioLogado().getUsuarioId());
		}

		corridaCtrl.setPisoExtra(isPisoExtra);
		
		if (novoRegistro) {
			corridaCtrl = corridaCtrlService.suscribir(corridaCtrl);
		} else {
			corridaCtrl = corridaCtrlService.actualizacion(corridaCtrl);
		}
		return corridaCtrl;
	}

	

	private void validaFlexBus(EsquemaCorrida esquemaCorrida, Corrida corrida) {
		if(esquemaCorrida.getIsFlexBus()) {
			corrida.setOcupacaoMinima(esquemaCorrida.getOcupacaoMinima());
			corrida.setHorasParaConfirmacao(esquemaCorrida.getHorasParaConfirmacao());
			corrida.setMinutosParaConfirmacao(esquemaCorrida.getMinutosParaConfirmacao());
			corrida.setIsFlexBus(Boolean.TRUE);
			
			corrida.setStatusFlexBus(StatusFlexBus.EM_RESERVA.getStatus());
		}
	}
	
	private Boolean realizaValidacoesGerais(EsquemaCorrida esquemaCorrida, Date dataGeracao, Integer diaSemana, Boolean isFeriado, 
			Ruta ruta, Corrida.Id id, Integer numCorrida, Date horario, ClaseServicio claseServicio, Marca marca, Boolean isPisoExtra, 
			List<EsquemaTramo> lsEsquemaTramo, boolean isGeraCorridaInativa, boolean isValidaTrecho ){
		Integer corridaId = esquemaCorrida.getNumCorrida();
		String fecCorrida = DateUtil.getStringDate(horario);
		if (!vigenciaValida(esquemaCorrida, dataGeracao)) {
			log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida + " vigencia invalida=" + dataGeracao);
			return Boolean.FALSE;
		}

		if (!((esquemaCorrida.getStatusCorrida() != null) && (esquemaCorrida.getStatusCorrida().equals("A") || esquemaCorrida.getStatusCorrida().equals("L")))) {
			log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida + "status corrida invalido=" + esquemaCorrida.getStatusCorrida());
			return Boolean.FALSE;
		}

		if (esquemaCorrida.getIndDiaSimDiaNao() == null || esquemaCorrida.getIndDiaSimDiaNao() == Boolean.FALSE) {
			if (!temCorridaDiaSemana(diaSemana, esquemaCorrida, isFeriado)) {
				log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida + "dia semana no valido=" + diaSemana);
				return Boolean.FALSE;
			}
		}

		if(!isGeraCorridaInativa) {
			if (!tramoServicioService.existenTiemposTramosEsquema(esquemaCorrida.getEsquemacorridaId())) {
				log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida +". Nao foi cadastrado o tempo de todos os trechos do esquema corrida=" + esquemaCorrida.getEsquemacorridaId());
				return Boolean.FALSE;
			}
		}

		if(!validaCorridaDiaSimDiaNao(esquemaCorrida, dataGeracao, numCorrida)) {
			return Boolean.FALSE;
		}

		if(!isGeraCorridaInativa) {
			if (corridaDAO.existe(id)) {
				log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida + ". corrida existe = " + numCorrida + ";" + ruta.getDescruta() + ";" + DateUtil.getStringDate(dataGeracao) + ";" + DateUtil.getStringDate(horario, "HH:mm") + ";" + claseServicio.getDescclase() + ";" + marca.getDescmarca() + " Piso: " + isPisoExtra);
				return Boolean.FALSE;
			}			
		}
		if(isValidaTrecho) {
			if(!realizaValidacoesTrecho(corridaId, fecCorrida, lsEsquemaTramo)) {
				return Boolean.FALSE;
			}			
		}
		
		
		if(marca !=null && Boolean.FALSE.equals(marca.getActivo())){
			log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida + ". MarcaId=" + marca.getMarcaId() +" inativa, corrida não pode ser gerada");
			return Boolean.FALSE;
		}
		
		return Boolean.TRUE;
	}
	
	private boolean realizaValidacoesTrecho(Integer corridaId, String fecCorrida,List<EsquemaTramo> lsEsquemaTramo ) {
		if (lsEsquemaTramo == null || lsEsquemaTramo.isEmpty()) {
			log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida + ". Não contem trechos (EsquemaTramo nulo ou vazio)");
			return Boolean.FALSE;
		}
		EsquemaTramo primeiroTrechoCorrida = lsEsquemaTramo.get(0);
		Parada origemCorrida = primeiroTrechoCorrida.getTramo().getOrigem();
		if (origemCorrida == null) {
			log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida + ". Origem não preenchido (Primeiro trecho EsquemaTramo)");
			return  Boolean.FALSE;
		}
		

		EsquemaTramo ultimoTrechoCorrida = lsEsquemaTramo.get(lsEsquemaTramo.size() - 1);
		Parada destinoCorrida = ultimoTrechoCorrida.getTramo().getDestino();
		if (destinoCorrida == null) {
			log.error("CorridaId:" + corridaId + " FecCorrida:" + fecCorrida + ". Destino não preenchido (Último trecho EsquemaTramo)");
			return Boolean.FALSE;
		}
		
		return true;
	}

	private Boolean validaCorridaDiaSimDiaNao(EsquemaCorrida esquemaCorrida, Date dataGeracao, Integer numCorrida ){
		if (Boolean.TRUE.equals(esquemaCorrida.getIndDiaSimDiaNao())) {
			Calendar calendarData = Calendar.getInstance();
			calendarData.setTime(dataGeracao);
			calendarData.add(Calendar.DATE, -1);

			Corrida.Id corridaOntem = new Corrida.Id();

			corridaOntem.setCorridaId(numCorrida);
			corridaOntem.setFeccorrida(DateUtil.normalizarToFecha(calendarData.getTime()));

			if (corridaDAO.existe(corridaOntem)) {
				log.info("Esquema configurado para gerar dia sim dia não e já possui corrida gerado no dia anterior, por isso não está data: " + DateUtil.getStringDate(dataGeracao));
				return Boolean.FALSE;
			}
		}
		return Boolean.TRUE;
	}
	
	private Calendar getCalendarFechorllegada(Date fecHorallegada) {
		Calendar llegada = Calendar.getInstance();
		llegada.setTime(fecHorallegada);
		llegada.set(llegada.get(Calendar.YEAR), llegada.get(Calendar.MONTH), llegada.get(Calendar.DATE),
				llegada.get(Calendar.HOUR_OF_DAY), llegada.get(Calendar.MINUTE), NumberUtils.INTEGER_ZERO);
		return llegada;
	}

	private Calendar getCalendarFechorSalida(Date fecHorSalida) {
		Calendar salida = Calendar.getInstance();
		salida.setTime(fecHorSalida);
		salida.set(salida.get(Calendar.YEAR), salida.get(Calendar.MONTH), salida.get(Calendar.DATE),
				salida.get(Calendar.HOUR_OF_DAY), salida.get(Calendar.MINUTE), NumberUtils.INTEGER_ZERO);
		return salida;
	}

	private void criaAssentoExclusivo(EsquemaCorrida esquemaCorrida, Corrida corrida) {
		List<EsquemaAsiento> lsEsquemaAsiento = esquemaAsientoService.obtenerPorCorrida(esquemaCorrida);
		for (EsquemaAsiento ea : lsEsquemaAsiento) {
			DetDiagramaAutobus detDiagramaAutobus = detDiagramaAutobusService.obtenerPorDiagramaAutobusAsiento(esquemaCorrida.getDiagramaAutobusId(), ea.getNumasiento());
			if (detDiagramaAutobus != null && detDiagramaAutobus.getVendible()) {
				AsientoExclusivo asientoExclusivo = new AsientoExclusivo();
				asientoExclusivo.setCorrida(corrida);
				asientoExclusivo.setMotivobloqueo(null);
				asientoExclusivo.setNodo(corrida.getNodo());
				asientoExclusivo.setNumasiento(ea.getNumasiento());
				asientoExclusivo.setVendible(ea.getIndvendible());
				asientoExclusivo.setParada(ea.getParada());
				asientoExclusivo.setTiempoLiberacion(ea.getTiempoLiberacion());
				if (ea.getIndvendible()) {
					asientoExclusivo.setMotivobloqueo(null);
				} else {
					asientoExclusivo.setMotivobloqueo(ea.getMotivobloqueo());
					asientoExclusivo.setDestino(ea.getDestino());
				}

				asientoExclusivo.setActivo(Boolean.TRUE);
				asientoExclusivo.setFecmodif(Calendar.getInstance().getTime());
				if (UsuarioLogado.getUsuarioLogado() != null) {
					asientoExclusivo.setUsuarioId(UsuarioLogado.getUsuarioLogado().getUsuarioId());
				}

				asientoExclusivo = asientoExclusivoService.suscribir(asientoExclusivo);
			}
		}
	}

	private Date calcularHusoHorVeranoLlegada(EsquemaTramo esquemaTramo, CorridaTramo corridaTramo) {

		Ciudad ciudadOrigen = esquemaTramo.getTramo().getOrigem().getCiudad();
		Estado estadoOrigen = ciudadOrigen.getEstado();
		
		int tiempoOrigen = estadoOrigen.getTiempoHorHuso() == null ? 0 : estadoOrigen.getTiempoHorHuso();

		Date fecInicioHorVerano = estadoOrigen.getFecInicioHorVerano();
		Date fecFinHorVerano = estadoOrigen.getFecFinoHorVerano();
		
		

		if ((fecInicioHorVerano != null) && (fecFinHorVerano != null)) {
			if ((DateUtil.compareOnlyDate(fecInicioHorVerano, corridaTramo.getFechorsalida()) <= 0) && (DateUtil.compareOnlyDate(fecFinHorVerano, corridaTramo.getFechorsalida()) >= 0)) {
				tiempoOrigen += estadoOrigen.getTiempoHorVerano();
			}
		}
		// 21688
		Ciudad ciudadDestino = esquemaTramo.getTramo().getDestino().getCiudad();
		Estado estadoDestino = ciudadDestino.getEstado();
		int tiempoDestino = 0;

		if (!estadoDestino.equals(estadoOrigen)) {
			tiempoDestino = estadoDestino.getTiempoHorHuso() == null ? 0 : estadoDestino.getTiempoHorHuso();
			
			

			Date fecInicioHorVeranoDestino = estadoDestino.getFecInicioHorVerano();
			Date fecFinHorVeranDestino = estadoDestino.getFecFinoHorVerano();

			if ((fecInicioHorVeranoDestino != null) && (fecFinHorVeranDestino != null)) {
				if ((DateUtil.compareOnlyDate(fecInicioHorVeranoDestino, corridaTramo.getFechorllegada()) <= 0) && (DateUtil.compareOnlyDate(fecFinHorVeranDestino, corridaTramo.getFechorllegada()) >= 0)) {
					tiempoDestino += estadoDestino.getTiempoHorVerano();
				}
			}
		} else {
			tiempoDestino =0;
			tiempoOrigen=0;
		}
		
		//Consideração do fuso por cidade. 
		//Nesse caso, é mandatório em relação ao estado.Ou seja, equanto que no fuso do estado, 
		//tem que haver Mudança de estado, para a cidade NÃO, pois pode-se considerar cada cidade
		//como um único estado. Sendo assim, devendo ser considerado sempre quando preenchido
		if ( (ciudadOrigen.getZonaHoraria() != null) && (ciudadOrigen.getZonaHoraria() != 0 )){
			log.info("Fuso da cidade origem " + ciudadOrigen.getNombciudad() + " : " + ciudadOrigen.getZonaHoraria());
			
			tiempoOrigen += ciudadOrigen.getZonaHoraria();
		}
		
		if ( (ciudadDestino.getZonaHoraria() != null) && (ciudadDestino.getZonaHoraria() != 0 )){
			log.info("Fuso da cidade destino " + ciudadDestino.getNombciudad() + " : " + ciudadDestino.getZonaHoraria());
			
			tiempoDestino += ciudadDestino.getZonaHoraria();
		}
		//Fim consideração fuso por cidade

		Calendar horLlegada = null;

		long elapsedMinutes = DateUtil.getElapsedMinutos(corridaTramo.getFechorsalida(), corridaTramo.getFechorllegada());

		horLlegada = Calendar.getInstance();
		horLlegada.setTime(corridaTramo.getFechorsalidaH());

		horLlegada.add(Calendar.MINUTE, (int) elapsedMinutes);
		
		// Si los tiempos son distintos, hube cambio de huso horario/horario de verano
		if (tiempoOrigen != tiempoDestino) {
			horLlegada.add(Calendar.HOUR, difHuso(tiempoOrigen, tiempoDestino));

		}

		return (horLlegada == null) ? null : horLlegada.getTime();
	}

	private int difHuso(int a, int b) {
		return b - a;
	}

	public Boolean generarCorrida(Date dataGeracao, List<EsquemaCorrida> lsEsquemaCorrida, boolean isGeracaoAutomaticaCorrida, List<CorridaTramo> corridaTramoList, boolean isValidaActivo) {
		
		DefaultTransactionDefinition def = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);

		// TODO: Melhorar o tratamento às transações manuais em caso de erro,etc
		TransactionStatus status = transactionManager.getTransaction(def);
		cantCorridaGenerada = 0;
		cantCorridaCommit = 1;
		java.sql.Date data = new java.sql.Date(dataGeracao.getTime());

		Integer unUsuarioId = constanteService.buscarValorConstantePorNomeConstante("USUARIO_RESERVA_CLIENTES");
		Integer unPuntoVenta= constanteService.buscarValorConstantePorNomeConstante("PUNTOVENTA_RESERVA_CLIENTES");
		Integer estacionId = constanteService.buscarValorConstantePorNomeConstante("ESTACION_RESERVA_CLIENTES"); 
		
		boolean corridaGerada = true;
		
		Corrida corrida = null;
		Corrida corridaPisoExtra = null;
		Corrida corridaPisoExtra2 = null;
		try {

			for (EsquemaCorrida esquemaCorrida : lsEsquemaCorrida) {
				log.info("esquema=" + esquemaCorrida.getEsquemacorridaId() + " numCorrida=" + esquemaCorrida.getNumCorrida());
				
				//Usado para validar se deve buscar o primeiro piso e vincular com o segungo/terceiro caso ele ja esteja criado
				boolean isSegundoOuTerceiroPiso = false;
				
				if (isGeracaoAutomaticaCorrida) {
					boolean esHijoRebote = esquemaCorridaDAO.esHijoRebote(esquemaCorrida);
					boolean esSegundoPiso = esquemaCorridaDAO.esSegundoPiso(esquemaCorrida);
					
					//Nessas duas condições, o "filho" só é gerado a partir do esquema corrida "pai"
					if (esHijoRebote ||  esSegundoPiso) {
						log.info(String.format("Esquema nao gerado. esHijoRebote:%s ; esSegundoPiso: %s",esHijoRebote,esSegundoPiso));
						corridaGerada = false;
						continue;
					}
				} else {
					// Caso nao seja automatico e o segundo piso seja enviado, busca o primeiro piso e geram os dois (ou tres pisos), 
					// caso o primeiro piso ja exista, o segundo e gerado e os dois sao vinculados
					boolean esHijoRebote = esquemaCorridaDAO.esHijoRebote(esquemaCorrida);
					
					//Nessas tres condições, o "filho" só é gerado a partir do esquema corrida "pai"
					if (esHijoRebote) {
						log.info("E segundo HijoRebote, procurando a corrida Pai. ");
						
						esquemaCorrida = esquemaCorridaDAO.buscarEsquemaPeloHijoRebote(esquemaCorrida);
						
						if(esquemaCorrida == null) {
							log.info(String.format("Esquema nao gerado. esHijoRebote:%s ; ",esHijoRebote));
							corridaGerada = false;
							continue;
						}
						log.info("Corrida Pai esquema=" + esquemaCorrida.getEsquemacorridaId() + " numCorrida=" + esquemaCorrida.getNumCorrida());
					}
					
					boolean esSegundoPiso = esquemaCorridaDAO.esSegundoPiso(esquemaCorrida);
					
					if(esSegundoPiso) {
						isSegundoOuTerceiroPiso = true;
						log.info("E segundo Piso, procurando o primeiro piso para gerar. ");
						esquemaCorrida = esquemaCorridaDAO.buscarPrimeiroPisoPeloSegundo(esquemaCorrida);
						
						if(esquemaCorrida == null) {
							log.info("Primeiro piso nao encontrado, nao ira gerar o servico: " );
							corridaGerada = false;
							continue;
						} 
						
						log.info("Primeiro Piso esquema=" + esquemaCorrida.getEsquemacorridaId() + " numCorrida=" + esquemaCorrida.getNumCorrida());
					}
					
					boolean esTerceiroPiso = esquemaCorridaDAO.esTerceiroPiso(esquemaCorrida);
					
					if(esTerceiroPiso) {
						isSegundoOuTerceiroPiso = true;
						log.info("E terceiro Piso, procurando o primeiro piso para gerar. ");
						esquemaCorrida = esquemaCorridaDAO.buscarPrimeiroPisoPeloTerceiro(esquemaCorrida);
						
						if(esquemaCorrida == null) {
							log.info("Primeiro piso nao encontrado, nao ira gerar o servico: " );
							corridaGerada = false;
							continue;
						}
						log.info("Primeiro Piso esquema=" + esquemaCorrida.getEsquemacorridaId() + " numCorrida=" + esquemaCorrida.getNumCorrida());
					}
				}
				
				if(esquemaCorrida.getIndCorridaDesativada()) {
					log.info("Corrida desativada nao sera gerada. " + esquemaCorrida.getEsquemacorridaId() + " numCorrida=" + esquemaCorrida.getNumCorrida());
					corridaGerada = false;
					continue;
				}
				
				Boolean isFeriado = this.isFeriado(data, esquemaCorrida);
				corrida = generarCorrida(esquemaCorrida, dataGeracao, false, isFeriado, corridaTramoList, isValidaActivo);
					
			
				corridaPisoExtra = null;
				corridaPisoExtra2 = null;

				if (corrida == null && !isSegundoOuTerceiroPiso) {
					corridaGerada = false;
					continue;
				
				} else if(corrida == null && isSegundoOuTerceiroPiso) {
					log.info(" Primeiro piso ja existe, buscando corrida para vincular ao piso extra");
					corrida = corridaDAO.buscarPorId(criarIdCorrida(dataGeracao, esquemaCorrida));
					
					if (corrida == null) {
						log.info("Nao foi encontrada corrida original, nao sera gerado o segundo/terceiro piso");
						corridaGerada = false;
						continue;
					}
				}

				// PISO EXTRA
				if (esquemaCorrida.getPisoExtra() != null && esquemaCorrida.getPisoExtra()) {
	
					EsquemaCorrida esquemaCorridaSegundoPiso = esquemaCorridaDAO.buscarPeloNumCorrida(esquemaCorrida.getNumCorridaPisoExtra());
					
					String mensagemLog = String.format("Esquema/Corrida primeiro piso: %s/%s ; Esquema/Corrida segundo piso: %s/%s ",esquemaCorrida.getEsquemacorridaId(),esquemaCorrida.getNumCorrida(), esquemaCorridaSegundoPiso.getEsquemacorridaId(),esquemaCorridaSegundoPiso.getNumCorrida() );
					log.info(mensagemLog);
					
					if(esquemaCorridaSegundoPiso.getIndCorridaDesativada()) {
						log.info("Corrida desativada nao sera gerada. " + esquemaCorrida.getEsquemacorridaId() + " numCorrida=" + esquemaCorrida.getNumCorrida());
						corridaGerada = false;
						continue;
					}
					
					corridaPisoExtra = generarCorrida(esquemaCorridaSegundoPiso, dataGeracao, true, isFeriado, null, false);
					Boolean atualizarPisoExtra = false;
					
					// caso corrida segundo piso for gerada antes, atualizar
					atualizarPisoExtra = atualizarCorridaPisoExtra(corridaPisoExtra, esquemaCorridaSegundoPiso, data, mensagemLog, atualizarPisoExtra);
															
					alterarNumPisoCorridaPisoExtra(corrida, corridaPisoExtra, mensagemLog, atualizarPisoExtra, false);
					
					//Valida se existe configuração de reserva para o cliente e crria Cria reserva para 2º piso
					if(corridaPisoExtra !=null) {
						if(unUsuarioId!=null && unPuntoVenta!=null && estacionId!=null) {
							validaCriaReservaCliente(corridaPisoExtra, unUsuarioId, unPuntoVenta, estacionId);								
						}else {
							log.error("Usuario ou ponto de venda ou estação não definidos para reserva especial nas contantes");
						}
					}
					
					if(esquemaCorrida.getNumCorridaPisoExtra2() != null) {
						
						EsquemaCorrida esquemaCorridaTerceiroPiso = esquemaCorridaDAO.buscarPeloNumCorrida(esquemaCorrida.getNumCorridaPisoExtra2());
						
						if(esquemaCorridaTerceiroPiso != null) {
							
							mensagemLog = String.format("Esquema/Corrida primeiro piso: %s/%s ; Esquema/Corrida terceiro piso: %s/%s ",esquemaCorrida.getEsquemacorridaId(),esquemaCorrida.getNumCorrida(), esquemaCorridaTerceiroPiso.getEsquemacorridaId(),esquemaCorridaTerceiroPiso.getNumCorrida() );
							log.info(mensagemLog);
							
							if(esquemaCorridaTerceiroPiso.getIndCorridaDesativada()) {
								log.info("Corrida desativada nao sera gerada. " + esquemaCorrida.getEsquemacorridaId() + " numCorrida=" + esquemaCorrida.getNumCorrida());
								corridaGerada = false;
								continue;
							}
							
							corridaPisoExtra2 = generarCorrida(esquemaCorridaTerceiroPiso, dataGeracao, true, isFeriado, null, false);
							atualizarPisoExtra = false;
							
							// caso corrida segundo piso for gerada antes, atualizar
							atualizarPisoExtra = atualizarCorridaPisoExtra(corridaPisoExtra2, esquemaCorridaTerceiroPiso, data, mensagemLog, atualizarPisoExtra);
												
							// caso corrida segundo piso for gerada antes, atualizar
							alterarNumPisoCorridaPisoExtra(corrida, corridaPisoExtra2, mensagemLog, atualizarPisoExtra, true);
							
							//Valida se existe configuração de reserva para o cliente e crria Cria reserva para 2º piso
							if(corridaPisoExtra !=null) {
								if(unUsuarioId!=null && unPuntoVenta!=null && estacionId!=null) {
									validaCriaReservaCliente(corridaPisoExtra, unUsuarioId, unPuntoVenta, estacionId);								
								}else {
									log.error("Usuario ou ponto de venda ou estação não definidos para reserva nas contantes");
								}
							}
						}
					}
				}

				if (esquemaCorrida.getEsquemaCorridaRebote() != null) {
					criaCorridaRebote(dataGeracao, corrida, corridaPisoExtra, corridaPisoExtra2, esquemaCorrida,
							isFeriado);
				}
				
				if(corrida != null) {
					generarCorridaInfo(corrida, esquemaCorrida);
				}
				
				if (cantCorridaGenerada >= cantCorridaCommit) {
					cantCorridaGenerada = 0;
					transactionManager.commit(status);
					status = transactionManager.getTransaction(def);
				}
				
			} 
		}catch (Exception e) {
			log.error("Erro ao gerar corrida", e );
		} 
		finally {
			if ((status != null) && (!status.isCompleted())) {
				transactionManager.commit(status);
			}
		}
		//Valida se existe configuração de reserva para o cliente e cria Cria reserva
		if(corrida !=null) {
			if(unUsuarioId!=null && unPuntoVenta!=null && estacionId!=null) {
				validaCriaReservaCliente(corrida, unUsuarioId, unPuntoVenta, estacionId);								
			}else {
				log.error("Usuario ou ponto de venda ou estação não definidos para reserva especial nas contantes");
			}
		}
		
		return corridaGerada;
	}

	private Id criarIdCorrida(Date dataGeracao, EsquemaCorrida esquemaCorrida) {
		Corrida.Id id = new Corrida.Id();
		id.setCorridaId(esquemaCorrida.getNumCorrida());
		id.setFeccorrida(DateUtil.normalizarToFecha(dataGeracao));
		
		return id;
	}

	private void criaCorridaRebote(Date dataGeracao, Corrida corrida, Corrida corridaPisoExtra,
			Corrida corridaPisoExtra2, EsquemaCorrida esquemaCorrida, Boolean isFeriado) {
		log.info("Corrida Rebote=" + esquemaCorrida.getEsquemacorridaId());

		Corrida corridaRebote = generarCorrida(esquemaCorrida.getEsquemaCorridaRebote(), dataGeracao, false, isFeriado, null, false);
		Corrida corridaPisoExtraRebote = null;
		boolean generaReboteDiaSeguinte = false;

		if (corridaRebote != null) {
			EsquemaCorrida ecRebote = esquemaCorrida.getEsquemaCorridaRebote();
			if (ecRebote.getPisoExtra() != null && ecRebote.getPisoExtra()) {
				
				EsquemaCorrida esquemaCorridaSegundoPiso = esquemaCorridaDAO.buscarPeloNumCorrida(ecRebote.getNumCorridaPisoExtra());
				
				log.info(String.format("Esquema/Corrida primeiro piso: %s/%s ; Esquema/Corrida segundo piso: %s/%s ",ecRebote.getEsquemacorridaId(),ecRebote.getNumCorrida(), esquemaCorridaSegundoPiso.getEsquemacorridaId(),esquemaCorridaSegundoPiso.getNumCorrida() ));
				
				corridaPisoExtraRebote = generarCorrida(esquemaCorridaSegundoPiso, dataGeracao, true, isFeriado, null, false);

				if (corridaRebote != null && corridaPisoExtraRebote != null) {
					// Salvando Referencia da Corrida no piso extra
					corridaPisoExtraRebote.setCorridaPiso(corridaRebote);
					corridaPisoExtraRebote.setNumPiso(2);
					corridaPisoExtraRebote = corridaDAO.actualizacion(corridaPisoExtraRebote);

					// Referencia do Piso extra na Corrida
					corridaRebote.setCorridaPiso(corridaPisoExtraRebote);
					corridaRebote.setNumPiso(1);
					corridaRebote = corridaDAO.actualizacion(corridaRebote);
				}
			}
			// RebotePisoExtra
			if (DateUtil.compareDate(corrida.getFechorsalida(), corridaRebote.getFechorsalida()) > 0) {
				generaReboteDiaSeguinte = true; // si fue generado con una fechamenor tienes que generar para el dia seguinte 
			}
		} else {
			generaReboteDiaSeguinte = true;// si no fue encontrado para el dia de hoy,genera para manhana
		}

		if (generaReboteDiaSeguinte) {
			log.info("Haciendo la busqueda por el esquema rebote para el dia seguiente pues ya existe al dia de hoy");

			corridaRebote = generarReboteDiaSeguinte(esquemaCorrida, dataGeracao);

			if (corridaPisoExtraRebote != null) {
				corridaPisoExtraRebote = generarReboteDiaSeguinte(esquemaCorrida, dataGeracao);
			}

			if (corridaRebote == null) {
				log.info("No fue posible generar la corrida rebote. No fue encontrado esquema para la fecha del dia seguinte a " + dataGeracao);
			}
		}
		if (corridaRebote != null) {
			log.info("Rebote generado=" + corridaRebote.getId());
			corrida.setCorridaRebote(corridaRebote);
			corridaDAO.actualizacion(corrida);
		}
		
		if (corridaPisoExtraRebote != null && corridaPisoExtra != null) {
			log.info("Rebote Piso Extra Generado=" + corridaPisoExtraRebote.getId());
			corridaPisoExtra.setCorridaRebote(corridaPisoExtraRebote);
			corridaDAO.actualizacion(corridaPisoExtra);
		}
		
		if (corridaPisoExtraRebote != null && corridaPisoExtra2 != null) {
			log.info("Rebote Piso Extra Generado=" + corridaPisoExtraRebote.getId());
			corridaPisoExtra2.setCorridaRebote(corridaPisoExtraRebote);
			corridaDAO.actualizacion(corridaPisoExtra2);
		}
	}

	private void validaCriaReservaCliente(Corrida corrida, Integer unUsuarioId, Integer unPuntoVenta, Integer estacionId) {
		try {
			List<ReservaEspecialCliente> reservaEspecialClienteList =  reservaEspecialClientService.bucarReservaEspecialByCorridaId(corrida.getId().getCorridaId().longValue()) ;
			if(reservaEspecialClienteList !=null) {
				for (ReservaEspecialCliente reservaEspecialCliente  : reservaEspecialClienteList) { 
					Date dataAtual = new Date ();
					//Valida se esta dentro da vigência
					if(DateUtil.isMenor(reservaEspecialCliente.getFecinicio(), dataAtual) && DateUtil.isMaior(reservaEspecialCliente.getFecfinal(), dataAtual)){
						Integer unoTerminalOrigenId = reservaEspecialCliente.getOrigem().getParadaId();
						Integer unoTerminalDestinoId = reservaEspecialCliente.getDestino().getParadaId();
						String unoAsientoSelec = reservaEspecialCliente.getNumAsentoCorrida().toString();
						Integer unaCorridaId =corrida.getId().getCorridaId().intValue(); 
						Date unaFecCorrida= corrida.getId().getFeccorrida();
						Integer unoTipoVenta= TPV_RESERVA_NORMAL;
						Integer unaCategoriaSelec= 1 ;
						String unoNumeOperacion= null ;
						Tramo tramo = null;
						Date fecViaje = corrida.getFechorsalida();
						Ruta ruta = rutaService.obtenerID(corrida.getRuta().getRutaId());
						for(RutaCombinacion rutaCombinacion :ruta.getRutaCombinacionList()) {
							if((rutaCombinacion.getTramo().getOrigem().getParadaId().intValue()==unoTerminalOrigenId.intValue())
									&& (rutaCombinacion.getTramo().getDestino().getParadaId().intValue()==unoTerminalDestinoId.intValue()) ) {
								tramo = rutaCombinacion.getTramo();
								break;
							}
						}
						
						for (CorridaTramo corridaTramo : corrida.getCorridaTramoList()) {
							//(Se origem corridatramo = origem cadastrada && destino corridatramo = destino cadastrado)
							// ou (se origem corridatramo = destino do cadastrado), tendo em vista que corridatramo são as sequencias e não as combinações 
							if(corridaTramo.getOrigem()!=null && corridaTramo.getOrigem().getParadaId().intValue()==unoTerminalOrigenId.intValue()
									&& corridaTramo.getDestino()!=null && corridaTramo.getDestino().getParadaId().intValue()==unoTerminalDestinoId.intValue()) {
								fecViaje = corridaTramo.getFechorsalidaH();
								break;
							}
						}
						if(fecViaje!=null) {
							if(!isDiaSemnanaOK(reservaEspecialCliente, fecViaje)) {
								log.error("Reserva não será realizada. Dia da semana desta corrida não corresponde ao dia da semana configurado na resserva epecial para o clienteId:" 
							+ reservaEspecialCliente.getClienteId() + " reservaEspecialClienteId:" + reservaEspecialCliente.getReservaEspecialClienteId() + " para a corrida:" + corrida.getId().getCorridaId() + "na data" + fecViaje);
								continue;
							}
						}
						
						if(tramo==null) {
							log.error("Reserva não será realizada. não foi encontrado trecho com Origem:" + unoTerminalOrigenId + " Destino:" +unoTerminalDestinoId + " para a corrida:" + corrida.getId().getCorridaId());
							continue;
						}
						List<VigenciaTarifa> vigenciaTarifaList = vigenciaTarifaService.buscarVigenciasIntervalo(new VigenciaTarifa(corrida.getFechorSalidaOriginal(), corrida.getFechorSalidaOriginal()));
						if(vigenciaTarifaList==null || vigenciaTarifaList.isEmpty()) {
							log.error("Reserva não será realizada. não foi encontrado Vigência Tarifa com Origem:" + unoTerminalOrigenId + " Destino:" +unoTerminalDestinoId + " para a corrida:" + corrida.getId().getCorridaId());
							continue;
						}
						Tarifa tarifa = tarifaService.buscar(tramo, corrida.getMarca(), corrida.getClaseServicio(), vigenciaTarifaList!=null ? vigenciaTarifaList.get(0) : null, new Moneda(1), corrida.getRuta());
						if(tarifa==null ) {
							log.error("Reserva não será realizada. não foi encontrado Tarifa com Origem:" + unoTerminalOrigenId + " Destino:" +unoTerminalDestinoId + " para a corrida:" + corrida.getId().getCorridaId());
							continue;
						}
						BigDecimal valorTarifa= tarifa!=null ? tarifa.getPrecio() : null ;
						BigDecimal valorSeguro= tarifa!=null ? tarifa.getImporteseguro() : null ;
						BigDecimal valorTaxaEmbarque= tarifa!=null ? tarifa.getImportetaxaembarque() : null ;
						BigDecimal valorPedagio =  tarifa!=null ? tarifa.getImportepedagio() : null ;
						Long tempoReserva = null;
						if(reservaEspecialCliente.getClienteId() ==null) {
							log.error("Cliente id não foi cadastrado (null) para reservaId=" + reservaEspecialCliente.getReservaEspecialClienteId() + " da corrida_id =" + reservaEspecialCliente.getCorridaId() );
							continue;
						}
						Cliente cliente = clienteService.obtenerID(reservaEspecialCliente.getClienteId().intValue());
						if(cliente ==null || !Boolean.TRUE.equals(cliente.getActivo())) {
							continue;
						}
						String  clienteFidelidadId = null;
						try {
							clienteFidelidadId = cliente.getListClienteFidelidad().get(0).getClienteFidelidadId().toString();
						} catch (Exception e) {
							log.error("Boleto Reserva não gerado. Erro ao obter cliente fidelidade para o clienteId:" + cliente.getClienteId(), e );
							continue;
						}
						boletoService.criaBoletoReserva(unoAsientoSelec, unaCorridaId,  unaFecCorrida,  unoTerminalOrigenId, 
								unoTerminalDestinoId, unoTipoVenta,  unaCategoriaSelec,  unoNumeOperacion,  unUsuarioId,  unPuntoVenta,   
								reservaEspecialCliente.getClienteId().intValue(),  corrida.getRuta().getRutaId(),  valorTarifa,  tempoReserva, 
								estacionId, cliente.getNumIdentificaUno(), clienteFidelidadId, 
								corrida.getClaseServicio().getClaseservicioId(), corrida.getMarca().getMarcaId(), fecViaje, corrida.getEmpresa().getEmpresaId(), 
								cliente.getNombcliente(), valorSeguro, valorTaxaEmbarque, valorPedagio);
					}
				}
			}
		} catch (Exception e) {
			log.error("Erro ao validar/criar reserva especial de clientes para corridaId: " + corrida.getId().getCorridaId() , e);
		}
	}

	/**
	 * Valida se o dia da semana desta corrida está configurado para gerar reserva especial de clientes
	 * @param reservaEspecialCliente
	 * @param fecViaje
	 * @return
	 */
	private boolean isDiaSemnanaOK(ReservaEspecialCliente reservaEspecialCliente, Date fecViaje) {
		int diaSemanaInt = DateUtil.getDiaDaSemana(fecViaje); 
		boolean isDiaSemanaOK = false;
		switch (diaSemanaInt) {
		case 1:
			isDiaSemanaOK = Boolean.TRUE.equals(reservaEspecialCliente.getIndDomingo()) ? true :false ;
			break;
		case 2:
			isDiaSemanaOK = Boolean.TRUE.equals(reservaEspecialCliente.getIndSegunda()) ? true :false ;
			break;
		case 3:
			isDiaSemanaOK = Boolean.TRUE.equals(reservaEspecialCliente.getIndTerca()) ? true :false ;
			break;
		case 4:
			isDiaSemanaOK = Boolean.TRUE.equals(reservaEspecialCliente.getIndQuarta()) ? true :false ;
			break;
		case 5:
			isDiaSemanaOK = Boolean.TRUE.equals(reservaEspecialCliente.getIndQuinta()) ? true :false ;
			break;
		case 6:
			isDiaSemanaOK = Boolean.TRUE.equals(reservaEspecialCliente.getIndSexta()) ? true :false ;
			break;
		case 7:
			isDiaSemanaOK = Boolean.TRUE.equals(reservaEspecialCliente.getIndSabado()) ? true :false ;
			break;
		default:
			break;
		}
		return isDiaSemanaOK;
	}

	private void alterarNumPisoCorridaPisoExtra(Corrida corrida, Corrida corridaPisoExtra, String mensagemLog, Boolean atualizarPisoExtra, boolean isPiso3) {
		if (corrida != null && corridaPisoExtra != null) {
			log.info(mensagemLog + "; atualizando as corridas do piso 1 e 2");
			// Salvando Referencia da Corrida no piso extra
			corridaPisoExtra.setCorridaPiso(corrida);
			corridaPisoExtra.setNumPiso(isPiso3 ? 3 : 2);
			corridaPisoExtra = corridaDAO.actualizacion(corridaPisoExtra);
			// Referencia do Piso extra na Corrida
			corrida.setCorridaPiso(corridaPisoExtra);
			corrida.setNumPiso(1);
			corrida = corridaDAO.actualizacion(corrida);
			
			if(atualizarPisoExtra) {
				corridaDAO.actualizacion(corridaPisoExtra);
			}
		} else {
			log.info(mensagemLog + "; nao foram atualizadas as corridas do piso 1 e 2");
		}
	}

	private Boolean atualizarCorridaPisoExtra(Corrida corridaPisoExtra, EsquemaCorrida esquemaCorridaSegundoPiso, java.sql.Date data, String mensagemLog, Boolean atualizarPisoExtra) {
		// caso corrida segundo piso for gerada antes, atualizar
		if(corridaPisoExtra == null) {
			Id id = new Id();
			id.setCorridaId(esquemaCorridaSegundoPiso.getNumCorrida());
			id.setFeccorrida(data);
			
			corridaPisoExtra = corridaDAO.buscarPorId(id);
			
			atualizarPisoExtra = corridaPisoExtra == null ? false : true;
			log.info(mensagemLog + "; atualizarPisoExtra=" + atualizarPisoExtra);
		}
		
		return atualizarPisoExtra;
	}

	/**
	 * Verifica se hay una excepcione de tiempo para el tramo informado.
	 * 
	 * @param corridaTramo
	 * @param claseServicio
	 * @return - Si existe una excepcion, regresa la nueva fecLlegada para el tramo, sino, regreso null
	 */
	private Date arreglaExcepcionTiempo(CorridaTramo corridaTramo, ClaseServicio claseServicio) {
		Date nuevaFecLlegada = null;

		Tramo tramo = corridaTramo.getTramo();

		List<TramoTiempo> lsExcepciones = tramoTiempoDAO.buscar(tramo, claseServicio, corridaTramo.getFechorsalida(), corridaTramo.getFechorllegada());

		if (!lsExcepciones.isEmpty()) {
			TramoTiempo tramoTiempo = lsExcepciones.get(0);
			List<Integer> diasSemana = getDiaDeLaSemana(tramoTiempo);

			Calendar fecCorrida = Calendar.getInstance();
			fecCorrida.setTime(corridaTramo.getFechorllegada());

			if (diasSemana.contains(fecCorrida.get(Calendar.DAY_OF_WEEK))) {

				Calendar fechorsalida = Calendar.getInstance();
				fechorsalida.setTime(corridaTramo.getFechorsalida());

				HoraSistema horaSistema = new HoraSistema(tramoTiempo.getTiemporecorrido());

				fechorsalida.add(Calendar.HOUR, horaSistema.getHora());
				fechorsalida.add(Calendar.MINUTE, horaSistema.getMinuto());

				nuevaFecLlegada = fechorsalida.getTime();
			}
		}

		return nuevaFecLlegada;
	}

	private Corrida generarReboteDiaSeguinte(EsquemaCorrida esquemaCorrida, Date dataGeracao) {
		Calendar tmp = Calendar.getInstance();
		tmp.setTime(dataGeracao);
		tmp.add(Calendar.DAY_OF_MONTH, 1);
		java.sql.Date data = new java.sql.Date(dataGeracao.getTime());
		Boolean isFeriado = this.isFeriado(data, esquemaCorrida);

		return generarCorrida(esquemaCorrida.getEsquemaCorridaRebote(), tmp.getTime(), false, isFeriado, null, false);
	}

	private List<Integer> getDiaDeLaSemana(TramoTiempo tramoTiempo) {
		List<Integer> diasValidos = new ArrayList<Integer>();

		if (tramoTiempo.getIndapliclun()) {
			diasValidos.add(Calendar.MONDAY);
		}
		if (tramoTiempo.getIndaplicmar()) {
			diasValidos.add(Calendar.TUESDAY);
		}
		if (tramoTiempo.getIndaplicmie()) {
			diasValidos.add(Calendar.WEDNESDAY);
		}
		if (tramoTiempo.getIndaplicjue()) {
			diasValidos.add(Calendar.THURSDAY);
		}
		if (tramoTiempo.getIndaplicvie()) {
			diasValidos.add(Calendar.FRIDAY);
		}
		if (tramoTiempo.getIndaplicsab()) {
			diasValidos.add(Calendar.SATURDAY);
		}
		if (tramoTiempo.getIndaplicdom()) {
			diasValidos.add(Calendar.SUNDAY);
		}

		return diasValidos;
	}

	@Override
	public Date calcularDataFinalGeneracionCorrida() {
		Date dataDe = new Date();

		Constante constante = constanteService.buscarPorNomeConstante("CANT_DIAS_GENERACION_CORRIDA");
		Calendar calendarData = Calendar.getInstance();
		calendarData.setTime(dataDe);
		calendarData.add(Calendar.DATE, Integer.parseInt(constante.getValorconstante()));
		Date dataAte = calendarData.getTime();

		return dataAte;
	}
	
	private boolean generaCorridaAprimorada() {
		try {
			Constante constante = constanteService.buscarPorNomeConstante("GENERACION_CORRIDA_APRIMORADA");
			return (constante != null && StringUtils.isNotBlank(constante.getValorconstante())
					&& Boolean.valueOf(constante.getValorconstante())) ? Boolean.TRUE : Boolean.FALSE;
		} catch (Exception e) {
		}

		return Boolean.FALSE;
	}

	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public boolean generarCorridaAutomatica() {
		
		if (generaCorridaAprimorada()) {
			return generarCorridaAutomaticaAprimorada();
		} else {
			return generarCorridaAutomaticaPadrao();
		}
	}
	
	private boolean generarCorridaAutomaticaPadrao() {
		feriadoCache.limpar();

		Date dataDe = Calendar.getInstance().getTime();

		Date dataAteConstante = calcularDataFinalGeneracionCorrida();
		Boolean hayErroConstante = !generarCorridaAutomaticaConstanteDias(dataDe, dataAteConstante);

		log.debug("corrida generadas por la constante. hayError=" + hayErroConstante);

		Boolean hayErroQtdeDias = !generarCorridaAutomaticaQtdeDias(dataDe);

		log.debug("corrida generadas por la cant de dias.hayError=" + hayErroQtdeDias);

		return hayErroConstante && hayErroQtdeDias;
	}
	
	private boolean generarCorridaAutomaticaAprimorada() {
		feriadoCache.limpar();
	
		Date dataDe = Calendar.getInstance().getTime();
	
		Date dataAteConstante = calcularDataFinalGeneracionCorrida();
		Boolean hayErroConstante = !generarCorridaAutomaticaAprimorada(dataDe, dataAteConstante);
	
		log.debug("corrida generadas aprimorada por la cant de dias.hayError=" + hayErroConstante);
	
		return hayErroConstante;
	}

	private boolean generarCorridaAutomaticaQtdeDias(Date dataDeOriginal) {
		SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");

		log.debug(" Generacion cant dias " + sdf.format(dataDeOriginal));

		// TODO: Melhorar o tratamento às transações manuais em caso de erro,etc
		DefaultTransactionDefinition def = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
		TransactionStatus status = transactionManager.getTransaction(def);

		List<EsquemaCorrida> lsEsquemaCorrida = esquemaCorridaService.buscaDentroVigenciaQtdeDiasNotNull(dataDeOriginal);
		for (EsquemaCorrida esquemaCorrida : lsEsquemaCorrida) {
			log.debug("esquemaCorrida=" + esquemaCorrida.getEsquemacorridaId());

			Integer qtdeDiasGerar = esquemaCorrida.getCantDiasGeneracion();

			if (qtdeDiasGerar <= 0) {
				log.debug("cant dias <=0");

				continue;
			}

			log.debug("cantDiasGenerar=" + qtdeDiasGerar);

			Calendar calendarData = Calendar.getInstance();
			calendarData.setTime(dataDeOriginal);
			calendarData.add(Calendar.DATE, qtdeDiasGerar - 1);

			Date dataAte = calendarData.getTime();
			Date dataDe = new Date(dataDeOriginal.getTime());

			log.debug("Periodo generacion = " + sdf.format(dataDe) + " hasta " + sdf.format(dataAte));

			if (DateUtil.compareOnlyDate(dataAte, esquemaCorrida.getEsquemaOperacional().getFecfinvigencia()) > 0) {
				log.info("Cant dias excede final de la vigencia");
				dataAte = esquemaCorrida.getEsquemaOperacional().getFecfinvigencia();
			}

			try {
				while (DateUtil.compareOnlyDate(dataDe, dataAte) <= 0) {
					
					
					List<EsquemaCorrida> lsTmp = new ArrayList<EsquemaCorrida>();
					lsTmp.add(esquemaCorrida);
					
					this.generarCorrida(dataDe, lsTmp, true, null, false);
					
					calendarData.setTime(dataDe);
					calendarData.add(Calendar.DATE, 1);
					dataDe = calendarData.getTime();
				}
				log.info("commit bloco corridas");
				transactionManager.commit(status);
				status = transactionManager.getTransaction(def);
			} catch (Exception ex) {
				log.error("", ex);
				log.error(" Erro ao gerar corrida = " + esquemaCorrida.getNumCorrida() + " esquemaCorrida = " + esquemaCorrida.getEsquemacorridaId());
				transactionManager.rollback(status);
				status = transactionManager.getTransaction(def);
			}
		}

		if (!status.isCompleted()) {
			log.info("commit bloco final");
			transactionManager.commit(status);
		}

		return Boolean.TRUE;
	}

	private boolean generarCorridaAutomaticaConstanteDias(Date dataDe, Date dataAte) {
		boolean generacionExitosa = true;
		SimpleDateFormat sdfData = new SimpleDateFormat("dd/MM/yyyy");

		log.debug("** AUTO - Data De: " + sdfData.format(dataDe) + " **Data Ateh: " + sdfData.format(dataAte));

		Calendar calendarData = Calendar.getInstance();
		calendarData.setTime(dataAte);
		calendarData.add(Calendar.DATE, 1);
		dataAte = calendarData.getTime();
		try {
			while (dataDe.before(dataAte)) {
				log.debug("*** Data: " + sdfData.format(dataDe));

				List<EsquemaCorrida> lsEsquemaCorrida = esquemaCorridaService.buscaDentroVigenciaQtdeDiasNull(dataDe);

				Boolean dentroVigenciaEsquemaOperacional = !lsEsquemaCorrida.isEmpty();
				if (dentroVigenciaEsquemaOperacional) {
					log.debug("Geracao de servicos sem cant dias especifico. Adotando o padrao");

					this.generarCorrida(dataDe, lsEsquemaCorrida, true, null, false);

				} else {
					log.info("No hay esquema vigente para la fecha " + sdfData.format(dataDe));
				}

				calendarData.setTime(dataDe);
				calendarData.add(Calendar.DATE, 1);
				dataDe = calendarData.getTime();
			}
		} catch (Exception ex) {
			log.error("Erro ao gerar corridas: " + ex, ex);
			generacionExitosa = false;
		}

		return generacionExitosa;
	}
	
	private boolean generarCorridaAutomaticaAprimorada(final Date dataInico, final Date dataConstanteFim) {
		boolean generacionExitosa = true;
		SimpleDateFormat sdfData = new SimpleDateFormat("dd/MM/yyyy");

		log.debug("** AUTO - Data De: " + sdfData.format(dataInico) + " **Data Ateh: " + sdfData.format(dataConstanteFim));

		// TODO: Melhorar o tratamento às transações manuais em caso de erro,etc
		DefaultTransactionDefinition def = new DefaultTransactionDefinition(DefaultTransactionDefinition.PROPAGATION_REQUIRES_NEW);
		TransactionStatus status = transactionManager.getTransaction(def);

		Calendar calendarData = Calendar.getInstance();
		calendarData.setTime(dataConstanteFim);

		
		Date dataAte = calendarData.getTime();

		try {
			log.info("*** Data: " + sdfData.format(dataAte));
			List<EsquemaCorrida> lsEsquemaCorrida = esquemaCorridaService.buscaDentroVigencia(dataInico);

			Date dataInicial = DateUtil.normalizarToFecha(dataInico);
			
			Boolean dentroVigenciaEsquemaOperacional = !lsEsquemaCorrida.isEmpty();
			if (dentroVigenciaEsquemaOperacional) {

				for (EsquemaCorrida esquemaCorrida : lsEsquemaCorrida) {
					log.debug("esquemaCorrida=" + esquemaCorrida.getEsquemacorridaId());

					Integer qtdeDiasGerar = esquemaCorrida.getCantDiasGeneracion();

					if (qtdeDiasGerar != null && qtdeDiasGerar >= 0) {
						log.info("cantDiasGenerar=" + qtdeDiasGerar);

						Calendar calendarDiasGenerar = Calendar.getInstance();
						calendarDiasGenerar.setTime(dataInicial);
						calendarDiasGenerar.add(Calendar.DATE, qtdeDiasGerar);
						dataAte = DateUtil.normalizarToFecha(calendarDiasGenerar.getTime());
					} else {
						calendarData.setTime(dataConstanteFim);
						dataAte = DateUtil.normalizarToFecha(calendarData.getTime());
					}
					SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
					log.debug("Periodo generacion = " + sdf.format(dataInicial) + " hasta " + sdf.format(dataAte));

					if (DateUtil.compareOnlyDate(dataAte, esquemaCorrida.getEsquemaOperacional().getFecfinvigencia()) > 0) {
						log.info("Cant dias excede final de la vigencia");
						dataAte = esquemaCorrida.getEsquemaOperacional().getFecfinvigencia();
					}

					try {
						while (DateUtil.compareOnlyDate(dataAte, dataInicial) >= 0) {

							boolean corridaExiste = corridaDAO.existe(new Corrida.Id(esquemaCorrida.getNumCorrida(), dataAte));
							String strDataGeracao = DateUtil.getStringDate(dataAte);
							
							log.info(String.format("Corrida existe = %s [%s - %s]",corridaExiste,esquemaCorrida.getNumCorrida() ,strDataGeracao));
							
							//A corrida já existir é a única condição para parar de gerar.
							//Se a corrida não for gerada (rotina mais abaixo), podem haver várias condições: o dia da semana, feriado, etc
							if (corridaExiste){
								log.info(String.format("Corrida do dia já gerada ", esquemaCorrida.getNumCorrida(), strDataGeracao));
								
								break;
							}
							
							boolean corridaGerada = this.generarCorrida(dataAte, Arrays.asList(esquemaCorrida), false, null, false);
							
							log.info(String.format("Corrida gerada = %s [%s - %s]",corridaGerada,esquemaCorrida.getNumCorrida() ,strDataGeracao));
							
							calendarData.setTime(dataAte);
							calendarData.add(Calendar.DATE, -1);
							dataAte = calendarData.getTime();
						}

						log.info("commit bloco corridas");

						transactionManager.commit(status);
						status = transactionManager.getTransaction(def);
					} catch (Exception ex) {
						log.error("", ex);
						log.error(" Erro ao gerar corrida = " + esquemaCorrida.getNumCorrida() + " esquemaCorrida = " + esquemaCorrida.getEsquemacorridaId());
						transactionManager.rollback(status);
						status = transactionManager.getTransaction(def);
					}
				}

			} else {
				log.info("No hay esquema vigente para la fecha " + sdfData.format(dataInicial));
			}

		} catch (Exception ex) {
			log.error("Erro ao gerar corridas: " + ex, ex);
			generacionExitosa = false;
		}

		return generacionExitosa;
	}

	private int cantDiasMaxGerarCorridasManual() {

		Constante constante = constanteService.buscarPorNomeConstante("CANT_MAX_CORRIDA_GERA_MANUAL");

		if ((constante == null) || (StringUtils.isBlank(constante.getValorconstante()))) {
			return CANT_MAX_CORRIDA_GERA_MANUAL;
		}

		return Integer.valueOf(constante.getValorconstante());

	}

	public Long generarCorridasSelecionadas(List<EsquemaCorrida> lsEsquemaCorrida, Date dataDe, Date dataAte) throws BusinessException {

		int cantDiasGeneracion = DateUtil.getElapsedDias(dataDe, dataAte);

		int cantDiasMaxGerarCorridasManual = cantDiasMaxGerarCorridasManual();

		log.info("cantDiasGeneracion=" + cantDiasGeneracion);
		log.info("cantDiasMaxGerarCorridasManual=" + cantDiasMaxGerarCorridasManual);

		if (cantDiasGeneracion > cantDiasMaxGerarCorridasManual()) {
			throw new BusinessException("corridaServiceImpl.geracaoCorrida.cantDiasManual", new Object[] { cantDiasMaxGerarCorridasManual });
		}

		SimpleDateFormat sdfData = new SimpleDateFormat("dd/MM/yyyy");

		log.info("** SELECIONADOS - Data De: " + sdfData.format(dataDe) + " **Data Ateh: " + sdfData.format(dataAte));

		Calendar calendarData = Calendar.getInstance();
		calendarData.setTime(dataDe);

		Long cantInicio = corridaDAO.cantCorridaGeneradasHoy();

		while (DateUtil.compareOnlyDate(dataDe, dataAte) <= 0) {
			log.info("*** Data: " + sdfData.format(dataDe));

			this.generarCorrida(dataDe, lsEsquemaCorrida, false, null, false);

			calendarData.add(Calendar.DATE, 1);
			dataDe = calendarData.getTime();
		}

		Long cantFim = corridaDAO.cantCorridaGeneradasHoy();

		return cantFim - cantInicio;
	}

	public List<Corrida> buscarGroupCorrridaId(Integer corridaId, Date dateInicio, Date dateFin) {
		GregorianCalendar gCalendarDe = new GregorianCalendar();
		gCalendarDe.setTime(dateInicio);
		gCalendarDe.set(Calendar.HOUR_OF_DAY, 0);
		gCalendarDe.set(Calendar.MINUTE, 0);
		gCalendarDe.set(Calendar.SECOND, 0);
		gCalendarDe.set(Calendar.MILLISECOND, 0);

		GregorianCalendar gCalendarAte = new GregorianCalendar();
		gCalendarAte.setTime(dateFin);
		gCalendarAte.set(Calendar.HOUR_OF_DAY, 0);
		gCalendarAte.set(Calendar.MINUTE, 0);
		gCalendarAte.set(Calendar.SECOND, 0);
		gCalendarAte.set(Calendar.MILLISECOND, 0);

		return corridaDAO.buscarGroupCorrridaId(corridaId, gCalendarDe.getTime(), gCalendarAte.getTime());
	}

	@Override
	public List<Corrida> buscarGroupCorrridaId(Integer corridaId, Integer origem, Integer destino, Integer ruta, Integer numRuta, Date dateInicio, Date dateFin) {
		GregorianCalendar gCalendarDe = new GregorianCalendar();
		gCalendarDe.setTime(dateInicio);
		gCalendarDe.set(Calendar.HOUR_OF_DAY, 0);
		gCalendarDe.set(Calendar.MINUTE, 0);
		gCalendarDe.set(Calendar.SECOND, 0);
		gCalendarDe.set(Calendar.MILLISECOND, 0);

		GregorianCalendar gCalendarAte = new GregorianCalendar();
		gCalendarAte.setTime(dateFin);
		gCalendarAte.set(Calendar.HOUR_OF_DAY, 0);
		gCalendarAte.set(Calendar.MINUTE, 0);
		gCalendarAte.set(Calendar.SECOND, 0);
		gCalendarAte.set(Calendar.MILLISECOND, 0);

		return corridaDAO.buscarGroupCorrridaId(corridaId, origem, destino, ruta, numRuta, gCalendarDe.getTime(), gCalendarAte.getTime());
	}

	@Override
	public Boolean atualizarCorridaFecHusoFecVerano(Estado estado, Date dataInicial, Integer corridaId) {
		try {
			List<Corrida> corridas = corridaDAO.buscarPorEstado(estado, dataInicial, corridaId);
			corridaDAO.actualizaFecHusoFecVerano(corridas);
			return Boolean.TRUE;

		} catch (Exception ex) {
			log.error("Erro ao atualizar corridas FecHuso FecVerano: " + ex, ex);
		}
		return Boolean.FALSE;

	}

	@Override
	public Boolean atualizarCorridaFecHusoFecVerano(Estado estado, Date dataInicial) {
		return atualizarCorridaFecHusoFecVerano(estado, dataInicial, null);
	}

	@Override
	public List<Corrida> buscarCorridaRelAproveitamento(Parada origem, Parada destino,
			Date feccorrida, Integer corridaId) {

		List<Corrida> ls = new ArrayList<Corrida>();
		ls =corridaDAO.buscarCorridaRelAproveitamento(origem, destino, feccorrida, corridaId);
		return ls;

	}

	@Override
	public Corrida buscaCorrridaFutura(Ruta ruta, Date fecInicio) {
		return corridaDAO.buscaCorrridaFutura(ruta, fecInicio);
	}

	@Override
	@Transactional
	public Boolean editarCorridaPorOutraCorrida(Estado estado, Date dataInicial, Integer corridaId, Date dataPadrao) {
		List<Corrida> corridas = corridaDAO.buscarPorEstado(estado, dataInicial, corridaId);
		
		if (corridas == null || corridas.isEmpty()){
			return false;
		}
		
		Corrida corrida =  corridaDAO.buscarPorId(new Corrida(corridaId, dataPadrao).getId());
		if(corrida != null){
			corridaDAO.editarCorridaPorOutraCorrida(corridas,corrida);
		}else {
			return false;
		}
		return true;
	}

	public Integer buscarOcupacaoCorrida(Corrida corrida) {
		return corridaDAO.buscarOcupacaoCorrida(corrida);
	}

	@Override
	public Corrida buscarPorId(Id id) {
		// TODO Auto-generated method stub
		return corridaDAO.buscarPorId(id);
	}
	
	@Override
	public Corrida buscarCorridaAtivaPorId(Id id) {
		return corridaDAO.buscarCorridaAtivaPorId(id);
	}
	
	private void generarCorridaInfo(Corrida corrida, EsquemaCorrida esquemaCorrida) {
		
		if(corrida != null && corrida.getId() != null && corrida.getId().getFeccorrida() != null && corrida.getId().getCorridaId() != null) {
			
			if(esquemaCorrida != null) {
				
				List<EsquemaCorridaInfo> lsEsquemaCorridaInfo = esquemaCorridaInfoService.obtenerPorEsquemaCorrida(esquemaCorrida);
				
				if(lsEsquemaCorridaInfo != null) {
					
					for (EsquemaCorridaInfo esquemaCorridaInfo : lsEsquemaCorridaInfo) {
						
						CorridaInfo corridaInfo = new CorridaInfo();
						corridaInfo.setOrigem(esquemaCorridaInfo.getOrigem());
						corridaInfo.setDestino(esquemaCorridaInfo.getDestino());
						corridaInfo.setCorrida(corrida);
						corridaInfo.setInfoCorrida(esquemaCorridaInfo.getInfoCorrida());
						corridaInfoService.suscribir(corridaInfo);
					}
				}

			}
		}
	}
	
	public static void main(String[] args) {
		Date dataInicialFiltroCorridaExistente = new Date();
		Date dataFinalFiltroCorridaExistente = DateUtil.somarDias(dataInicialFiltroCorridaExistente, 7);
	
		LocalDate inicioLocalDate = dataInicialFiltroCorridaExistente.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
		LocalDate fimLocalDate = dataFinalFiltroCorridaExistente.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().plusDays(1);
		for (LocalDate date = inicioLocalDate; date.isBefore(fimLocalDate); date = date.plusDays(1)) {
			Date dataIter = Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
			DateFormat formatter = new SimpleDateFormat("EEE", new Locale("pt", "BR"));
			String dia = formatter.format(dataIter);
			System.out.println("Teste dia:" + dia);
		}
	}
	
}
