package de.duehl.vocabulary.japanese.ui.dialog.kanji.stroke.partial;

import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.List;

/**
 * Diese Klasse ermöglicht es, einen Path2D.Double nur bis zu einem bestimmten Fortschritt
 * darzustellen.
 *
 * Dazu wird der ursprüngliche Pfad in kleine gerade Teilstücke zerlegt. Diese Klasse dient
 * ausschließlich zur schrittweisen Darstellung eines aktuell animierten Strichs. Für die normale
 * Darstellung sollte weiterhin der ursprüngliche Path2D.Double verwendet werden.
 *
 * Die hier zerlegten Path2D.Double wurde aus ursprünglich aus dem KanjiVG-Projekt stammenden
 * Pfaden erstellt.
 *
 *
 * 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-22
 * @author Christian Dühl
 */

public class PartialPath {

    /**
     * Die maximale Abweichung bei der Zerlegung von Bézierkurven in Geraden.
     *
     * Die KanjiVG-Pfade haben eine Größe von 109 x 109 Einheiten. Ein Wert von 0.1 liefert für
     * diesen Anwendungsfall eine sehr feine Annäherung.
     */
    private static final double FLATNESS = 0.1;

    /** Die Punkte, aus denen der Pfad besteht. */
    private final List<PartialPoint> points;

    /** Die Gesamtlänge des Pfades. */
    private final double totalLength;

    /**
     * Erzeugt einen PartialPath aus einem vorhandenen Path2D.
     *
     * @param originalPath
     *            Der ursprüngliche vollständige Pfad.
     */
    public PartialPath(Path2D.Double originalPath) {
        points = new ArrayList<>();

        PathIterator iterator = originalPath.getPathIterator(null, FLATNESS);
        double[] coordinates = new double[6];

        PartialPoint previousPoint = null;
        double length = 0;

        while (!iterator.isDone()) {
            int segmentType = iterator.currentSegment(coordinates);

            switch (segmentType) {
                case PathIterator.SEG_MOVETO:
                    previousPoint = new PartialPoint(coordinates[0], coordinates[1], true);
                    points.add(previousPoint);
                    break;

                case PathIterator.SEG_LINETO:
                    PartialPoint point = new PartialPoint(coordinates[0], coordinates[1], false);

                    if (previousPoint != null) {
                        length += distance(previousPoint, point);
                    }

                    points.add(point);
                    previousPoint = point;
                    break;

                case PathIterator.SEG_CLOSE:
                    /*
                     * KanjiVG verwendet bei den von uns betrachteten Pfaden kein SEG_CLOSE.
                     */
                    break;

                default:
                    throw new IllegalStateException("Unerwartetes Path-Segment: " + segmentType);
            }

            iterator.next();
        }

        totalLength = length;
    }

    /**
     * Erzeugt einen Pfad, der den angegebenen Anteil des ursprünglichen Pfades enthält.
     *
     * @param progress
     *            0.0 = nichts, 1.0 = kompletter Pfad
     *
     * @return der teilweise dargestellte Pfad
     */
    public Path2D.Double createPath(double progress) {
        if (progress <= 0.0) {
            return new Path2D.Double();
        }

        if (progress >= 1.0) {
            return createCompletePath();
        }

        double wantedLength = totalLength * progress;

        Path2D.Double result = new Path2D.Double();

        double currentLength = 0.0;
        PartialPoint previousPoint = null;

        for (PartialPoint point : points) {

            if (point.isMoveTo()) {
                result.moveTo(point.getX(), point.getY());
                previousPoint = point;
                continue;
            }

            if (previousPoint == null) {
                previousPoint = point;
                continue;
            }

            double segmentLength = distance(previousPoint, point);

            if (currentLength + segmentLength <= wantedLength) {
                result.lineTo(point.getX(), point.getY());

                currentLength += segmentLength;
                previousPoint = point;
            }
            else {
                double remainingLength = wantedLength - currentLength;

                double ratio = remainingLength / segmentLength;

                double x = previousPoint.getX() + ratio * (point.getX() - previousPoint.getX());

                double y = previousPoint.getY() + ratio * (point.getY() - previousPoint.getY());

                result.lineTo(x, y);
                break;
            }
        }

        return result;
    }

    /**
     * Erzeugt einen vollständigen Pfad aus den approximierten Punkten.
     *
     * Diese Methode wird bei dem momentanen Konzept gar nicht benötigt, weil in diesem Fall die
     * originalen Pfade gezeichnet werden, ich belasse sie aber der Vollständigkeit halber in
     * dieser Klasse.
     */
    private Path2D.Double createCompletePath() {
        Path2D.Double result = new Path2D.Double();

        for (PartialPoint point : points) {
            if (point.isMoveTo()) {
                result.moveTo(point.getX(), point.getY());
            }
            else {
                result.lineTo(point.getX(), point.getY());
            }
        }

        return result;
    }

    private static double distance(PartialPoint first, PartialPoint second) {
        double dx = second.getX() - first.getX();
        double dy = second.getY() - first.getY();

        return Math.sqrt(dx * dx + dy * dy);
    }

    /** Getter für die Gesamtlänge des Pfades. */
    public double getTotalLength() {
        return totalLength;
    }

}
