package de.duehl.basics.version;

/*
 * Copyright 2021 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.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.text.Text;
import de.duehl.basics.text.html.HtmlTool;

/**
 * Diese Klasse schneidet aus einem Text aus einer changes.html die passenden Neuerungen zwischen
 * zwei Versionen aus.
 *
 * @version 1.03     2021-08-13
 * @author Christian Dühl
 */

public class ChangesBetweenVersionsCutter {

    private static final Version NO_VERSION_FOUND = new Version(
            "0.0.0.0", "NO VERSION FOUND"); // Umbruch gegen uneindeutige Version im Projekt!
    private static final String VERSION_STRING = "<h2>Ver" + "sion ";
    private static final String FROM_STRING = " vom ";
    private static final String H2_END_STRING = "</h2>";

    /** Aktuelle Version. */
    private final Version actualVersion;

    /** Version beim letzten Programmstart. */
    private final Version lastVersion;

    /** HTML-Text mit der Beschreibung der Änderungen aus einer changes.html-Datei. */
    private final List<String> changesLines;

    /**
     * Verzeichnis der in dem Änderungs-HTML beschriebene Versionen zum Index der Zeile ihrer
     * Überschrift.
     */
    private final Map<Version, Integer> versionsAtLines;

    /** Sortierte Liste der Versionen in versionsAtLines. */
    private final List<Version> orderedKeyset;

    /**
     * Konstruktor.
     *
     * @param actualVersion
     *            Aktuelle Version.
     * @param lastVersion
     *            Version beim letzten Programmstart.
     * @param changesHtml
     *            HTML-Text mit der Beschreibung der Änderungen aus einer changes.html-Datei.
     */
    public ChangesBetweenVersionsCutter(Version actualVersion, Version lastVersion,
            String changesHtml) {
        this.actualVersion = actualVersion;
        this.lastVersion = lastVersion;

        changesLines = Text.splitByLineBreaks(changesHtml);
        versionsAtLines = new HashMap<>();
        orderedKeyset = new ArrayList<>();
    }

    public String detectChangesBetweenVersions() {
        emptyCollections();
        detectVersionsAndLines();
        createOrderedKeysetForVersionsAtLines();
        List<Version> versions = determineNewestAndOldestVersionsBetweenVersions();
        String changes = cutHtmlForVersions(versions);
        return cutComments(changes);
    }

    private void emptyCollections() {
        versionsAtLines.clear();
        orderedKeyset.clear();
    }

    private void detectVersionsAndLines() {
        for (int index = 0; index < changesLines.size(); ++index) {
            String line = changesLines.get(index);
            if (lineIsVersionHeader(line)) {
                Version versionOfLine = detectVersionFromVersionHeader(line);
                versionsAtLines.put(versionOfLine, index);
            }
        }
    }

    private boolean lineIsVersionHeader(String line) {
        return line.trim().startsWith(VERSION_STRING);
    }

    private Version detectVersionFromVersionHeader(String line) {
        int versionIndex = line.indexOf(VERSION_STRING);
        int fromIndex = line.indexOf(FROM_STRING);
        int endIndex = line.indexOf(H2_END_STRING);
        if (versionIndex == -1
                || fromIndex == -1
                || endIndex == -1
                || fromIndex <= versionIndex
                || endIndex <= fromIndex) {
            return NO_VERSION_FOUND;
        }
        else {
            String version = line.substring(versionIndex + VERSION_STRING.length(), fromIndex);
            String date = line.substring(fromIndex + FROM_STRING.length(), endIndex);
            return new Version(version, date);
        }
    }

    private void createOrderedKeysetForVersionsAtLines() {
        orderedKeyset.addAll(versionsAtLines.keySet());
        Collections.sort(orderedKeyset, new Comparator<Version>() {
            @Override
            public int compare(Version v1, Version v2) {
                int lineIndex1 = versionsAtLines.get(v1);
                int lineIndex2 = versionsAtLines.get(v2);
                return lineIndex1 - lineIndex2;
            }
        });
    }

    private List<Version> determineNewestAndOldestVersionsBetweenVersions() {
        Version newestVersion = NO_VERSION_FOUND;
        Version oldestVersion = NO_VERSION_FOUND;

        for (Version version : orderedKeyset) {
            if (version.isNewerThan(lastVersion) && !version.isNewerThan(actualVersion)) {
                if (newestVersion == NO_VERSION_FOUND || version.isNewerThan(newestVersion)) {
                    newestVersion = version;
                }
                if (oldestVersion == NO_VERSION_FOUND || oldestVersion.isNewerThan(version)) {
                    oldestVersion = version;
                }
            }
        }

        if (newestVersion == NO_VERSION_FOUND || oldestVersion == NO_VERSION_FOUND) {
            return new ArrayList<>();
        }
        else {
            return CollectionsHelper.buildListFrom(oldestVersion, newestVersion);
        }
    }

    private String cutHtmlForVersions(List<Version> versions) {
        if (versions.isEmpty()) {
            return "";
        }
        else {
            Version oldestVersion = versions.get(0);
            Version newestVersion = versions.get(1);
            return cutHtmlForVersions(oldestVersion, newestVersion);
        }
    }

    private String cutHtmlForVersions(Version oldestVersion, Version newestVersion) {
        int fromIndex = versionsAtLines.get(newestVersion);
        int toIndex = versionsAtLines.get(oldestVersion);

        /*
         * Für die Erklärung unter dem ältesten muss man das Ende (alte Version) auch noch
         * mitnehmen!
         *
         * Falls es der älteste ist, nehmen wir alles bis zum Ende des HTML-Bodies.
         */
        int indexOfOldestVersionInOrderedKeyset = orderedKeyset.indexOf(oldestVersion);
        if (orderedKeyset.size() > indexOfOldestVersionInOrderedKeyset + 1) {
            Version olderVersion = orderedKeyset.get(indexOfOldestVersionInOrderedKeyset + 1);
            int indexOfOlderVersion = versionsAtLines.get(olderVersion);
            toIndex = indexOfOlderVersion - 1;
        }
        else {
            String bodyEndString = "</body>";
            int bodyEndIndex = Text.searchFirstLineIndexContainig(changesLines, bodyEndString);
            if (bodyEndIndex == -1) {
                toIndex = changesLines.size() - 1;
            }
            else {
                toIndex = bodyEndIndex - 1;
            }
        }

        List<String> cuttedLines = changesLines.subList(fromIndex, toIndex + 1);
        return Text.joinWithLineBreak(cuttedLines) + Text.LINE_BREAK;
    }

    private String cutComments(String changes) {
        String cleanedChanges = changes;
        cleanedChanges = HtmlTool.removeComments(cleanedChanges);

        /*
         * Eventuell könnte man hier noch defekte anfängliche und endende Kommentare ausschneiden,
         * für den Fall, dass man innerhalb eines Kommentars geschnitten hat:
         *
         * HtmlTool.removeDefectBeginningComment
         * HtmlTool.removeDefectEndingComment
         */

        return cleanedChanges;
    }

}
