package de.duehl.swing.ui.update;

/*
 * 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.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.datetime.DateAndTime;
import de.duehl.basics.datetime.time.TimeHelper;
import de.duehl.basics.text.NumberString;
import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.dialogs.base.AbstractDialogBase;
import de.duehl.swing.ui.elements.watch.CountDownWatchLabel;
import de.duehl.swing.ui.update.data.LabelAndTitle;
import de.duehl.swing.ui.update.dialog.ChangeUpdateIntervalDialog;

/**
 * Diese Klasse stellt die grafische Oberfläche zur regelmäßigen Ausführung eines Tasks dar.
 *
 * @version 1.01     2025-11-13
 * @author Christian Dühl
 */

public class UpdateGui {

    private static final String INTERVAL_TITLE = "Aktualisierungsintervall";
    private static final String LAST_DURATION_TITLE = "Letzte Ausführungsdauer";
    private static final String COUNT_TITLE = "Anzahl Aktualisierungen";
    private static final String START_DATE_TITLE = "Datum letzte Aktualisierung";
    private static final String START_TIME_TITLE = "Uhrzeit letzte Aktualisierung";
    private static final String COUNT_DOWN_TITLE = "Nächste Aktualisierung in";


    /** Grafische Oberfläche auf der die Aktualisierungen angezeigt werden. */
    private final AbstractDialogBase gui;

    private final JLabel intervalLabel;
    private final JLabel lastDurationLabel;
    private final JLabel countLabel;
    private final JLabel startDateLabel;
    private final JLabel startTimeLabel;
    private final CountDownWatchLabel countDownWatchLabel;

    /** Button zum sofortigen Aktualisieren. */
    private final JButton updateNowButton;

    /** Logik zur regelmäßigen Ausführung des Tasks. */
    private UpdateLogic logic;

    /** Liste der Label und der zugehörigen Titel zur Darstellung. */
    private final List<LabelAndTitle> labelsAndTitles;

    /** Gibt an, wie viel größer die Schriftgröße werden soll. */
    private int addToFontSize;

    /**
     * Konstruktor.
     *
     * @param gui
     *            Grafische Oberfläche auf der die Aktualisierungen angezeigt werden.
     */
    public UpdateGui(AbstractDialogBase gui) {
        this.gui = gui;

        intervalLabel = new JLabel("... bitte warten ...");
        lastDurationLabel = new JLabel("noch nicht gelaufen");
        countLabel = new JLabel("0");
        startDateLabel = new JLabel("noch nicht gelaufen");
        startTimeLabel = new JLabel("noch nicht gelaufen");
        countDownWatchLabel = new CountDownWatchLabel(0);
        updateNowButton = new JButton("Aktualisieren");

        labelsAndTitles = CollectionsHelper.buildListFrom(
                new LabelAndTitle(INTERVAL_TITLE, intervalLabel),
                new LabelAndTitle(LAST_DURATION_TITLE, lastDurationLabel),
                new LabelAndTitle(COUNT_TITLE, countLabel),
                new LabelAndTitle(START_DATE_TITLE, startDateLabel),
                new LabelAndTitle(START_TIME_TITLE, startTimeLabel),
                new LabelAndTitle(COUNT_DOWN_TITLE, countDownWatchLabel)
                );

        addToFontSize = 0;
    }

    /** Setter für die Logik zur regelmäßigen Ausführung des Tasks. */
    void setlogic(UpdateLogic logic) {
        this.logic = logic;
    }

    /** Legt fest, wie viel größer die Schriftgröße werden soll. */
    public void setAddToFontSize(int addToFontSize) {
        this.addToFontSize = addToFontSize;
    }

    /** Erzeugt einen Panel mit den Aktualisierungsdaten in zweispaltiger Grid-Form. */
    public JPanel createUpdateGridPanel() {
        JPanel panel = new JPanel();
        GuiTools.createTitle("", panel);

        panel.setLayout(new GridLayout(0, 2, 2, 2));

        for (LabelAndTitle labelAndTitle : labelsAndTitles) {
            addTitledLabel(panel, labelAndTitle.getTitle(), labelAndTitle.getLabel());
        }

        return panel;
    }

    /**
     * Fügt zwei JLabel zum Panel hinzu (hier empfiehlt sich dafür ein Grid-Layout), zum einen ein
     * erzeugtes für den Titel und zum anderen das übergebene Label.
     *
     * @param panel
     *            Panel dem die Label hinzugefügt werden sollen.
     * @param title
     *            Titel für das Label (wird im eigenen label angezeigt).
     * @param label
     *            Eigentliches Label.
     */
    private void addTitledLabel(JPanel panel, String title, JLabel label) {
        JLabel titleLabel = createCalculationTitleLabel(title);
        GuiTools.biggerFont(titleLabel, addToFontSize);
        panel.add(titleLabel);

        GuiTools.biggerFont(label, addToFontSize);
        panel.add(label);
    }

    private JLabel createCalculationTitleLabel(String title) {
        JLabel label = new JLabel(title);
        label.setBorder(new EmptyBorder(0, 30, 0, 0));
        return label;
    }

    /** Erzeugt einen Panel mit den Aktualisierungsdaten in zweispaltiger Grid-Form. */
    public JPanel createUpdateGridBagPanel() {
        JPanel panel = new JPanel();
        GuiTools.createTitle("", panel);

        panel.setLayout(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets     = new Insets(0, 0, 0, 0);
        gbc.fill       = GridBagConstraints.BOTH;
        gbc.anchor     = GridBagConstraints.WEST;
        gbc.weightx    = 0.0;
        gbc.weighty    = 0.0;
        gbc.gridx      = 0;
        gbc.gridy      = 0;

        for (LabelAndTitle labelAndTitle : labelsAndTitles) {
            addGriddedTitledPanel(panel, gbc, labelAndTitle.getTitle(), labelAndTitle.getLabel());
        }

        return panel;
    }

    /**
     * Fügt zwei JLabel zum Panel mit GridBagLayout hinzu, zum einen ein erzeugtes für den Titel
     * und zum anderen das übergebene Label.
     *
     * @param panel
     *            Panel dem die Label hinzugefügt werden sollen.
     * @param gbc
     *            Die GridBagConstraints des GridBagLayouts.
     * @param title
     *            Titel für das Label (wird im eigenen label angezeigt).
     * @param label
     *            Eigentliches Label.
     */
    private void addGriddedTitledPanel(JPanel panel, GridBagConstraints gbc, String title,
            JLabel label) {
        gbc.gridx = 0;
        gbc.weightx = 1.0;
        JLabel titleLabel = new JLabel(title);
        GuiTools.biggerFont(titleLabel, addToFontSize);
        panel.add(titleLabel, gbc);

        gbc.gridx = 1;
        gbc.weightx = 0.0;
        GuiTools.biggerFont(label, addToFontSize);
        panel.add(label, gbc);

        ++gbc.gridy;
    }

    /*
     * Bei Bedarf weitere Methoden neben createUpdateGridPanel() und createUpdateGridBagPanel() zur
     * Anordnung anbieten.
     */

    /**
     * Erstellt einen Panel mit einem Button zum Ändern des Aktualisierungsintervalls und einem
     * Button zur sofortigen Aktualisierung.
     */
    public Component createUpdateButtons() {
        JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout());

        panel.add(createChangeUpdateIntervalButton());
        panel.add(createUpdateNowButton());

        return panel;
    }

    private Component createChangeUpdateIntervalButton() {
        JButton button = new JButton("Aktualisierungsintervall ändern ...");
        button.setFocusable(false);
        button.setEnabled(true);
        button.addActionListener(e -> changeUpdateInterval());
        return button;
    }

    private void changeUpdateInterval() {
        ChangeUpdateIntervalDialog dialog = new ChangeUpdateIntervalDialog(
                logic.getUpdateIntervalInSeconds(), gui.getWindowAsComponent());
        dialog.setVisible(true);
        if (dialog.isApplied()) {
            int seconds = dialog.getUpdateIntervalInSeconds();
            checkAndPerhapsSetUpdateIntervalInSeconds(seconds);
        }
    }

    /**
     * Prüft, ob der übergebene Abstand zwischen den Ausführungen des Tasks in Sekunden im
     * zulässigen Bereich liegt und setzt ihn im Erfolgsfall.
     *
     * Gibt zurück, ob der Wert gesetzt wurde. Anderenfalls wurde der Fehler angezeigt.
     */
    private boolean checkAndPerhapsSetUpdateIntervalInSeconds(int updateIntervalInSeconds) {
        return checkAndPerhapsSetUpdateIntervalInSeconds(updateIntervalInSeconds, true);
    }

    /**
     * Prüft, ob der übergebene Abstand zwischen den Ausführungen des Tasks in Sekunden im
     * zulässigen Bereich liegt und setzt ihn im Erfolgsfall.
     *
     * Führt den Task aber nicht aus!
     *
     * Gibt zurück, ob der Wert gesetzt wurde. Anderenfalls wurde der Fehler angezeigt.
     */
    public boolean checkAndPerhapsSetUpdateIntervalInSecondsWithoutRun(int updateIntervalInSeconds) {
        return checkAndPerhapsSetUpdateIntervalInSeconds(updateIntervalInSeconds, false);
    }

    private boolean checkAndPerhapsSetUpdateIntervalInSeconds(int updateIntervalInSeconds,
            boolean runTask) {
        String errorMessage = logic.checkUpdateIntervalInSecondsIsInRange(updateIntervalInSeconds);
        if (errorMessage.isEmpty()) {
            if (runTask) {
                logic.setUpdateIntervalInSeconds(updateIntervalInSeconds);
            }
            else {
                logic.setUpdateIntervalInSecondsWithoutRun(updateIntervalInSeconds);
            }
            return true;
        }
        else {
            GuiTools.informUser(gui.getWindowAsComponent(),
                    "Fehlerhaftes Aktualisierungsintervall eingegeben!", errorMessage);
            return false;
        }
    }

    private Component createUpdateNowButton() {
        updateNowButton.setFocusable(false);
        updateNowButton.setEnabled(true);
        updateNowButton.addActionListener(e -> logic.updateNow());
        return updateNowButton;
    }

    /** Setzt das Aktualisierungsintervall für die Anzeige in der Gui. */
    public void showIntervalTimeInSeconds(int seconds) {
        String text;
        if (seconds < 61) {
            text = seconds + " Sekunden";
        }
        else {
            text = TimeHelper.secondsToHoursMinutesSeconds(seconds);
        }
        intervalLabel.setText(text);
        intervalLabel.repaint();
    }

    /**
     * Informiert darüber, dass der regelmäßige Job einmal ausgeführt wurde.
     *
     * @param duration
     *            Dauer des Jobs (wird von einer StopWatch bereits im hübschen Format hh:mm:ss
     *            übergeben).
     * @param updateCount
     *            Anzahl Ausführungen des Jobs.
     * @param startDateAndTime
     *            Startzeit und -datum der Ausführung.
     */
    public void jobDone(String duration, int updateCount, DateAndTime startDateAndTime) {
        SwingUtilities.invokeLater(() -> jobDoneInEdt(duration, updateCount, startDateAndTime));
    }

    private void jobDoneInEdt(String duration, int updateCount, DateAndTime startDateAndTime) {
        lastDurationLabel.setText(duration);
        countLabel.setText(NumberString.taupu(updateCount));

        String date = startDateAndTime.getDate().toString();
        String time = startDateAndTime.getTime().toString();
        startDateLabel.setText(date);
        startTimeLabel.setText(time);

        gui.repaint();
        gui.endLongTimeProcess(); // das nutzt eh invokeLater

        long seconds = TimeHelper.hoursMinutesSecondsToSeconds(duration);
        logic.changeUpdateIntervalIfUpdateTookTooLong(seconds);
    }

    /**
     * Startet einen Prozess, der eine Weile braucht.
     *
     * @param message
     *            Anzuzeigende Nachricht.
     */
    public void startLongTimeProcess(String message) {
        gui.startLongTimeProcess(message);
    }

    /** Startet den Countdown erneut. */
    public void restartCountDownWatch(int updateIntervalInSeconds) {
        countDownWatchLabel.setDurationInSeconds(updateIntervalInSeconds);
        countDownWatchLabel.start();
    }

    /** Beendet den Timer vom CountDownLabel. */
    public void quit() {
        countDownWatchLabel.stopActualisationRunnable();
    }

    /** Führt den Aktualisierungs-Button aus, als wäre daraufgeklickt worden. */
    public void pressActualizeButton() {
        updateNowButton.doClick();
    }

}
