/**
 * 
 */
package com.ejie.ad74a.util;

import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Month;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

/**
 * @author jhidalgo
 *
 */
public class UtilDates {

	private static final String YEARMONTHPATTERN = "yyyyMM";
	public static final String DATEPATTERN = "dd/MM/yyyy";

	public static final String DATEPATTERN2 = "dd-MM-yyyy HH:mm:ss"; //2024-12-10T08:28:15 //La 'T' se quita al leer el fichero
	public static final String DATEPATTERN4 = "yyyy-MM-dd HH:mm:ss"; //2024-12-10T08:28:15 //La 'T' se quita al leer el fichero
	//public static final String DATEPATTERN2 = "yyyy-MM-dd hh:mm:ss.SSSX"; //2025-05-29T08:55:59.232+02:00
	public static final String DATEPATTERN3 = "yyyy-MM-dd";
	private static final String LARGEPATTERN = "EEEE MMMM dd HH:mm:ss 'CET' yyyy";
	private static final String CETPATTERN = "CET";

	/**
	 * 
	 */
	private UtilDates() {

	}

	/**
	 * Calcula unidades de tiempo a partir de los milisegundos del sistema. Usado
	 * para trazas.
	 * 
	 * @param long milisegundos
	 * @return array de long. 0:
	 */
	public static long[] getHhMmSs(long millis) {
		long[] result = new long[3];
		// Horas
		result[0] = TimeUnit.MILLISECONDS.toHours(millis);
		// Minutos
		result[1] = TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(result[0]);
		// Segundos
		result[2] = TimeUnit.MILLISECONDS.toSeconds(millis)
				- TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis));
		return result;
	}

	/**
	 * Devuelve String con formato del patron recibido
	 * @param date
	 * @param pattern
	 * @return
	 */
	public static String formatDateToString(Date date, String pattern) {
		Format formatter = new SimpleDateFormat(pattern);
		
		return null!= date ? formatter.format(date) : null;
	}
	
	
	/**
	 * Devuelve String con formato fecha por defecto "dd/MM/yyyy"
	 * @param date
	 * @return
	 */
	public static String formatDateToString(Date date) {
		return formatDateToString(date, DATEPATTERN);
	}
	
	/**
	 * Devuelve String con formato fecha por defecto "yyyy-MM-dd"
	 * @param date
	 * @return
	 */
	public static String formatDateToString3(Date date) {
		return formatDateToString(date, DATEPATTERN3);
	}

	/**
	 * Formatea el String aniomes con pattern yyyyMM a LocalDate con el primer dia
	 * del mes
	 * 
	 * @param dateStr
	 * @return
	 */
	public static LocalDate formatYearMonthToLocalDateAtFirstDayOfMonth(String yearMonth) {
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YEARMONTHPATTERN);
		return YearMonth.parse(yearMonth, formatter).atDay(1);
	}

	/**
	 * Formatea el String aniomes con pattern yyyyMM a LocalDate con el ultimo dia
	 * del mes
	 * 
	 * @param dateStr
	 * @return
	 */
	public static LocalDate formatYearMonthToLocalDateAtLastDayOfMonth(String yearMonth) {
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YEARMONTHPATTERN);
		return YearMonth.parse(yearMonth, formatter).atEndOfMonth();
	}

	/**
	 * 
	 * @param date
	 * @return
	 */
	public static String formatLocalDateToString(LocalDate date) {
		return formatLocalDateToString(date, DATEPATTERN);
	}

	/**
	 * Formatea LocalDate a formato String con el patron pasado. En caso de recibir
	 * un null, devuelve ""
	 * 
	 * @param date
	 * @param pattern
	 * @return
	 */
	public static String formatLocalDateToString(LocalDate date, String pattern) {
		return null != date ? date.format(DateTimeFormatter.ofPattern(pattern)) : "";
	}

	/**
	 * Formatea LocalDate a formato Date
	 * 
	 * @param localDate
	 * @return
	 */
	public static Date formatLocalDateToDate(LocalDate localDate) {
		return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
	}

	/**
	 * Formatea Date a formato LocalDate
	 * 
	 * @param date
	 * @return
	 */
	public static LocalDate formatDateToLocalDate(Date date) {
		return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDate();
	}
	
	/**
	 * Devuelve el numero de dias entre fecha de inicio y fecha de fin. El parametro
	 * endDateExclusive permite incluir o excluir el ultimo dia de la fecha final.
	 * Si cualquiera de las 2 fechas es null, devuelve nulo.
	 * 
	 * @param initLocalDate
	 * @param finalLocalDate
	 * @param endDateExclusive
	 * @return
	 */
	public static Long getDaysBetweenTwoLocalDates(LocalDate initLocalDate, LocalDate finalLocalDate,
			Boolean endDateExclusive) {
		if (null != initLocalDate && null != finalLocalDate) {
			finalLocalDate = endDateExclusive.booleanValue() ? finalLocalDate : finalLocalDate.plusDays(1);
			return ChronoUnit.DAYS.between(initLocalDate, finalLocalDate);
		}

		return null;
	}

	/**
	 * Devuelve el numero de dias entre fecha de inicio y fecha de fin. Si
	 * cualquiera de las 2 fechas es null, devuelve nulo.
	 * 
	 * @param initLocalDate
	 * @param finalLocalDate
	 * @return
	 */
	public static Long getDaysBetweenTwoLocalDates(LocalDate initLocalDate, LocalDate finalLocalDate) {
		if (null != initLocalDate && null != finalLocalDate) {
			return getDaysBetweenTwoLocalDates(initLocalDate, finalLocalDate, Boolean.FALSE);
		}

		return null;
	}
	
	/**
	 * Devuelve String con el anio y mes anterior al actual con pattern yyyyMM
	 * 
	 * @return
	 */
	public static String getPreviousToCurrentYearMonth() {
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern(YEARMONTHPATTERN);
		return YearMonth.now().minusMonths(1).format(formatter);
	}
	
	/**
	 * Devuelve un optional con la fecha mayor de entre las pasadas como parametros, depreciando las fechas nulas
	 * @param fechas
	 * @return
	 */
	public static Optional<LocalDate> getLocalDateMayor(LocalDate...fechas ) {
		return Arrays.asList(fechas).stream().filter(Objects::nonNull).max(LocalDate::compareTo);
	}
	
	/**
	 * Deveuvle un optional con la fecha menor de entre las pasadas como parametros, depreciando las fechas nulas
	 * @param fechas
	 * @return
	 */
	public static Optional<LocalDate> getLocalDateMenor(LocalDate...fechas ) {
		return Arrays.asList(fechas).stream().filter(Objects::nonNull).min(LocalDate::compareTo);
	}

	/**
	 * Devuelve true si la fecha se encuentra dentro del rango. Si la fecha de fin
	 * es null, no se observa la fecha de fin, solo si la fecha es posterior o igual
	 * a la fecha de inicio. False en caso contrario.
	 * 
	 * @param date
	 * @param startDate
	 * @param endDate
	 * @return
	 */
	public static boolean isDateWithinRage(LocalDate date, LocalDate startDate, LocalDate endDate) {
		
		return (date.isAfter(startDate) || date.isEqual(startDate))
				&& (null == endDate || (date.isBefore(endDate) || date.isEqual(endDate)));
	}
	
	/**
	 * Devuelve el mes en letras mayusculas
	 * @param monthNumber
	 * @return
	 */
	public static String formatNMesToUpperCaseText(int monthNumber) {
		return Month.of(monthNumber).getDisplayName(TextStyle.FULL, new Locale("es", "ES")).toUpperCase();
	}
	
	/**
	 * Devuelve el numero de mes en formato NN, es decir, longitud de 2 carateres
	 * @param monthNumber
	 * @return
	 */
	public static String formatNMesToString(int monthNumber) {
		return String.format("%02d", monthNumber);
	}
	
	public static Date getFechaSistema() {
		Calendar calendar = Calendar.getInstance();
		Date dateObj = calendar.getTime();
		return dateObj;
	}
	
	/**
	 * Transforma una fecha String a java.sql.Date.
	 * 
	 * @param fecha   String con la fecha a convertir
	 * @param formato String con el formato de la fecha
	 * @return java.sql.Date fecha convertida
	 * @exception ParseException Se produce la excepción cuando la fecha está mal
	 *                           formateada.
	 */
	public static java.util.Date formatearStringToDateFormat(String fecha, String formato) throws ParseException {
		SimpleDateFormat formatoDelTexto = new SimpleDateFormat(formato);
		java.util.Date dFecha = null;

		dFecha = new java.util.Date(formatoDelTexto.parse(fecha).getTime());

		return dFecha;
	}
	
	/**
	 * Transforma una fecha en formato 'yyyy-mm-dd' (Date) a otra en formato day month dd hh:mm:ss CET yyyy (Date)
	 * 
	 * @param fechaSinFormato   Date
	 * @return java.sql.Date fecha formateada
	 * @exception ParseException Se produce la excepción cuando la fecha está mal
	 *                           formateada.
	 */
	public static java.util.Date formatearDateToDateFormat(Date fechaSinFormato) throws ParseException {
		
		if (fechaSinFormato == null) {
			return null;
		}

        // Formato de entrada
        SimpleDateFormat inputFormat = new SimpleDateFormat(DATEPATTERN3);
        Date date = inputFormat.parse(formatDateToString3(fechaSinFormato));

        // Formato de salida
        SimpleDateFormat outputFormat = new SimpleDateFormat(LARGEPATTERN, Locale.ENGLISH);
        outputFormat.setTimeZone(TimeZone.getTimeZone(CETPATTERN));
        String formattedDateStr = outputFormat.format(date);

        // Convertir la fecha formateada de nuevo a Date
        Date formattedDate = outputFormat.parse(formattedDateStr);
		
		return formattedDate;
    }
	
}
