package de.duehl.vocabulary.japanese.logic.symbol.kanji.strokes.path;

import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.text.NumberString;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse parst die aus einer SVG-Datei aus dem Kanji-VG Projekt extrahierten Pfade.
 *
 * Da diese nur ein sehr eingeschränktes Set der SVG-Möglichkeiten nutzen, erstelle ich hier selbst
 * Pfade daraus.
 *
 *
 * Zu Kanji-VG siehe:
 *
 * https://github.com/KanjiVG/kanjivg
 *
 * KanjiVG is copyright Ulrich Apel and released under the Creative Commons Attribution-Share
 * Alike 3.0 licence: http://creativecommons.org/licenses/by-sa/3.0/
 *
 * The project's documentation is at https://kanjivg.tagaini.net/.
 *
 * @version 1.01     2026-08-19
 * @author Christian Dühl
 */

public class PathParser {

    /**
     * Der Pfad als String, der hier geparsed wird. (z.B.
     * "M20,19.5c3.62,1.47,8.62,5.38,10.25,8.34"), dieser wird hier verändert.
     */
    private String path;

    /**
     * Der Pfad als String, der hier geparsed wird. (z.B.
     * "M20,19.5c3.62,1.47,8.62,5.38,10.25,8.34"), unverändert für Fehlerausgaben.
     */
    private String originalPath;

    /** Der Pfad für die Darstellung in Swing, der hier erzeugt wird. */
    private Path2D.Double swingPath;

    /** Die aktuelle x-Koordinate (für relative Befehle). */
    private double currentX;

    /** Die aktuelle y-Koordinate (für relative Befehle). */
    private double currentY;

    /** Die letzte x-Koordinate (für S/s-Befehle). */
    private double lastControlX;

    /** Die letzte y-Koordinate (für S/s-Befehle). */
    private double lastControlY;

    /** Gibt an, ob das vorige Kommando ein C/c-Befehl war. */
    private boolean previousCommandWasCubicBezier;

    /**
     * Konstruktor.
     *
     * @param path
     *            Der Pfad als String, der hier geparsed wird. (z.B.
     *            "M20,19.5c3.62,1.47,8.62,5.38,10.25,8.34").
     */
    public PathParser(String path) {
        this.path = path;
        originalPath = path;
    }

    /** Führt das Parsen durch. */
    public void parse() {
        swingPath = new Path2D.Double();

        while (!path.isEmpty()) {
            parseFirstCommandOfPath();
        }
    }

    private void parseFirstCommandOfPath() {
        String command = extractFirstCommand();
        String content = extractContent();
        content = modifyContent(content);
        List<Double> parameters = parseCommandContent(content);

        switch (command) {
            case "M":
                absoluteMoveTo(parameters);
                break;
            case "m":
                relativeMoveTo(parameters);
                break;
            case "C":
                absoluteCubicBezier(parameters);
                break;
            case "c":
                relativeCubicBezier(parameters);
                break;
            case "S":
                absoluteSmoothCubicBezier(parameters);
                break;
            case "s":
                relativeSmoothCubicBezier(parameters);
                break;
            default:
                throw new RuntimeException("Unbekannter Befehl '" + command + "'.\n"
                        + "\t" + "originalPath : " + originalPath + "\n"
                        );
        }
    }

    private static final List<String> KNOWN_COMMANDS = CollectionsHelper.buildListFrom(
            "M", "m", "C", "c", "S", "s");

    private String extractFirstCommand() {
        String command = path.substring(0, 1);
        path = path.substring(1);

        if (!KNOWN_COMMANDS.contains(command)) {
            throw new RuntimeException("Unbekannter Befehl '" + command + "'.\n"
                    + "\t" + "originalPath : " + originalPath + "\n"
                    );
        }

        return command;
    }

    private String extractContent() {
        for (int index = 0; index < path.length(); ++index) {
            String character = path.substring(index, index + 1);
            if (KNOWN_COMMANDS.contains(character)) {
                String content = path.substring(0, index);
                path = path.substring(index);
                return content;
            }
        }

        String content = path;
        path = "";

        return content;
    }


    private static final Pattern DIGIT_MINUS_DIGIT_PATTERN = Pattern.compile(""
            + "([0-9])(-[0-9])");

    /**
     * Modifiziert den übergebenen Inhalt.
     *
     * Achtung, es gibt in den SVG-Koordinaten auch sowas:
     *     M10-20
     * gemeint sind zwei Zahlen:
     *  10 und -20
     */
    private String modifyContent(String content) {
        Matcher matcher = DIGIT_MINUS_DIGIT_PATTERN.matcher(content);
        return matcher.replaceAll("$1,$2");
    }

    private List<Double> parseCommandContent(String content) {
        List<String> numbers = Text.splitByKomma(content);

        List<Double> parameters = new ArrayList<>();
        for (String number : numbers) {
            if (NumberString.isDigitPerhapsNegativeWithPointSequence(number)) {
                double parameter = NumberString.parseDouble(number,
                        "Lies sich nicht ale Double parsen: " + number);
                parameters.add(parameter);
            }
        }

        return parameters;
    }

    private void absoluteMoveTo(List<Double> parameters) {
        parameterCheck("M", parameters, 2);

        currentX = parameters.get(0);
        currentY = parameters.get(1);

        swingPath.moveTo(currentX, currentY);

        previousCommandWasCubicBezier = false;
    }

    private void relativeMoveTo(List<Double> parameters) {
        parameterCheck("m", parameters, 2);

        currentX += parameters.get(0);
        currentY += parameters.get(1);

        swingPath.moveTo(currentX, currentY);

        previousCommandWasCubicBezier = false;
    }

    private void absoluteCubicBezier(List<Double> parameters) {
        parameterCheck("C", parameters, 6);

        double x1 = parameters.get(0);
        double y1 = parameters.get(1);
        double x2 = parameters.get(2);
        double y2 = parameters.get(3);
        double x3 = parameters.get(4);
        double y3 = parameters.get(5);

        currentX = x3;
        currentY = y3;

        swingPath.curveTo(x1, y1, x2, y2, x3, y3);

        lastControlX = x2;
        lastControlY = y2;

        previousCommandWasCubicBezier = true;
    }

    private void relativeCubicBezier(List<Double> parameters) {
        parameterCheck("c", parameters, 6);

        double x1 = currentX + parameters.get(0);
        double y1 = currentY + parameters.get(1);
        double x2 = currentX + parameters.get(2);
        double y2 = currentY + parameters.get(3);
        double x3 = currentX + parameters.get(4);
        double y3 = currentY + parameters.get(5);

        currentX = x3;
        currentY = y3;

        swingPath.curveTo(x1, y1, x2, y2, x3, y3);

        lastControlX = x2;
        lastControlY = y2;

        previousCommandWasCubicBezier = true;
    }

    private void absoluteSmoothCubicBezier(List<Double> parameters) {
        parameterCheck("S", parameters, 4);

        double x1;
        double y1;

        if (previousCommandWasCubicBezier) {
            x1 = 2 * currentX - lastControlX;
            y1 = 2 * currentY - lastControlY;
        }
        else {
            x1 = currentX;
            y1 = currentY;
        }

        double x2 = parameters.get(0);
        double y2 = parameters.get(1);

        double x3 = parameters.get(2);
        double y3 = parameters.get(3);

        lastControlX = x2;
        lastControlY = y2;

        currentX = x3;
        currentY = y3;

        swingPath.curveTo(x1, y1, x2, y2, x3, y3);

        previousCommandWasCubicBezier = true;
    }

    private void relativeSmoothCubicBezier(List<Double> parameters) {
        parameterCheck("s", parameters, 4);

        double x1 = 2 * currentX - lastControlX;
        double y1 = 2 * currentY - lastControlY;

        double x2 = currentX + parameters.get(0);
        double y2 = currentY + parameters.get(1);

        double x3 = currentX + parameters.get(2);
        double y3 = currentY + parameters.get(3);

        lastControlX = x2;
        lastControlY = y2;

        currentX = x3;
        currentY = y3;

        swingPath.curveTo(x1, y1, x2, y2, x3, y3);

        previousCommandWasCubicBezier = true;
    }

    private void parameterCheck(String command, List<Double> parameters, int numberOfParameters) {
        if (parameters.size() != numberOfParameters) {
            throw new RuntimeException("Zum Befehl '" + command + "' wurden nicht genau "
                    + numberOfParameters + " Parameter " + "übergeben:\n"
                    + "\t" + "parameters : " + parameters + "\n"
                    + "\t" + "originalPath : " + originalPath + "\n"
                    );
        }
    }

    /** Getter für den Pfad für die Darstellung in Swing, der hier erzeugt wird. */
    public Path2D.Double getSwingPath() {
        return swingPath;
    }

}
