package de.duehl.basics.datetime.date;

/*
 * Copyright 2026 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.duehl.basics.text.NumberString;

/**
 * Eine Hilfsklasse rund um das Datum.
 *
 * @version 1.01     2026-08-13
 * @author Christian Dühl
 */

public class DateHelper {

    /** Erzeugt das heutige Datum. */
    public static ImmutualDate actualDate() {
        Calendar cal = Calendar.getInstance();
        return calendarToDate(cal); // Der normale Konstruktor täte es auch ... new ImmutualDate()
    }

    /** Gibt das heutige Datum im Format "TT.MM.YYYY" zurück. */
    public static String actualDateAsString() {
        Calendar cal = Calendar.getInstance();
        ImmutualDate iDate = calendarToDate(cal);
        return iDate.toString();
    }

    /** Erzeugt das Datum aus einem Calendar-Objekt. */
    public static ImmutualDate calendarToDate(Calendar cal) {
        int day = cal.get(Calendar.DAY_OF_MONTH);
        int month = cal.get(Calendar.MONTH) + 1;
        int year = cal.get(Calendar.YEAR);

        return new ImmutualDate(day, month, year);
    }

    /** Stellt fest, ob der angegebene Wochentag heute ist. */
    public static boolean weekDayIsToday(Weekday weekday) {
        ImmutualDate today = new ImmutualDate();
        Weekday weekdayToday = today.dayOfTheWeek();
        return weekdayToday == weekday;
    }

    /** Getter für das Datum aus Jahreszahl, Monatszahl und Tag des Monats. */
    public static ImmutualDate getDateFromYYYYMMDD(String yearMonthDay) {
        if (!NumberString.isDigitSequence(yearMonthDay) || yearMonthDay.length() != 8) {
            throw new IllegalArgumentException(
                    "Die Eingabe '" + yearMonthDay + "' besteht nicht aus acht Ziffern.");
        }
        return new ImmutualDate(yearMonthDay);
    }

    private final static Pattern DDMMYYYY_PATTERN = Pattern.compile(
            "(\\d{2})(\\d{2})(\\d{4})");

    /** Getter für das Datum aus Jahreszahl, Monatszahl und Tag des Monats. */
    public static ImmutualDate getDateFromDDMMYYYY(String dayMonthYear) {
        if (!NumberString.isDigitSequence(dayMonthYear) || dayMonthYear.length() != 8) {
            throw new IllegalArgumentException(
                    "Die Eingabe '" + dayMonthYear + "' besteht nicht aus acht Ziffern.");
        }
        Matcher matcher = DDMMYYYY_PATTERN.matcher(dayMonthYear);
        boolean success = matcher.matches();
        if (success) {
            int day   = Integer.parseInt(matcher.group(1));
            int month = Integer.parseInt(matcher.group(2));
            int year  = Integer.parseInt(matcher.group(3));

            return new ImmutualDate(day, month, year);
        }
        else {
            return new ImmutualDate(0, 0, 0);
        }
    }

    private final static Pattern DD_DOT_MM_DOT_YYYY_PATTERN = Pattern.compile(
            "(\\d{2})\\.(\\d{2})\\.(\\d{4})");

    /**
     * Prüft ob der übergeben String im Format DD.MM.YYYY vorliegt.
     *
     * Hierbei wird nur verlangt, dass es Ziffern und Punkte sind, eine Gültigkeit des Datums wird
     * nicht geprüft.
     */
    public static boolean isInDdDotMmDotYyyyFormat(String dayMonthYear) {
        Matcher matcher = DD_DOT_MM_DOT_YYYY_PATTERN.matcher(dayMonthYear);
        boolean success = matcher.matches();
        return success;
    }

    /** Sortiert die übergebene Liste von alt nach neu. */
    public static void sortFromOldToNew(List<ImmutualDate> dates) {
        Collections.sort(dates, new Comparator<>() {
            @Override
            public int compare(ImmutualDate date1, ImmutualDate date2) {
                return date1.compareTo(date2);
            }
        });
    }

    /** Sortiert die übergebene Liste von neu nach alt. */
    public static void sortFromNewToOld(List<ImmutualDate> dates) {
        Collections.sort(dates, new Comparator<>() {
            @Override
            public int compare(ImmutualDate date1, ImmutualDate date2) {
                return date2.compareTo(date1);
            }
        });
    }

    /** Gibt alle Datumswerte vom frühesten bis zum spätesten zurück. */
    public static List<ImmutualDate> getRange(ImmutualDate minimalDate, ImmutualDate maximalDate) {
        if (minimalDate.after(maximalDate)) {
            throw new IllegalArgumentException("Das früheste Datum " + minimalDate
                    + " liegt nach dem spätesten Datum " + maximalDate + "!");
        }

        List<ImmutualDate> list = new ArrayList<>();
        for (ImmutualDate date = minimalDate; !date.after(maximalDate); date = date.addDays(1)) {
            list.add(date);
        }

        return list;
    }

    /** Gibt die Datumswerte des aktuellen Monats aufsteigend sortiert zurück. */
    public static List<ImmutualDate> getDatesOfActualMonth() {
        return getDatesOfMonthWithDate(new ImmutualDate());
    }

    /**
     * Gibt die Datumswerte des Monats, in dem das übergebene Datum liegt, aufsteigend sortiert
     * zurück.
     *
     * @param date
     *            Tag im gewünschten Monat.
     */
    public static List<ImmutualDate> getDatesOfMonthWithDate(ImmutualDate date) {
        List<ImmutualDate> dates = new ArrayList<>();
        ImmutualDate firstDayOfMonth = new ImmutualDate(1, date.getMonth(), date.getYear());
        ImmutualDate dateToAdd = firstDayOfMonth;
        while (dateToAdd.getMonth() == date.getMonth()) {
            dates.add(dateToAdd);
            dateToAdd = dateToAdd.addDays(1);
        }
        return dates;
    }

    /** Gibt die Datumswerte der aktuellen Woche aufsteigend sortiert zurück. */
    public static List<ImmutualDate> getDatesOfActualWeek() {
        return getDatesOfWeekWithDate(new ImmutualDate());
    }

    /**
     * Gibt die Datumswerte der Woche, in dem das übergebene Datum liegt, aufsteigend sortiert
     * zurück.
     *
     * @param date
     *            Tag im gewünschten Monat.
     */
    public static List<ImmutualDate> getDatesOfWeekWithDate(ImmutualDate date) {
        List<ImmutualDate> dates = new ArrayList<>();
        ImmutualDate monday;
        if (date.dayOfTheWeek() == Weekday.MONDAY) {
            monday = date;
        }
        else {
            monday = date.getWeekdayBefore(Weekday.MONDAY);
        }
        ImmutualDate dateToAdd = monday;
        while (dateToAdd.dayOfTheWeek() != Weekday.MONDAY || dateToAdd.equals(monday)) {
            dates.add(dateToAdd);
            dateToAdd = dateToAdd.addDays(1);
        }
        return dates;
    }

    /**
     * Erzeugt eine neue Liste von Datumswerten aus der übergebenen Liste, welche nur Arbeitstage
     * enthält, also keine Wochenenden oder Feiertage.
     */
    public static List<ImmutualDate> keepOnlyWorkDays(List<ImmutualDate> dates) {
        List<ImmutualDate> workingDates = new ArrayList<>();
        for (ImmutualDate date : dates) {
            if (date.isWorkDay()) {
                workingDates.add(date);
            }
        }
        return workingDates;
    }

    /**
     * Ermittelt aus einer Reihe von Datumswerten das neuste.
     *
     * Sind in der Liste ungültige Datumswerte enthalten, werden diese weggelassen. Sind alle
     * ungültig, wird das erste Element zurück gegeben, auch wenn es ungültig ist.
     *
     * @param dates
     *            Die Datumswerte.
     * @return Der neuste Datumswert.
     */
    public static String determineNewestDate(String ... dates) {
        List<ImmutualDate> immutualDates = new ArrayList<>();
        for (String date : dates) {
            ImmutualDate immutualDate = new ImmutualDate(date);
            if (immutualDate.isValid()) {
                immutualDates.add(immutualDate);
            }
        }
        if (immutualDates.isEmpty()) { // dann sind alle ungültig ...
            return dates[0];
        }
        return determineNewestImmutualDate(immutualDates).toString();
    }

    /**
     * Ermittelt aus einer Reihe von Datumswerten, welche schon als ImmutualDate vorliegen, das
     * neuste.
     *
     * @param immutualDates
     *            Die Datumswerte.
     * @return Der neuste Datumswert.
     */
    public static ImmutualDate determineNewestImmutualDate(List<ImmutualDate> immutualDates) {
        if (immutualDates.isEmpty()) {
            throw new RuntimeException("In einer leeren Menge von Datumswerten kann nicht das "
                    + "neuste ermitelt werden.");
        }

        ImmutualDate newestDate = immutualDates.get(0);

        for (int index = 1; index < immutualDates.size(); ++index) {
            ImmutualDate aDate = immutualDates.get(index);
            if (aDate.after(newestDate)) {
                newestDate = aDate;
            }
        }

        return newestDate;
    }

    /** Gibt die normierte Form des Datums zurück. */
    public static String createCorrectDate(String date) {
        ImmutualDate immutualDate = new ImmutualDate(date);
        return immutualDate.toString();
    }

    /** Fügt in ein achtstelliges Datum ohne Punkte im Format DDMMYYYY die beiden Punkte ein. */
    public static String insertDateDotsDDMMYYYY(String dateWithoutDots) {
        if (!NumberString.isDigitSequence(dateWithoutDots) || dateWithoutDots.length() != 8) {
            return dateWithoutDots;
        }

        String day = dateWithoutDots.substring(0, 2);
        String month = dateWithoutDots.substring(2, 4);
        String year = dateWithoutDots.substring(4);

        return day + "." + month + "." + year;
    }

    /** Fügt in ein achtstelliges Datum ohne Punkte im Format DDMMYYYY die beiden Punkte ein. */
    public static String removeDateDotsDDMMYYYY(String dateWithDots) {
        if (dateWithDots.length() != 10) {
            return dateWithDots;
        }

        return dateWithDots.replace(".", "");
    }

    private static final Pattern ANY_DATE_PATTERN = Pattern.compile(ImmutualDate.ANY_DATE_REGEX);

    /** Gibt an, ob im übergebenen Text irgend ein Datum vorkommt. */
    public static boolean conainesAnyDate(String text) {
        Matcher matcher = ANY_DATE_PATTERN.matcher(text);
        return matcher.find();
    }

    private static final Pattern ANY_FULL_DATE_PATTERN = Pattern.compile(
            ImmutualDate.ANY_FULL_DATE_REGEX);

    /** Gibt an, ob im übergebenen Text irgend ein Datum vorkommt. */
    public static boolean conainesAnyFullDate(String text) {
        Matcher matcher = ANY_FULL_DATE_PATTERN.matcher(text);
        return matcher.find();
    }

    /** Prüft ob der übergebene String eine vierstellige Jahreszahl ist. */
    public static boolean isYear(String year) {
        return year.length() == 4
                && NumberString.isDigitSequence(year)
                && (year.startsWith("19") || year.startsWith("20"));
    }

    /** Gibt das Datum 01.01.1970 zurück. */
    public static ImmutualDate getEpoch() {
        return new ImmutualDate(1, 1, 1970);
    }

    /**
     * Gibt an, ob das Datum in der Zukunft liegt.
     *
     * Bei dieser Methode wird das Datum von heute jedes Mal neu ermittelt.
     */
    public static boolean isDateInTheFuture(ImmutualDate date) {
        ImmutualDate actualDate = actualDate();
        return actualDate.before(date);
    }

    /** Das Datum von heute als fester Wert, um unnötige Aufrufe zu sparen. */
    private static final ImmutualDate DATE_OF_TODAY = actualDate();

    /**
     * Gibt an, ob das Datum in der Zukunft liegt.
     *
     * Bei dieser Methode wird das Datum von heute einmalig beim Start des Programms ermittelt.
     */
    public static boolean isDateInTheFutureWithFixTodayDate(ImmutualDate date) {
        return DATE_OF_TODAY.before(date);
    }

}
