package de.duehl.basics.java;

/*
 * 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.duehl.basics.io.Charset;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.java.data.EnumAnalyseState;
import de.duehl.basics.java.data.EnumDefinition;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse sucht im eingelesenen Quellcode einer (meiner!) Enum-Klassen nach den Definitionen
 * und dem optionalen Inhalt in der Klammer nach den Definitionen.
 *
 * Es wird nicht zwischen einer leeren und einer nicht vorhandenen Klammer unterschieden.
 *
 * @version 1.03     2025-11-28
 * @author Christian Dühl
 */

public class JavaEnumAnalyser {

    /** Die Zeilen mit dem Quellcode der Enum-Datei (xxx.java). */
    private final List<String> lines;

    /** Die Liste der gefundenen Enum-Definitionen. */
    private List<EnumDefinition> enumDefinitons;

    /** Der Zustand in dem sich die Analyse befindet. */
    private EnumAnalyseState state;

    /** Die Zeile, die aktuell bearbeitet wird. */
    private String actualLine;

    /** Der zu analysierende Rest de aktuellen Zeile. */
    private String rest;

    /** Die Definition, die aktuell bearbeitet wird. */
    private String actualDefiniton;

    /** Der Inhalt der gerade analysierten Klammer. Diese kann über mehrere Zeilen gehen. */
    private String actualBraceContent;

    /**
     * Konstruktor.
     *
     * @param filename
     *            Der Dateiname mit Pfad der Enum-Datei (xxx.java).
     */
    public JavaEnumAnalyser(String filename) {
        lines = FileHelper.readFileToList(filename, Charset.UTF_8);
    }

    /**
     * Konstruktor.
     *
     * @param lines
     *            Die Zeilen mit dem Quellcode der Enum-Datei (xxx.java).
     */
    public JavaEnumAnalyser(List<String> lines) {
        this.lines = lines;
    }

    /** Führt die Analyse durch. */
    public void analyse() {
        enumDefinitons = new ArrayList<>();

        state = EnumAnalyseState.FIRST_NOT_FOUND;

        for (String line : lines) {
            actualLine = line;
            analyseLine();
        }

        checkDefinitionsAreDisjunct();
    }

    private void analyseLine() {
        switch (state) {
            case FIRST_NOT_FOUND:
            case DEFINITION_CLOSED:
                searchForDefinitonStart();
                break;
            case DEFINITION_STARTED_BUT_NOT_CLOSED:
                analyseLineWithAdditionalBraceContent();
                break;
            case ALL_DEFINITIONS_CLOSED:
                // nicht tun
                break;
            default:
                throw new RuntimeException("Unbekannter EnumAnalyseState '" + state + "'");
        }
    }

    private static final String DEFINITION_LINE_START = "    ";
    private static final String DEFINITION_LINE_END_KOMMA = ",";
    private static final String DEFINITION_LINE_END_SEMIKOLON = ";";

    private static final String ENUM_DEFINITION_WORD_REGEX = "[A-ZÄÖÜ][_A-ZÄÖÜ0-9]*";
    private static final Pattern ENUM_DEFINITION_WORD_PATTERN =
            Pattern.compile(ENUM_DEFINITION_WORD_REGEX);

    private static final String OPTIONAL_BRACE_START = "(";
    private static final String OPTIONAL_BRACE_END = ")";

    private void searchForDefinitonStart() {
        if (actualLine.startsWith(DEFINITION_LINE_START)) {
            rest = actualLine.substring(DEFINITION_LINE_START.length());
            rest = rest.strip();
            if (!rest.isEmpty()) {
                analyseLineWithStartingDefiniton();
            }
        }
    }

    private void analyseLineWithStartingDefiniton() {
        if (rest.startsWith(DEFINITION_LINE_END_SEMIKOLON)) {
            state = EnumAnalyseState.ALL_DEFINITIONS_CLOSED;
            return;
        }

        state = EnumAnalyseState.DEFINITION_STARTED_BUT_NOT_CLOSED;

        Matcher matcher = ENUM_DEFINITION_WORD_PATTERN.matcher(rest);
        boolean found = matcher.find();
        if (!found) {
            throw new RuntimeException("Analysefehler bei Definition, sie wird nicht gefunden:\n"
                    + "\t" + "actualLine = '" + actualLine + "'\n"
                    + "\t" + "rest       = '" + rest + "'\n"
                    );
        }
        if (matcher.start() > 0) {
            throw new RuntimeException("Analysefehler bei Definition, sie wird zu weit hinten "
                    + "gefunden:\n"
                    + "\t" + "actualLine      = '" + actualLine + "'\n"
                    + "\t" + "rest            = '" + rest + "'\n"
                    + "\t" + "matcher.start() = '" + matcher.start() + "'\n"
                    );
        }

        actualDefiniton = matcher.group();
        rest = rest.substring(actualDefiniton.length());
        rest = rest.strip();
        analyseAfterDefintion();
    }

    private void analyseAfterDefintion() {
        if (rest.startsWith(OPTIONAL_BRACE_START)) {
            actualBraceContent = "";
            rest = rest.substring(OPTIONAL_BRACE_START.length());
            analyseInBrace();
        }
        else {
            actualBraceContent = "";
            analyseRestAfterBraceOrWithoutBrace();
        }
    }

    private void analyseInBrace() {
        int openCount1 = Text.countPartInString(actualBraceContent, OPTIONAL_BRACE_START);
        int closeCount1 = Text.countPartInString(actualBraceContent, OPTIONAL_BRACE_END);

        int openCount2 = Text.countPartInString(rest, OPTIONAL_BRACE_START);
        int closeCount2 = Text.countPartInString(rest, OPTIONAL_BRACE_END);

        int openCount = openCount1 + openCount2;
        int closeCount = closeCount1 + closeCount2;

        if (closeCount > openCount) {
            int lastClosingBraceIndex = rest.lastIndexOf(OPTIONAL_BRACE_END);
            actualBraceContent = Text.concatenate(actualBraceContent,
                    rest.substring(0, lastClosingBraceIndex));
            rest = rest.substring(lastClosingBraceIndex + 1);
            analyseRestAfterBraceOrWithoutBrace();
        }
        else {
            actualBraceContent = Text.concatenate(actualBraceContent, rest);
            state = EnumAnalyseState.DEFINITION_STARTED_BUT_NOT_CLOSED;
        }
    }

    private void analyseRestAfterBraceOrWithoutBrace() {
        if (rest.equals(DEFINITION_LINE_END_KOMMA)) {
            state = EnumAnalyseState.DEFINITION_CLOSED;
        }
        else if (rest.equals(DEFINITION_LINE_END_SEMIKOLON)) {
            state = EnumAnalyseState.ALL_DEFINITIONS_CLOSED;
        }
        else if (!rest.isEmpty()) {
            throw new RuntimeException("Analysefehler bei Definition ohne Klammer:\n"
                    + "\t" + "actualLine = '" + actualLine + "'\n"
                    + "\t" + "rest  = '" + rest + "'\n"
                    );
        }
        EnumDefinition enumDefinition = new EnumDefinition(actualDefiniton, actualBraceContent);
        enumDefinitons.add(enumDefinition);
    }

    private void analyseLineWithAdditionalBraceContent() {
        rest = actualLine.strip();
        analyseInBrace();
    }

    private void checkDefinitionsAreDisjunct() {
        Map<String, List<EnumDefinition>> enumDefinitionsByDefinitions = new HashMap<>();

        for (EnumDefinition enumDefinition : enumDefinitons) {
            String definition = enumDefinition.getDefinition();
            if (!enumDefinitionsByDefinitions.containsKey(definition)) {
                enumDefinitionsByDefinitions.put(definition, new ArrayList<>());
            }
            List<EnumDefinition> list = enumDefinitionsByDefinitions.get(definition);
            list.add(enumDefinition);
        }

        List<String> errorLines = new ArrayList<>();

        for (String definition : enumDefinitionsByDefinitions.keySet()) {
            List<EnumDefinition> list = enumDefinitionsByDefinitions.get(definition);
            if (list.size() != 1) {
                errorLines.add("Zur Definition '" + definition + "' wurde nicht genau ein Eintrag "
                        + "gefunden.");
                for (EnumDefinition enumDefinition : list) {
                    errorLines.add("    " + enumDefinition.getDefinition()
                            + "(" + enumDefinition.getBraceContent() + ")");
                }
            }
        }

        if (!errorLines.isEmpty()) {
            throw new RuntimeException("Analysefehler: Mehrfach gefundene Definitionen:\n"
                    + Text.joinWithLineBreak(errorLines));
        }
    }

    /** Gibt die Anzahl der gefundenen Definitionen zurück. */
    public int getNumberOfDefinitions() {
        return enumDefinitons.size();
    }

    /** Getter für die Liste der gefundenen Enum-Definitionen. */
    public List<EnumDefinition> getEnumDefinitons() {
        return enumDefinitons;
    }

    /**
     * Zählt wie oft ein bestimmter Part in den Klammern hinter Definitionen vorkommt.
     *
     * @param wantedPartInBrace
     *            Der Part, der in den Klammern hinter der Definition enthalten sein soll.
     */
    public int countDefinitionsWithPartInBrace(String wantedPartInBrace) {
        int count = 0;

        for (EnumDefinition enumDefinition : enumDefinitons) {
            String braceContent = enumDefinition.getBraceContent();
            if (braceContent.contains(wantedPartInBrace)) {
                ++count;
            }
        }

        return count;
    }


}
