package cz.fidentis.analyst.visitors.mesh;

import cz.fidentis.analyst.mesh.MeshVisitor;
import cz.fidentis.analyst.mesh.core.MeshFacet;
import cz.fidentis.analyst.mesh.core.MeshTriangle;
import cz.fidentis.analyst.visitors.DistanceWithNearestPoints;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

/**
 * This visitor finds the minimal distance between the given 3D point and mesh facets.
 * All closest mesh facets and all the closests points from their surface are returned.
 * <p>
 * This visitor is thread-safe.
 * </p>
 *
 * @author Matej Lukes
 * @author Radek Oslejsek
 */
public class MeshDistanceToTriangles extends MeshVisitor implements DistanceWithNearestPoints {
    
    private double distance = Double.POSITIVE_INFINITY;
    private final Point3d point3d;
    private Map<MeshFacet, List<Point3d>> nearestPoints = new HashMap<>();
    
    /**
     * @param point A 3D point from which distance is computed. Must not be {@code null}
     * @throws IllegalArgumentException if some parameter is wrong
     */
    public MeshDistanceToTriangles(Point3d point) {
        if (point == null) {
            throw new IllegalArgumentException("point");
        }
        this.point3d = point;
    }

    @Override
    public Map<MeshFacet, List<Point3d>> getNearestPoints() {
        return Collections.unmodifiableMap(nearestPoints);
    }

    @Override
    public double getDistance() {
        return distance;
    }

    @Override
    public void visitMeshFacet(MeshFacet facet) {        
        for (MeshTriangle tri: facet) {
            Point3d projection = tri.getClosestPoint(point3d);
            Vector3d aux = new Vector3d(projection);
            aux.sub(point3d);
            final double dist = aux.length();
            
            synchronized (this) {
                if (dist > distance) {
                    continue;
                }
                if (dist < distance) {
                    distance = dist;
                    nearestPoints.clear();                    
                }
                nearestPoints.computeIfAbsent(facet, meshFacet -> new ArrayList<>())
                        .add(projection);
            }
        }
    }
}
