package de.duehl.swing.ui.dialogs.logging;

/*
 * Copyright 2022 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.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableCellRenderer;

import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.colors.Colorizer;
import de.duehl.swing.ui.components.elements.TitledLabel;
import de.duehl.swing.ui.components.selections.StringSelection;
import de.duehl.swing.ui.dialogs.base.NonModalFrameDialogBase;
import de.duehl.swing.ui.tables.DifferentBackgroundsTableRenderer;
import de.duehl.basics.logging.LogEntry;
import de.duehl.basics.logging.reader.LogFileReader;

/**
 * Diese Klasse stellt einen Dialog zur Anzeige eines Logfiles dar.
 *
 * @version 1.03     2022-07-20
 * @author Christian Dühl
 */

public class LogfileDialog extends NonModalFrameDialogBase {

    private static final Dimension DIALOG_DIMENSION = new Dimension(1400, 950);

    /** Vordergrundfarbe für die Tabelle. */
    private final Color foreground;

    /** Hintergrundfarbe für die Tabelle. */
    private final Color background;

    /** Name der Log-Datei. */
    private final String logFileName;

    /** Angezeigten Tabelle. */
    private final JTable logTable;

    /** Modell der angezeigten Tabelle. */
    private final LogFileTableModel logTableModel;

    /** Gibt an, wieherum das Logfile angezeigt werden soll. */
    private boolean showFirstLineAtTop;

    /** Gibt an, ob auf Änderungen der Selektion der Tabelle reagiert werden soll. */
    private boolean reactAtTableSelection;

    private final List<LogEntry> allLogEntries;
    private final List<LogEntry> shownLogEntries;

    private final StringSelection sortField;

    private final TitledLabel numberOfAllElementsLabel;
    private final TitledLabel numberOfFilteredElementsLabel;

    private final JRadioButton messageButton;
    private final JRadioButton methodButton;
    private final JRadioButton classButton;

    private final JLabel warningLabel;

    /**
     * Konstruktor.
     *
     * @param logFileName
     *            Name der Log-Datei.
     * @param parentLocation
     *            Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird.
     */
    public LogfileDialog(String logFileName, Point parentLocation) {
        this(logFileName, null, parentLocation, null);
    }

    /**
     * Konstruktor.
     *
     * @param logFileName
     *            Name der Log-Datei.
     * @param parentLocation
     *            Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird.
     * @param programImage
     *            Anzuzeigendes ProgrammIcon.
     */
    public LogfileDialog(String logFileName, Point parentLocation, Image programImage) {
        this(logFileName, null, parentLocation, programImage);
    }

    /**
     * Konstruktor mit Colorizer.
     *
     * @param logFileName
     *            Name der Log-Datei.
     * @param colorizer
     *            Farbverwaltung für die Gui.
     * @param parentLocation
     *            Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird.
     * @param programImage
     *            Anzuzeigendes ProgrammIcon.
     */
    public LogfileDialog(String logFileName, Colorizer colorizer, Point parentLocation,
            Image programImage) {
        super(parentLocation, programImage, "Logfile", DIALOG_DIMENSION, colorizer);
        this.logFileName = logFileName;

        foreground = determineForeground();
        background = determineBackground();

        logTable = new JTable();
        logTableModel = new LogFileTableModel();

        sortField = new StringSelection("Filter: ");
        messageButton = new JRadioButton("in der Nachricht");
        methodButton = new JRadioButton("im Methodennamen");
        classButton = new JRadioButton("im Klassennamen");
        numberOfAllElementsLabel = new TitledLabel("Zeilen gesamt:", "");
        numberOfFilteredElementsLabel = new TitledLabel("Zeilen im Filter:", "");

        showFirstLineAtTop = true;
        topDownBox = new JCheckBox("frühesten Eintrag oben anzeigen");
        topDownBox.setSelected(showFirstLineAtTop);
        warningLabel = new JLabel();

        reactAtTableSelection = false;
        addEscapeBehaviour();
        fillDialog();

        allLogEntries = readLog();
        shownLogEntries = new ArrayList<>();
        filter();
    }

    @Override
    protected void populateDialog() {
        add(warningLabel, BorderLayout.NORTH);
        add(createCenterPart(), BorderLayout.CENTER);
        add(createLowerPart(), BorderLayout.SOUTH);
    }

    private Component createCenterPart() {
        JPanel panel = new JPanel();
        setColors(panel);
        panel.setLayout(new BorderLayout());

        panel.add(createLogTable(), BorderLayout.CENTER);

        return panel;
    }

    private Component createLogTable() {
        JTable logTable = createTable();

        JScrollPane scrollPane = new JScrollPane(logTable);
        scrollPane.setPreferredSize(new Dimension(250, 10));
        setColors(scrollPane);

        return scrollPane;
    }

    private JTable createTable() {
        logTable.setModel(logTableModel);
        logTable.setColumnModel(new LogFileColumnModel());
        logTable.setDefaultRenderer(Object.class, createRenderer());
        logTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        logTable.getSelectionModel().addListSelectionListener(createSelectionListener());
        logTable.setRowHeight((int) (1.6 * DifferentBackgroundsTableRenderer.FONT_SIZE));

        setColors(logTable);

        return logTable;
    }

    private TableCellRenderer createRenderer() {
        return new DifferentBackgroundsTableRenderer(foreground, background);
    }

    private ListSelectionListener createSelectionListener() {
        return new ListSelectionListener() {

            @Override
            public void valueChanged(ListSelectionEvent e) {
                // Falls man doch mal Die Tabelle leeren und neu füllen will, siehe
                // RelatedEditorGui#setRelatedsIntoTable()

                if (!e.getValueIsAdjusting() && reactAtTableSelection) {
                    // nur bei ListSelectionModel.SINGLE_SELECTION:
                    int row = logTable.getSelectedRow();
                    if (row > -1) {
                        tableRowSelected(row);
                    }
                }
            }
        };
    }

    private void tableRowSelected(int entryIndex) {
        clearSelection();
        List<LogEntry> entries = logTableModel.getRows();
        Point myLocation = getDialog().getLocation();
        LogEntryDialog dialog = new LogEntryDialog(entries, entryIndex, logFileName,
                getColorizer(), myLocation, getProgramImage());
        dialog.setVisible(true);

        clearSelection();
        //fillLogIntoTable();
        sortField.requestFocus();
    }

    private void clearSelection() {
//        TableCellEditor cellEditor = logTable.getCellEditor();
//        if (cellEditor != null) {
//            if (!cellEditor.stopCellEditing()) {
//                cellEditor.cancelCellEditing();
//            }
//        }
//
//        int row = logTable.getSelectedRow();
//        int column = logTable.getSelectedColumn();
//        if (row > -1 && column > -1) {
//            TableCellEditor editor = logTable.getCellEditor(row, column);
//            editor.cancelCellEditing();
//            editor.stopCellEditing();
//        }
//

        logTable.clearSelection();
        logTable.validate();
        logTable.revalidate();
        logTable.repaint();
    }

    /**
     * Erzeugt den unteren Bereich mit den Buttons.
     *
     * @return Erzeugtes Panel.
     */
    private Component createLowerPart() {
        JPanel panel = new JPanel();
        setColors(panel);
        panel.setLayout(new BorderLayout());

        panel.add(createSearchPart(), BorderLayout.WEST);
        panel.add(GuiTools.centerHorizontal(createInfoAndOrientationPart()), BorderLayout.CENTER);
        panel.add(createQuitButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createSearchPart() {
        JPanel panel = new JPanel();
        setColors(panel);
        panel.setLayout(new BorderLayout());

        panel.add(createSearchField(), BorderLayout.NORTH);
        panel.add(createSearchOptions(), BorderLayout.SOUTH);

        return panel;
    }

    private Component createSearchField() {
        sortField.makeHorizontal();
        sortField.addReturnListener(() -> filter());
        return sortField.getPanel();
    }

    private void filter() {
        String search = sortField.getTrimmedText();
        shownLogEntries.clear();
        if (search.isEmpty()) {
            shownLogEntries.addAll(allLogEntries);
        }
        else {
            boolean inMessage = messageButton.isSelected();
            boolean inMethod = methodButton.isSelected();
            boolean inClass = classButton.isSelected();
            for (LogEntry entry : allLogEntries) {
                String toSearchIn = "";
                if (inMessage) {
                    toSearchIn = entry.getMessage();
                }
                else if (inMethod) {
                    toSearchIn = entry.getMethod();
                }
                else if (inClass) {
                    toSearchIn = entry.getClassName();
                }
                if (toSearchIn.contains(search)) {
                    shownLogEntries.add(entry);
                }
            }
        }
        fillLogIntoTable();
    }

    private Component createSearchOptions() {
        JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));

        ButtonGroup group = new ButtonGroup();
        group.add(methodButton);
        group.add(classButton);
        group.add(messageButton);

        messageButton.setSelected(true);

        panel.add(methodButton);
        panel.add(classButton);
        panel.add(messageButton);

        return panel;
    }

    private JComponent createInfoAndOrientationPart() {
        JPanel panel = new JPanel();
        setColors(panel);
        panel.setLayout(new BorderLayout());

        panel.add(createInfoPart(), BorderLayout.NORTH);
        panel.add(createOrientaionOptions(), BorderLayout.SOUTH);

        return panel;
    }

    private Component createInfoPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 0));

        numberOfAllElementsLabel.setValueColor(Color.BLUE);
        numberOfFilteredElementsLabel.setValueColor(Color.BLUE);

        panel.add(numberOfAllElementsLabel.getPanel());
        panel.add(numberOfFilteredElementsLabel.getPanel());

        return panel;
    }

    private final JCheckBox topDownBox;

    private Component createOrientaionOptions() {
        topDownBox.addActionListener(e -> changeOrientation());
        topDownBox.setSelected(showFirstLineAtTop);
        return topDownBox;
    }

    private void changeOrientation() {
        showFirstLineAtTop = topDownBox.isSelected();
        fillLogIntoTable();
    }

    private JButton createQuitButton() {
        JButton quitButton = new JButton("Schließen");
        setColors(quitButton);
        quitButton.addActionListener(e -> closeDialog());
        return quitButton;
    }

    private List<LogEntry> readLog() {
        LogFileReader reader = new LogFileReader(logFileName);
        List<LogEntry> entries = reader.read();

        if (entries.size() > 0) {
            entries.remove(0); // Titelzeile wegwerfen
        }

        return entries;
    }

    /**
     * Gibt an, ob das Logfile umgedreht angezeigt werden soll. Normalerweise werden die Zeilen des
     * Logfiles in der Reihenfolge angezeigt, in der sie geschrieben wurden. Wird diese Methode
     * aufgerufen, so werden die Zeilen andersherum, zuerst die neuste bis zuletzt die älteste
     * Zeile angezeigt.
     *
     * Muss vor setVisible() aufgerufen werden.
     */
    public void showLastLineAtTop() {
        showFirstLineAtTop = false;
        topDownBox.setSelected(showFirstLineAtTop);
        //fillLogIntoTable(); Nein, muss vor setVisible aufgerufen werden!
    }

    @Override
    public void setVisible(boolean visible) {
        /* Kein SwingUtilities.invokeLater(), sonst stoppt der aufrufende Prozess nicht! */
        fillLogIntoTable();
        super.setVisible(visible);
    }

    private void fillLogIntoTable() {
        reactAtTableSelection = false;
        logTable.removeAll();
        logTableModel.clear();

        List<LogEntry> copiedLogEntries = new ArrayList<>();
        copiedLogEntries.addAll(shownLogEntries);
        if (!showFirstLineAtTop) {
            Collections.reverse(copiedLogEntries);
        }

        for (LogEntry element : copiedLogEntries) {
            logTableModel.addEntry(element);
        }

        numberOfAllElementsLabel.setValue(Integer.toString(allLogEntries.size()));
        numberOfFilteredElementsLabel.setValue(Integer.toString(shownLogEntries.size()));

        //clearSelection();
        reactAtTableSelection = true;
        refresh();
    }

    public void showNoAutomaticUpdate() {
        warningLabel.setText("<html><body>"
                + "Es wird das Logfile des jetzt gerade laufenden Programms angezeigt. "
                + "Das Logfile wird <b>nicht</b> automatisch aktualisiert."
                + "</body></html>");
        warningLabel.setForeground(Color.RED);
        warningLabel.setHorizontalAlignment(JLabel.CENTER);
        warningLabel.setBorder(new EmptyBorder(10, 20, 10, 20));
        GuiTools.biggerFont(warningLabel, 10);
    }

}
