package de.duehl.basics.datetime.time;

/*
 * Copyright 2025 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.Calendar;
import java.util.List;

import de.duehl.basics.datetime.time.data.HoursMinutesSeconds;
import de.duehl.basics.text.NumberString;
import de.duehl.basics.text.Text;

/**
 * Eine Hilfsklasse rund um die Zeit.
 *
 * @version 1.01     2025-07-23
 * @author Christian Dühl
 */

public class TimeHelper {

    /** Anzahl Millisekunden einer Stunde. */
    public static final long ONE_HOUR_MILLIS = 60L * 60L * 1000L;

    /** Anzahl Millisekunden eines Tages. */
    public static final long ONE_DAY_MILLIS = 24L * ONE_HOUR_MILLIS;

    private TimeHelper() {} // keine Instanz bilden!

    /** Gibt die aktuelle Zeit in Millisekunden seit 01.01.1970 zurück. */
    public static long getCurrentTimeInMilliseconds() {
        return System.currentTimeMillis();
    }

    /**
     * Erzeugt aus einem von Menschen lesbaren Ergebnis in der Form HH:MM:SS eine Zeit in Sekunden.
     *
     * @param hoursMinutesSeconds
     *            Ein von Menschen lesbares Ergebnis in der Form HH:MM:SS.
     * @return Zeit in Sekunden.
     */
    public static long hoursMinutesSecondsToSeconds(String hoursMinutesSeconds) {
        if (!hoursMinutesSeconds.matches("\\d{2,}:\\d\\d:\\d\\d")) {
            throw new IllegalArgumentException("Falsches Format, erwartet wird HH:MM:SS (z.B. "
                    + "\"02:24:31\") bekommen haben wir \"" + hoursMinutesSeconds + "\".");
        }
        List<String> parts = Text.splitByColon(hoursMinutesSeconds);
        if (parts.size() != 3) {
            throw new IllegalArgumentException("Falsches Format, erwartet wird HH:MM:SS (z.B. "
                    + "\"02:24:31\") bekommen haben wir \"" + hoursMinutesSeconds + "\".");
        }
        String hours = parts.get(0);
        String minutes = parts.get(1);
        String seconds = parts.get(2);

        long h = NumberString.parseLong(hours, hours + " lässt sich nicht zu long parsen");
        long m = NumberString.parseLong(minutes, minutes + " lässt sich nicht zu long parsen");
        long s = NumberString.parseLong(seconds, seconds + " lässt sich nicht zu long parsen");

        return h * 60 * 60  +  m * 60  +  s;
    }

    /**
     * Erzeugt aus einer Zeit in Sekunden ein von Menschen lesbares Ergebnis in der Form HH:MM:SS.
     *
     * @param seconds
     *            Zeit in Sekunden.
     * @return Ein von Menschen lesbares Ergebnis in der Form HH:MM:SS.
     */
    public static String secondsToHoursMinutesSeconds(long seconds) {
        if (seconds < 0) {
            return "ERROR";
        }
        else {
            HoursMinutesSeconds hoursMinutesSeconds = secondsToHoursMinutesSecondsInternal(seconds);
            return hoursMinutesSeconds.toFormattedString();
        }
    }

    /**
     * Erzeugt aus einer Zeit in Sekunden ein von Menschen lesbares Ergebnis in der Form HH:MM:SS.
     * Dabei werden die Stunden modulo 24 genommen.
     *
     * @param seconds
     *            Zeit in Sekunden.
     * @return Ein von Menschen lesbares Ergebnis in der Form HH:MM:SS.
     */
    public static String secondsToHoursMinutesSecondsSinceMidnight(long seconds) {
        if (seconds < 0) {
            return "ERROR";
        }
        else {
            HoursMinutesSeconds hoursMinutesSeconds = secondsToHoursMinutesSecondsInternal(seconds);
            hoursMinutesSeconds.cutToSinceMidnight();
            return hoursMinutesSeconds.toFormattedString();
        }
    }

    private static HoursMinutesSeconds secondsToHoursMinutesSecondsInternal(long seconds) {
        long rest = seconds;
        long secondsOut = rest % 60;

        rest -= secondsOut;
        rest /= 60;
        long minutes = rest % 60;

        rest -= minutes;
        rest /= 60;
        long hours = rest;

        return new HoursMinutesSeconds(hours, minutes, secondsOut);
    }

    /**
     * Erzeugt aus einer Zeit in Millisekunden ein von Menschen lesbares Ergebnis in der Form
     * HH:MM:SS:mmm.
     *
     * @param milliSeconds
     *            Zeit in Millisekunden.
     * @return Ein von Menschen lesbares Ergebnis in der Form HH:MM:SS:mmm.
     */
    public static String milliSecondsToHoursMinutesSecondsMilliSeconds(long milliSeconds) {
        if (milliSeconds < 0) {
            return "ERROR";
        }

        long seconds = milliSeconds / 1000;
        long rest = milliSeconds - 1000 * seconds;

        String humanSeconds = secondsToHoursMinutesSeconds(seconds);

        return String.format("%s:%03d", humanSeconds, rest);
    }

    /**
     * Erzeugt aus einer Zeit in Millisekunden ein von Menschen lesbares Ergebnis in der Form
     * HH:MM:SS(mmm).
     *
     * @param milliSeconds
     *            Zeit in Millisekunden.
     * @return Ein von Menschen lesbares Ergebnis in der Form HH:MM:SS:mmm.
     */
    public static String milliSecondsToHoursMinutesSecondsMilliSecondsBraces(long milliSeconds) {
        if (milliSeconds < 0) {
            return "ERROR";
        }

        long seconds = milliSeconds / 1000;
        long rest = milliSeconds - 1000 * seconds;

        String humanSeconds = secondsToHoursMinutesSeconds(seconds);

        return String.format("%s(%03d)", humanSeconds, rest);
    }

    /**
     * Addiert zwei Zeitangaben im Format HH:MM:SS.
     *
     * @param hoursMinutesSeconds1
     *            Erste Zeitangabe im Format HH:MM:SS.
     * @param hoursMinutesSeconds2
     *            Zweite Zeitangabe im Format HH:MM:SS
     * @return Addierte Zeitangaben im Format HH:MM:SS.
     */
    public static String addHourMinutesSecondsToHourMinutesSeconds(String hoursMinutesSeconds1,
            String hoursMinutesSeconds2) {
        long seconds1 = TimeHelper.hoursMinutesSecondsToSeconds(hoursMinutesSeconds1);
        long seconds2 = TimeHelper.hoursMinutesSecondsToSeconds(hoursMinutesSeconds2);
        long totalSeconds = seconds1 + seconds2;

        return TimeHelper.secondsToHoursMinutesSeconds(totalSeconds);
    }

    /*
     * TODO millis to Datum ... man will ja nicht den Zeitabstand vom 1.1.70, sondern eine
     * vernünftige Darstellung der Sicherungszeit.
     */

    /** Erzeugt einen Zeitpunkt mit der aktuellen Uhrzeit. */
    public static ImmutualTime actualTime() {
        Calendar cal = Calendar.getInstance();
        return calendarToTime(cal);
    }

    /** Gibt die aktuelle Uhrzeit im Format "hh:mm:ss" zurück. */
    public static String actualTimeAsString() {
        Calendar cal = Calendar.getInstance();
        ImmutualTime iTime = calendarToTime(cal);
        return iTime.toString();
    }

    /** Erzeugt den Zeitpunkt aus einem Calendar-Objekt. */
    public static ImmutualTime calendarToTime(Calendar cal) {
        int hour = cal.get(Calendar.HOUR_OF_DAY);
        int minute = cal.get(Calendar.MINUTE);
        int second = cal.get(Calendar.SECOND);

        return new ImmutualTime(hour, minute, second);
    }

    /**
     * Prüft die Eingabe darauf, eine Zeitangabe im Format "HH:MM" zu sein. Erlaubt sind Werte von
     * "00:00" bis "23:59".
     */
    public static boolean isHhMmTime(String time) {
        if (time.length() != 5) {
            return false;
        }

        String h1 = time.substring(0, 1);
        String h2 = time.substring(1, 2);
        String colon = time.substring(2, 3);
        String m1 = time.substring(3, 4);
        String m2 = time.substring(4, 5);

        if (!is0or1or2(h1)) {
            return false;
        }
        if (!is0to9(h2)) {
            return false;
        }
        if (!colon.equals(":")) {
            return false;
        }
        if (!is0to5(m1)) {
            return false;
        }
        if (!is0to9(m2)) {
            return false;
        }

        if (h1.equals("2") && !is0to3(h2)) {
            return false;
        }

        return true;
    }

    /** Prüft, ob der gegebenen Wert "0", "1" oder "2" ist. */
    public static boolean is0or1or2(String digit) {
        return digit.equals("0") || digit.equals("1") || digit.equals("2");
    }

    /** Prüft, ob der gegebenen Wert "0", "1", "2" oder "3" ist. */
    public static boolean is0to3(String digit) {
        return digit.equals("0")
                || digit.equals("1")
                || digit.equals("2")
                || digit.equals("3");
    }

    /** Prüft, ob der gegebenen Wert "0", "1", "2" ... oder "5" ist. */
    public static boolean is0to5(String digit) {
        return digit.equals("0")
                || digit.equals("1")
                || digit.equals("2")
                || digit.equals("3")
                || digit.equals("4")
                || digit.equals("5");
    }

    /** Prüft, ob der gegebenen Wert "0", "1", "2" ... oder "9" ist. */
    public static boolean is0to9(String digit) {
        return digit.equals("0")
                || digit.equals("1")
                || digit.equals("2")
                || digit.equals("3")
                || digit.equals("4")
                || digit.equals("5")
                || digit.equals("6")
                || digit.equals("7")
                || digit.equals("8")
                || digit.equals("9");
    }

    /**
     * Erzeugt aus einer Laufzeit wie "00:00:07" oder "5021:23:21" die Anzahl an Sekunden. Im
     * Fehlerfall wird -1 zurück gegeben.
     */
    public static long runtimeToSeconds(String runtime) {
        List<String> parts = Text.splitByColon(runtime);
        if (parts.size() != 3) {
            return -1;
        }

        String hoursString = parts.get(0);
        long hours = NumberString.parseLongIgnore(hoursString, -1);
        if (hours == -1) {
            return -1;
        }

        String minutesString = parts.get(1);
        long minutes = NumberString.parseIntIgnore(minutesString, -1);
        if (minutes == -1) {
            return -1;
        }

        String secondsString = parts.get(2);
        long seconds = NumberString.parseIntIgnore(secondsString, -1);
        if (seconds == -1) {
            return -1;
        }

        return hours * 60L * 60L + minutes * 60L + seconds;
    }

    /**
     * Erzeugt einen String der Art 01:23:45 aus den Sekunden oder im Fehlerfall den String
     * "unbekannt".
     */
    public static String secondsToRuntime(long seconds) {
        if (seconds < 0) {
            return "unbekannt";
        }

        long rest = seconds;
        long secondsOut = rest % 60;

        rest -= secondsOut;
        rest /= 60;
        long minutes = rest % 60;

        rest -= minutes;
        rest /= 60;
        long hours = rest;

        return NumberString.addLeadingZeroes(hours, 2) + ":"
                + NumberString.addLeadingZeroes(minutes, 2) + ":"
                + NumberString.addLeadingZeroes(secondsOut, 2);
    }

    /**
     * Addiert mehrere von Menschen lesbare Ergebnisse in der Form HH:MM:SS.
     *
     * @param mutltipleHoursMinutesSeconds
     *            Mehrere von Menschen lesbare Ergebnisse in der Form HH:MM:SS.
     * @return Ein von Menschen lesbares Ergebnis in der Form HH:MM:SS als Gesamtergebnis der
     *         Eingaben.
     */
    public static String summerizeHourMinuteSeconds(String ... mutltipleHoursMinutesSeconds) {
        long totalSeconds = 0L;
        for (String hoursMinutesSeconds : mutltipleHoursMinutesSeconds) {
            long seconds = hoursMinutesSecondsToSeconds(hoursMinutesSeconds);
            totalSeconds += seconds;
        }
        String totalHoursMinutesSeconds = secondsToHoursMinutesSeconds(totalSeconds);
        return totalHoursMinutesSeconds;
    }

    /**
     * Fügt in eine sechsstellige Uhrzeit ohne Doppelpunkte im Format HHMMSS die beiden Punkte ein.
     */
    public static String insertTimeColonsHHMMSS(String timeWithoutColons) {
        if (!NumberString.isDigitSequence(timeWithoutColons) || timeWithoutColons.length() != 6) {
            return timeWithoutColons;
        }

        String hour = timeWithoutColons.substring(0, 2);
        String minutes = timeWithoutColons.substring(2, 4);
        String seconds = timeWithoutColons.substring(4, 6);

        return hour + ":" + minutes + ":" + seconds;
    }

    /**
     * Diese Methode erstellt aus den übergebenen Sekunden eine Angabe in der Form 5:21 für Minuten
     * und Sekunden. Das macht natürlich nur für kleine Zeiten einen Sinn, wie z.B. bei der
     * Laufzeit von Musikstücken.
     */
    public static String secondsToMinutesSeconds(int lengthInSeconds) {
        int minutes = lengthInSeconds / 60;
        int secondsLeft = lengthInSeconds % 60;
        String between;
        if (secondsLeft < 10) {
            between = ":0";
        }
        else {
            between = ":";
        }
        return minutes + between + secondsLeft;
    }

}
