package cz.fidentis.analyst.visitors.kdtree;

import cz.fidentis.analyst.kdtree.KdNode;
import cz.fidentis.analyst.kdtree.KdTree;
import cz.fidentis.analyst.mesh.core.MeshPoint;
import cz.fidentis.analyst.mesh.core.MeshPointImpl;
import java.util.HashSet;
import java.util.Set;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;

/**
 *
 * @author Daniel Schramm
 */
public class ClosestNodeMultipleVisitorTest {
    
    private final Point3d centerNode = new Point3d(0, 0, 0);
    
    private Set<MeshPoint> getTreeNodesSymmetric(double size) {
        final Set<MeshPoint> treeNodes = new HashSet<>();
        treeNodes.add(new MeshPointImpl(new Point3d( size,  size,  size), new Vector3d(0, 0, 0), new Vector3d(0, 0, 0)));
        treeNodes.add(new MeshPointImpl(new Point3d(-size,  size,  size), new Vector3d(0, 0, 0), new Vector3d(0, 0, 0)));
        treeNodes.add(new MeshPointImpl(new Point3d( size, -size,  size), new Vector3d(0, 0, 0), new Vector3d(0, 0, 0)));
        treeNodes.add(new MeshPointImpl(new Point3d(-size, -size,  size), new Vector3d(0, 0, 0), new Vector3d(0, 0, 0)));
        treeNodes.add(new MeshPointImpl(new Point3d( size,  size, -size), new Vector3d(0, 0, 0), new Vector3d(0, 0, 0)));
        treeNodes.add(new MeshPointImpl(new Point3d(-size,  size, -size), new Vector3d(0, 0, 0), new Vector3d(0, 0, 0)));
        treeNodes.add(new MeshPointImpl(new Point3d( size, -size, -size), new Vector3d(0, 0, 0), new Vector3d(0, 0, 0)));
        treeNodes.add(new MeshPointImpl(new Point3d(-size, -size, -size), new Vector3d(0, 0, 0), new Vector3d(0, 0, 0)));
        
        return treeNodes;
    }
    
    private void testContainsExactlyPositions(Set<MeshPoint> expected, Set<KdNode> actual) {
        final Set<KdNode> actualCopy = new HashSet<>(actual);
        for (final MeshPoint point: expected) {
            final Point3d position = point.getPosition();
            boolean found = false;
            for (final KdNode node: actual) {
                if (position.equals(node.getLocation())) {
                    found = true;
                    actualCopy.remove(node);
                }
            }
            assertTrue(found, position.toString());
        }
        assertTrue(actualCopy.isEmpty());
    }
    
    @Test
    public void allNodesClosestTest() {
        final Set<MeshPoint> closest = getTreeNodesSymmetric(1);
        final KdTree kdTree = new KdTree(closest);
        
        final KdTreeClosestNode visitor = new KdTreeClosestNode(centerNode);
        kdTree.accept(visitor);
        
        final Set<KdNode> closestFound = visitor.getClosestNodes();
        assertEquals(8, closestFound.size());
        testContainsExactlyPositions(closest, closestFound);
    }
    
    @Test
    public void singleClosestNodeTest() {
        final MeshPoint closest = new MeshPointImpl(new Point3d(1, 1, 0), new Vector3d(0, 0, 1), new Vector3d());
        final Set<MeshPoint> treeNodes = getTreeNodesSymmetric(1);
        treeNodes.add(closest);
        final KdTree kdTree = new KdTree(treeNodes);
        
        final KdTreeClosestNode visitor = new KdTreeClosestNode(centerNode);
        kdTree.accept(visitor);
        
        final Set<KdNode> closestFound = visitor.getClosestNodes();
        assertEquals(1, closestFound.size());
        assertEquals(closest.getPosition(), ((KdNode) closestFound.toArray()[0]).getLocation());
    }
    
    @Test
    public void closestInBothSubtreesTest() {
        final Set<MeshPoint> closest = getTreeNodesSymmetric(1);
        final Set<MeshPoint> treeNodes = new HashSet<>(closest);
        treeNodes.addAll(getTreeNodesSymmetric(2));
        assertEquals(16, treeNodes.size());
        final KdTree kdTree = new KdTree(treeNodes);
        
        final KdTreeClosestNode visitor = new KdTreeClosestNode(centerNode);
        kdTree.accept(visitor);
        
        final Set<KdNode> closestFound = visitor.getClosestNodes();
        assertEquals(8, closestFound.size());
        testContainsExactlyPositions(closest, closestFound);
    }
    
    @Test
    public void manyPointsEightClosestTest() {
        final Set<MeshPoint> closest = getTreeNodesSymmetric(1);
        final Set<MeshPoint> treeNodes = new HashSet<>(closest);
        for (int i = 2; i < 100; i++) {
            treeNodes.addAll(getTreeNodesSymmetric(i));
        }
        assertEquals(8 * 99, treeNodes.size());
        final KdTree kdTree = new KdTree(treeNodes);
        
        final KdTreeClosestNode visitor = new KdTreeClosestNode(centerNode);
        kdTree.accept(visitor);
        
        final Set<KdNode> closestFound = visitor.getClosestNodes();
        assertEquals(8, closestFound.size());
        testContainsExactlyPositions(closest, closestFound);
    }
    
    @Test
    public void threeTreesSingleClosestTest() {
        final Set<MeshPoint> closest = getTreeNodesSymmetric(1);
        final KdTree kdTreeClosest = new KdTree(closest);
        
        final KdTree kdTree1 = new KdTree(getTreeNodesSymmetric(2));
        final KdTree kdTree2 = new KdTree(getTreeNodesSymmetric(3));
        
        final KdTreeClosestNode visitor = new KdTreeClosestNode(centerNode);
        kdTree1.accept(visitor);
        kdTreeClosest.accept(visitor);
        kdTree2.accept(visitor);
        
        final Set<KdNode> closestFound = visitor.getClosestNodes();
        assertEquals(8, closestFound.size());
        testContainsExactlyPositions(closest, closestFound);
    }
    
    @Test
    public void threeTreesTwoClosestTest() {
        final Set<MeshPoint> closest = getTreeNodesSymmetric(1);
        final Set<MeshPoint> closest1 = new HashSet<>();
        final Set<MeshPoint> closest2 = new HashSet<>();
        boolean odd = true;
        for (final MeshPoint point: closest) {
            if (odd) {
                closest1.add(point);
            } else {
                closest2.add(point);
            }
            odd = !odd;
        }
        assertEquals(8, closest.size());
        assertEquals(4, closest1.size());
        assertEquals(4, closest2.size());
        
        final KdTree kdTreeClosest1 = new KdTree(closest1);
        final KdTree kdTreeClosest2 = new KdTree(closest2);
        final KdTree kdTree = new KdTree(getTreeNodesSymmetric(2));
        
        final KdTreeClosestNode visitor = new KdTreeClosestNode(centerNode);
        kdTreeClosest1.accept(visitor);
        kdTree.accept(visitor);
        kdTreeClosest2.accept(visitor);
        
        final Set<KdNode> closestFound = visitor.getClosestNodes();
        assertEquals(8, closestFound.size());
        testContainsExactlyPositions(closest, closestFound);
    }
    
}
