package cz.fidentis.analyst.visitors.mesh;

import cz.fidentis.analyst.Logger;
import cz.fidentis.analyst.mesh.MeshVisitor;
import cz.fidentis.analyst.mesh.core.MeshFacet;
import cz.fidentis.analyst.mesh.core.MeshTriangle;
import cz.fidentis.analyst.symmetry.Plane;

import javax.vecmath.Point3d;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * A visitor that calculates the cross-section of a face and a cutting plane using the edges between triangles.
 * <p>
 * This visitor is thread-safe.
 * </p>
 *
 * @author Dominik Racek
 */
public class CrossSectionZigZag extends MeshVisitor {
    private List<Point3d> points;
    private Set<Point3d> usedPoints;
    private Set<MeshTriangle> visited;

    private Plane plane;

    /**
     * Constructor for CrossSectionZigZag visitor
     *
     * @param plane cutting plane
     */
    public CrossSectionZigZag(Plane plane) {
        this.plane = plane;
        this.points = new ArrayList<>();
        this.usedPoints = new HashSet<>();
    }

    private void addPoint(Point3d p, boolean direction) {
        if (!usedPoints.contains(p)) {
            usedPoints.add(p);

            if (direction) {
                points.add(p);
            } else {
                points.add(0, p);
            }
        }
    }

    private void visitMeshFacetRec(MeshFacet facet, int p1, int p2, MeshTriangle previous, boolean direction) {
        List<MeshTriangle> triangles = facet.getAdjacentTriangles(p1, p2);
        triangles.remove(previous);
        if (triangles.isEmpty()) {
            return;
        }

        //Find the next intersected edge
        MeshTriangle current = triangles.get(0);

        if (visited.contains(current)) {
            return;
        }
        visited.add(current);

        //Find the index of the last vertex of current (first ones being p1 and p2)
        int p3;
        if (current.index1 != p1 && current.index1 != p2) {
            p3 = current.index1;
        } else if (current.index2 != p1 && current.index2 != p2) {
            p3 = current.index2;
        } else {
            p3 = current.index3;
        }

        //Next intersected edge will be between p1-p3 or p2-p3
        Point3d intersection = plane.getIntersectionWithLine(facet.getVertex(p1).getPosition(),
                facet.getVertex(p3).getPosition());

        if (intersection == null) {
            intersection = plane.getIntersectionWithLine(facet.getVertex(p2).getPosition(),
                    facet.getVertex(p3).getPosition());

            addPoint(intersection, direction);
            visitMeshFacetRec(facet, p2, p3, current, direction);
        } else {
            addPoint(intersection, direction);
            visitMeshFacetRec(facet, p1, p3, current, direction);
        }

    }

    @Override
    public void visitMeshFacet(MeshFacet facet) {
        Logger out = Logger.measureTime();

        visited = new HashSet<>();

        synchronized (this) {

            // Find the first triangle
            MeshTriangle first = null;
            for (MeshTriangle tri : facet) {
                if (tri.checkIntersectionWithPlane(plane.getNormal(), plane.getDistance())) {
                    first = tri;
                    break;
                }
            }

            if (first == null) {
                return;
            }

            visited.add(first);

            //Figure out which lines are intersected
            Point3d intersection1 = plane.getIntersectionWithLine(first.getVertex1(), first.getVertex2());
            Point3d intersection2 = plane.getIntersectionWithLine(first.getVertex2(), first.getVertex3());
            Point3d intersection3 = plane.getIntersectionWithLine(first.getVertex3(), first.getVertex1());

            if (intersection1 != null) {
                //Intersection 1
                addPoint(intersection1, true);
                visitMeshFacetRec(facet, first.index1, first.index2, first, true);
                if (intersection2 != null) {
                    //Intersection 1 and 2
                    addPoint(intersection2, false);
                    visitMeshFacetRec(facet, first.index2, first.index3, first, false);
                } else if (intersection3 != null) {
                    //Intersection 1 and 3
                    addPoint(intersection3, false);
                    visitMeshFacetRec(facet, first.index3, first.index1, first, false);
                }
            } else if (intersection2 != null) {
                //Intersection 2
                addPoint(intersection2, true);
                visitMeshFacetRec(facet, first.index2, first.index3, first, true);
                if (intersection3 != null) {
                    //Intersection 2 and 3
                    addPoint(intersection3, false);
                    visitMeshFacetRec(facet, first.index3, first.index1, first, false);
                }

            } else if (intersection3 != null) {
                //Intersection 3 only
                addPoint(intersection3, true);
                visitMeshFacetRec(facet, first.index3, first.index1, first, true);
            }
            //No intersection
        }

        out.printDuration("Cross section with zigzag method");
    }

    public List<Point3d> getPoints() {
        return points;
    }
}
