package de.duehl.swing.ui.filter.dialog;

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

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.io.FineFileWriter;
import de.duehl.swing.ui.buttons.painted.GoBackButton;
import de.duehl.swing.ui.buttons.painted.MinusButton;
import de.duehl.swing.ui.buttons.painted.PlusButton;
import de.duehl.swing.ui.filter.exceptions.FilterException;
import de.duehl.swing.ui.filter.project.gateway.FilterGateway;

/**
 * Diese Klasse stellt eine Zeile des Hauptteils des Filterkombinationsdialoges dar.
 *
 * @version 1.01     2025-05-07
 * @author Christian Dühl
 */

public class FilterCombinationLine<Data, Type> {

    private static final Insets BUTTON_INSETS = new Insets(0, 3, 0, 3);
    static final String BITTE_WAEHLEN = "- bitte wählen -";

    /** Panel mit den Zeilen des Filters aus dem FilterDialog. */
    private final JPanel combinationPanel;

    /** Zeilennummer dieser Zeile. Wird hier je nach Einfügen oder Löschen verändert. */
    private int lineNumber;

    /** Objekt das eindeutige Beschreibungen der Methoden zum Filtern liefert. */
    private final FilterGateway<Data, Type> gateway;

    /** Kann Zeilen hinzufügen oder entfernen. */
    private final FilterCombinationLineHandler<Data, Type> lineHandler;

    /**
     * Gibt an, welcher Typ Zeile diese hier ist. Der wird je nach Einstellung der Zeile verändert.
     */
    private FilterCombinationLineType type;

    /** Panel mit der Zeile, die hier aufgebaut wird. */
    private final JPanel linePanel;

    /* Gui-Elemente */
    private JComboBox<String> methodComboBox;
    private JTextField parameterField;

    /**
     * Konstruktor
     *
     * @param combinationPanel
     *            JPanel, in das die zu erzeugende Zeile eingebaut wird.
     * @param lineNumber
     *            Zeilennummer dieser Zeile.
     * @param gateway
     *            Objekt das eindeutige Beschreibungen der Methoden zum Filtern liefert.
     * @param lineHandler
     *            Kann Zeilen hinzufügen oder entfernen.
     */
    public FilterCombinationLine(JPanel combinationPanel, int lineNumber,
            FilterGateway<Data, Type> gateway,
            FilterCombinationLineHandler<Data, Type> lineHandler) {
        this.combinationPanel = combinationPanel;
        this.lineNumber = lineNumber;
        this.gateway = gateway;
        this.lineHandler = lineHandler;

        linePanel = new JPanel();
        initLinePanel();

        createStartLine();
    }

    /**
     * Konstruktor aus einer Zeile einer eingelesenen Filter-Datei.
     *
     * @param combinationPanel
     *            JPanel, in das die zu erzeugende Zeile eingebaut wird.
     * @param lineNumber
     *            Zeilennummer dieser Zeile.
     * @param filterMethods
     *            Objekt das eindeutige Beschreibungen der Methoden zum Filtern liefert.
     * @param lineHandler
     *            Kann Zeilen hinzufügen oder entfernen.
     * @param line
     *            Zeile einer eingelesenen Filter-Datei.
     */
    public FilterCombinationLine(JPanel combinationPanel, int lineNumber,
            FilterGateway<Data, Type> filterMethods,
            FilterCombinationLineHandler<Data, Type> lineHandler, String line) {
        this(combinationPanel, lineNumber, filterMethods, lineHandler);

        String[] parts = line.split("\\|");
        boolean success = true;
        if (2 > parts.length) {
            success = false;
        }
        else if (lineNumber != Integer.parseInt(parts[0]) ) {
            success = false;
        }
        else if (2 == parts.length) {
            switch (parts[1]) {
                case "START":
                    break;
                case "INTERSECTION":
                    createIntersection();
                    break;
                case "UNION":
                    createUnion();
                    break;
                case "(":
                    createOpeningBrace();
                    break;
                case "NOT(":
                    createNegatedOpeningBrace();
                    break;
                case ")":
                    createClosingBrace();
                    break;
                default:
                    success = false;
                    break;
            }
        }
        else if (3 == parts.length) {
            if ("METHOD".equals(parts[1])) {
                createMethodLine();
            }
            else if ("NOT METHOD".equals(parts[1])) {
                createNegatedMethodLine();
            }
            else {
                success = false;
            }

            if (success) {
                methodComboBox.setSelectedItem(parts[2]);
            }
        }
        else if (4 == parts.length) {
            if ("PARAMISED METHOD".equals(parts[1])) {
                createParamisedMethodLine();
            }
            else if ("NOT PARAMISED METHOD".equals(parts[1])) {
                createNegatedParamisedMethodLine();
            }
            else {
                success = false;
            }

            if (success) {
                methodComboBox.setSelectedItem(parts[2]);
                parameterField.setText(parts[3]);
            }
        }

        if (!success) {
            throw new FilterException("Kein Erfolg beim Vestehen der Filterzeile:\n"
                    + "\t" + "line = " + line);
        }
    }

    private void initLinePanel() {
        linePanel.setLayout(new BorderLayout(5, 0));
        combinationPanel.add(linePanel);
    }

    /** Erzeugt eine Zeile im Ausgangszustand. */
    private void createStartLine() {
        type = FilterCombinationLineType.START;

        linePanel.removeAll();
        linePanel.add(createAndAndRemoveButtonPart(), BorderLayout.WEST);
        linePanel.add(createChoiceComboBox(), BorderLayout.EAST);

        /* Nicht verwendete Elemente null setzen: */
        methodComboBox = null;
        parameterField = null;
    }

    private Component createAndAndRemoveButtonPart() {
        JPanel buttonPanel = new JPanel(new FlowLayout());
        buttonPanel.add(createAddButton());
        buttonPanel.add(createDeleteButton());
        return buttonPanel;
    }

    private Component createAndRemoveAndRevertButtonPart() {
        JPanel buttonPanel = new JPanel(new FlowLayout());
        buttonPanel.add(createBackToStartButton());
        buttonPanel.add(createAddButton());
        buttonPanel.add(createDeleteButton());
        return buttonPanel;
    }

    private Component createBackToStartButton() {
        JButton addButton = new GoBackButton();
        addButton.setFocusable(false);
        addButton.setMargin(BUTTON_INSETS);
        addButton.addActionListener(e -> createStartLine());

        return addButton;
    }

    private Component createAddButton() {
        JButton addButton = new PlusButton();
        addButton.setFocusable(false);
        addButton.setMargin(BUTTON_INSETS);
        addButton.addActionListener(e -> lineHandler.addLine(this));

        return addButton;
    }

    private Component createDeleteButton() {
        JButton button = new MinusButton();
        button.setFocusable(false);
        button.setMargin(BUTTON_INSETS);
        button.addActionListener(e -> lineHandler.removeLine(this));

        return button;
    }

    private static final String CHOICE_METHOD = "Methode";
    private static final String CHOICE_NEGATED_METHOD = "negierte Methode";
    private static final String CHOICE_PARAMISED_METHOD = "parametrisierte Methode";
    private static final String CHOICE_NEGATED_PARAMISED_METHOD = "negierte parametrisierte Methode";
    private static final String CHOICE_OPENING_BRACE = "Klammer auf";
    private static final String CHOICE_NEGATED_OPENING_BRACE = "negierte Klammer auf";
    private static final String CHOICE_CLOSING_BRACE = "Klammer zu";
    private static final String CHOICE_SCHNITTMENGE = "Schnittmenge (und)";
    private static final String CHOICE_VEREINIGUNG = "Vereinigung (oder)";

    private Component createChoiceComboBox() {
        List<String> choicesList = new ArrayList<>();
        choicesList.add(BITTE_WAEHLEN);

        if (gateway.hasMethods()) {
            choicesList.add(CHOICE_METHOD);
            choicesList.add(CHOICE_NEGATED_METHOD);
        }
        if (gateway.hasParamisedMethods()) {
            choicesList.add(CHOICE_PARAMISED_METHOD);
            choicesList.add(CHOICE_NEGATED_PARAMISED_METHOD);
        }

        choicesList.add(CHOICE_OPENING_BRACE);
        choicesList.add(CHOICE_NEGATED_OPENING_BRACE);
        choicesList.add(CHOICE_CLOSING_BRACE);
        choicesList.add(CHOICE_SCHNITTMENGE);
        choicesList.add(CHOICE_VEREINIGUNG);

        String[] choices = CollectionsHelper.stringListToArray(choicesList);

        /*
        String[] choices = {
                BITTE_WAEHLEN,
                CHOICE_METHOD,
                CHOICE_NEGATED_METHOD,
                CHOICE_PARAMISED_METHOD,
                CHOICE_NEGATED_PARAMISED_METHOD,
                CHOICE_OPENING_BRACE,
                CHOICE_NEGATED_OPENING_BRACE,
                CHOICE_CLOSING_BRACE,
                CHOICE_SCHNITTMENGE,
                CHOICE_VEREINIGUNG
        };
        */

        JComboBox<String> choiceComboBox = new JComboBox<>();
        for (String negation : choices) {
            choiceComboBox.addItem(negation);
        }

        choiceComboBox.addActionListener(e ->
                replaceStartLineByWantedLine((String) choiceComboBox.getSelectedItem()));

        return choiceComboBox;
    }

    private void replaceStartLineByWantedLine(String selectedChoice) {
        switch (selectedChoice) {
            case CHOICE_METHOD:
                createMethodLine();
                break;
            case CHOICE_NEGATED_METHOD:
                createNegatedMethodLine();
                break;
            case CHOICE_PARAMISED_METHOD:
                createParamisedMethodLine();
                break;
            case CHOICE_NEGATED_PARAMISED_METHOD:
                createNegatedParamisedMethodLine();
                break;
            case CHOICE_OPENING_BRACE:
                createOpeningBrace();
                break;
            case CHOICE_NEGATED_OPENING_BRACE:
                createNegatedOpeningBrace();
                break;
            case CHOICE_CLOSING_BRACE:
                createClosingBrace();
                break;
            case CHOICE_SCHNITTMENGE:
                createIntersection();
                break;
            case CHOICE_VEREINIGUNG:
                createUnion();
                break;
        }
    }

    /** Erzeugt eine Zeile mit einer einfachen Methode ohne Negierung. */
    private void createMethodLine() {
        type = FilterCombinationLineType.METHOD;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" Methode "), BorderLayout.CENTER);
        linePanel.add(createMethodComboBox(), BorderLayout.EAST);

        validate();
    }

    /** Erzeugt die Methodenauswahlcombobox. */
    private Component createMethodComboBox() {
        JComboBox<String> methodComboBox = new JComboBox<>();
        methodComboBox.addItem(BITTE_WAEHLEN);
        for (String method : gateway.getMethods()) {
            methodComboBox.addItem(method);
        }

        this.methodComboBox = methodComboBox;

        return methodComboBox;
    }

    /** Erzeugt eine Zeile mit einer einfachen Methode mit Negierung. */
    private void createNegatedMethodLine() {
        type = FilterCombinationLineType.NEGATED_METHOD;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" nicht Methode "), BorderLayout.CENTER);
        linePanel.add(createMethodComboBox(), BorderLayout.EAST);

        validate();
    }

    /** Erzeugt eine Zeile mit einer parametrisierten Methode ohne Negierung. */
    private void createParamisedMethodLine() {
        type = FilterCombinationLineType.PARAMISED_METHOD;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" parametrisierte Methode "), BorderLayout.CENTER);
        linePanel.add(createParamizedMethodComboBoxAndParameterField(), BorderLayout.EAST);

        validate();
    }

    /** Erzeugt die Methodenauswahlcombobox und Eingabefeld. */
    private Component createParamizedMethodComboBoxAndParameterField() {
        JPanel panel = new JPanel(new FlowLayout());

        panel.add(createParamizedMethodComboBox());
        panel.add(createParameterField());

        return panel;
    }

    private Component createParamizedMethodComboBox() {
        JComboBox<String> paramizedMethodComboBox = new JComboBox<>();
        paramizedMethodComboBox.addItem(BITTE_WAEHLEN);
        for (String method : gateway.getParamisedMethods()) {
            paramizedMethodComboBox.addItem(method);
        }

        this.methodComboBox = paramizedMethodComboBox;

        return paramizedMethodComboBox;
    }

    private JTextField createParameterField() {
        JTextField parameterField = new JTextField("", 6);
        parameterField.requestFocus();

        this.parameterField = parameterField;

        return parameterField;
    }

    /** Erzeugt eine Zeile mit einer parametrisierten Methode mit Negierung. */
    private void createNegatedParamisedMethodLine() {
        type = FilterCombinationLineType.NEGATED_PARAMISED_METHOD;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" nicht parametrisierte Methode "), BorderLayout.CENTER);
        linePanel.add(createParamizedMethodComboBoxAndParameterField(), BorderLayout.EAST);

        validate();
    }

    private void createOpeningBrace() {
        type = FilterCombinationLineType.BRACE_OPEN;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" ("), BorderLayout.CENTER);

        validate();
    }

    private void createNegatedOpeningBrace() {
        type = FilterCombinationLineType.NEGATED_BRACE_OPEN;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" nicht ("), BorderLayout.CENTER);

        validate();
    }

    private void createClosingBrace() {
        type = FilterCombinationLineType.BRACE_CLOSE;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" )"), BorderLayout.CENTER);

        validate();
    }

    private void createIntersection() {
        type = FilterCombinationLineType.INTERSECTION;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" geschnitten mit (und)"), BorderLayout.CENTER);

        validate();
    }

    private void createUnion() {
        type = FilterCombinationLineType.UNION;

        linePanel.removeAll();
        linePanel.add(createAndRemoveAndRevertButtonPart(), BorderLayout.WEST);
        linePanel.add(new JLabel(" vereinigt mit (oder)"), BorderLayout.CENTER);

        validate();
    }

    /** Validiert die aktuelle Zeile. */
    public void validate() {
        linePanel.validate();
        combinationPanel.validate();
    }


    /******************************************
    ***                                     ***
    ***      S e t t e r / G e t t e r      ***
    ***                                     ***
    ******************************************/

    /** Setter für die Zeilennummer. */
    public void setLineNumber(int lineNumber) {
        this.lineNumber = lineNumber;
    }

    /** Getter für die Zeilennummer. */
    public int getLineNumber() {
        return lineNumber;
    }

    /** Getter für die Methodenauswahlcombobox Auswahl des Benutzers. */
    public String getMethodComboBoxSelection() {
        if (null == methodComboBox) {
            throw new FilterException("methodComboBox ist null!");
        }
        else {
            return(String) methodComboBox.getSelectedItem();
        }
    }

    /** Getter für den Eintrag im Parameterfeld. */
    public String getParameter() {
        if (null == parameterField) {
            throw new FilterException("parameterField ist null!");
        }
        else {
            return parameterField.getText();
        }
    }

    /** Gibt den Typen der Zeile als String zurück. */
    public String getTypeAsString() {
        return type.toString();
    }

    /** Getter für das Panel mit der Zeile, die hier aufgebaut wird. */
    public JPanel getLinePanel() {
        return linePanel;
    }

    /******************************************
    ***                                     ***
    ***        T y p a b f r a g e n        ***
    ***                                     ***
    ******************************************/

    public boolean isInStartPhase() {
        return type == FilterCombinationLineType.START;
    }

    public boolean isMethodLine() {
        return type == FilterCombinationLineType.METHOD;
    }

    public boolean isNegatedMethodLine() {
        return type == FilterCombinationLineType.NEGATED_METHOD;
    }

    public boolean isParamisedMethodLine() {
        return type == FilterCombinationLineType.PARAMISED_METHOD;
    }

    public boolean isParamisedNegatedMethodLine() {
        return type == FilterCombinationLineType.NEGATED_PARAMISED_METHOD;
    }

    public boolean isIntersectionLine() {
        return type == FilterCombinationLineType.INTERSECTION;
    }

    public boolean isUnionLine() {
        return type == FilterCombinationLineType.UNION;
    }

    public boolean isBraceOpenLine() {
        return type == FilterCombinationLineType.BRACE_OPEN;
    }

    public boolean isNegatedBraceOpenLine() {
        return type == FilterCombinationLineType.NEGATED_BRACE_OPEN;
    }

    public boolean isBraceCloseLine() {
        return type == FilterCombinationLineType.BRACE_CLOSE;
    }

    /** Schreibt die Beschreibung der Zeile mit dem übergebene Writer in eine Datei. */
    public void write(FineFileWriter writer) {
        switch (type) {
            case START:
                writer.writeln(lineNumber + "|START");
                break;
            case METHOD:
                writer.writeln(lineNumber + "|METHOD|"
                        + methodComboBox.getSelectedItem());
                break;
            case NEGATED_METHOD:
                writer.writeln(lineNumber + "|NOT METHOD|"
                        + methodComboBox.getSelectedItem());
                break;
            case PARAMISED_METHOD:
                writer.writeln(lineNumber + "|PARAMISED METHOD|"
                        + methodComboBox.getSelectedItem()
                        + "|"
                        + parameterField.getText());
                break;
            case NEGATED_PARAMISED_METHOD:
                writer.writeln(lineNumber + "|NOT PARAMISED METHOD|"
                        + methodComboBox.getSelectedItem()
                        + "|"
                        + parameterField.getText());
                break;
            case INTERSECTION:
                writer.writeln(lineNumber + "|INTERSECTION");
                break;
            case UNION:
                writer.writeln(lineNumber + "|UNION");
                break;
            case BRACE_OPEN:
                writer.writeln(lineNumber + "|(");
                break;
            case NEGATED_BRACE_OPEN:
                writer.writeln(lineNumber + "|NOT(");
                break;
            case BRACE_CLOSE:
                writer.writeln(lineNumber + "|)");
                break;
            default:
                writer.writeln(lineNumber + "|UNKNOWN STUFF");
                break;
        }
    }

    /**
     * Ermittelt zu einer Zeile mit Methoden-Combobox den Typ der gewählten Methode. Dafür wird auf
     * die Arrays methodTypes und paramisedMethodTypes zurückgegriffen, die die Typen zu den
     * Beschreibungen enthalten.
     *
     * Mit Hilfe von FilterMethods sind die Beschreibungen eindeutig und können außerhalb in einen
     * Typ umgewandelt werden.
     *
     * @return Beschreibung des Filters (Methode)
     */
    public String getMethodDescription() {
        String description = getMethodComboBoxSelection();
        if (description.equals(BITTE_WAEHLEN)) {
            throw new FilterException("Inhalt der methodComboBox ist noch '" + BITTE_WAEHLEN + "'!");
        }
        return description;
    }

}
