package de.duehl.basics.test;

import java.util.ArrayList;
import java.util.List;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.io.Charset;
import de.duehl.basics.io.textfile.FullNormalTextFileReader;
import de.duehl.basics.io.textfile.FullTextFileReader;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse ist die abstrakte Basis für Suchen nach bestimmten Methodenaufrufen in den Tests.
 *
 * @version 1.01     2019-05-09
 * @author Christian Dühl
 */

public abstract class FindMethodCallsInTestFiles {

    private static final List<String> ALLOWED_LINE_ENDINGS = CollectionsHelper.buildListFrom(
            ");", ",", "); // Händisch geändert");

    private static final List<String> ALLOWED_LINE_ENDINGS_STAND_ALONE =
            CollectionsHelper.buildListFrom(",");

    public static final String JAVA_END = ".java";

    /** Liste mit den Namen der einzulesenden Testklasse. */
    private final List<String> testClassNames;

    /** Liste mit den Namen der zu suchenden Methoden. */
    private final List<String> testMethods;

    /** Liste der ermittelten Unternehmensnamen. */
    private final List<String> informations;

    /**
     * Gibt an, ob die letzte bearbeitete Zeile einen öffnenden Methodennamen ohne
     * Unternehmensnamen enthielt.
     */
    private boolean lastLineContainedOpeningTestMethodWithoutQuotedString;

    /** Objekt das Testdateien im richtigen Verzeichnis finden kann. */
    private final FindJavaTestFileOnLocalHardDrive finder;

    /**
     * Konstruktor.
     *
     * @param testClassNames
     *            Liste mit den Namen der einzulesenden Testklasse.
     * @param testMethods
     *            Liste mit den Namen der zu suchenden Methoden.
     * @param finder
     *            Objekt das Testdateien im richtigen Verzeichnis finden kann.
     */
    public FindMethodCallsInTestFiles(List<String> testClassNames, List<String> testMethods,
            FindJavaTestFileOnLocalHardDrive finder) {
        this.testClassNames = testClassNames;
        this.testMethods = testMethods;
        this.finder = finder;
        informations = new ArrayList<>();
        lastLineContainedOpeningTestMethodWithoutQuotedString = false;
        readMethodCallsFromFiles();
    }

    private void readMethodCallsFromFiles() {
        for (String testFilename : testClassNames) {
            FullTextFileReader reader = createReader(testFilename);
            reader.beQuiet();
            reader.read(line -> analyseLine(line));
        }
    }

    private FullTextFileReader createReader(String testFilename) {
        String filename = finder.findJavaTestFile(testFilename + JAVA_END);
        return new FullNormalTextFileReader(filename, Charset.UTF_8);
    }

    /* Wollte man das aus dem Jar machen, ginge statt dessen:
        private FullTextFileReader createReader(String testFilename) {
            Class<?> clazz = AcronymFinderTestMethods.class;
            return new FullTextFileFromJarReader(testFilename + JAVA_END, Charset.UTF_8, clazz);
        }
     */

    private void analyseLine(String line) {
        String cleanedLine = line.trim();
        analyseCleanedLine(cleanedLine);
    }

    private void analyseCleanedLine(String line) {
        if (lastLineContainedOpeningTestMethodWithoutQuotedString) {
            extractInformationFrom(removeAllowedLineEndingsForStandAlone(line));
            lastLineContainedOpeningTestMethodWithoutQuotedString = false;
        }
        else {
            for (String testMethod : testMethods) {
                if (line.startsWith(testMethod + "(\"")) {
                    handleLineWithStartingTestMethodWithInformationOnSameLine(line, testMethod);
                }
                else if (line.equals(testMethod + "(")) {
                    lastLineContainedOpeningTestMethodWithoutQuotedString = true;
                }
            }
        }
    }

    private String removeAllowedLineEndingsForStandAlone(String line) {
        return removeAllowedLineEndings(line, ALLOWED_LINE_ENDINGS_STAND_ALONE);
    }

    private String removeAllowedLineEndings(String line) {
        return removeAllowedLineEndings(line, ALLOWED_LINE_ENDINGS);
    }

    private String removeAllowedLineEndings(String line, List<String> allowedEndings) {
        String cleanedLine = line;

        for (String ending : allowedEndings) {
            cleanedLine = Text.removeTextAtEndIfEndsWith(cleanedLine, ending);
        }

        return cleanedLine;
    }

    private void handleLineWithStartingTestMethodWithInformationOnSameLine(String line,
            String testMethod) {
        String lineStart = testMethod + "(";
        String rest = Text.removeTextAtFrontIfStartsWith(line, lineStart);
        if (rest.length() == line.length()) {
            throw new RuntimeException("Zeile begann nicht mit der erwarteten Zeichenfolge.\n"
                    + "\t" + "Bereinigte Zeile       : " + line + "\n"
                    + "\t" + "Erwarteter Zeilenanfang: " + lineStart + "\n");
        }

        int lengthBefore = rest.length();
        rest = removeAllowedLineEndings(rest);
        int lengthAfter = rest.length();
        if (lengthBefore == lengthAfter) {
            throw new RuntimeException("Zeile hat unerwartetes Ende.\n"
                    + "\t" + "Bereinigte Zeile       : " + line + "\n");
        }

        extractInformationFrom(rest);
    }

    protected abstract void extractInformationFrom(String infoString);

    /** Fügt die übergebene Information hinzu. */
    protected void addInformation(String information) {
        if (informations.contains(information)) {
            System.out.println("Der Test von\n\t" + information + "\nkommt mehrfach vor!");
        }
        else {
            informations.add(information);
        }
    }

    /** Getter für die Liste der eingelesenen Informationen. */
    protected List<String> getInformations() {
        return informations;
    }

}
