package cz.fidentis.analyst.visitors.mesh;

import cz.fidentis.analyst.mesh.core.*;
import cz.fidentis.analyst.visitors.mesh.HausdorffDistance.Strategy;
import org.junit.jupiter.api.Test;

import javax.vecmath.Vector3d;
import java.util.List;
import java.util.Map;
import javax.vecmath.Point3d;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class HausdorffDistanceTest {

    protected MeshFacet getTrivialFacet(double offset, double size) {
        MeshFacet facet = new MeshFacetImpl();
        facet.addVertex(new MeshPointImpl(new Point3d(0, 0, offset), new Vector3d(0, 0, 1), new Vector3d()));
        facet.addVertex(new MeshPointImpl(new Point3d(size, 0, offset), new Vector3d(0, 0, 1), new Vector3d()));
        facet.addVertex(new MeshPointImpl(new Point3d(0, size, offset), new Vector3d(0, 0, 1), new Vector3d()));

        facet.getCornerTable().addRow(new CornerTableRow(0, -1));
        facet.getCornerTable().addRow(new CornerTableRow(1, -1));
        facet.getCornerTable().addRow(new CornerTableRow(2, -1));

        return facet;
    }
    
    protected MeshModel getTrivialModel(double offset, double size) {
        MeshModel model = new MeshModel();
        model.addFacet(getTrivialFacet(offset, size));
        return model;
    }
    
    protected void testDist(HausdorffDistance vis, MeshModel firstModel, MeshModel secondModel, double expectedDist) {
        MeshFacet secondFacet = secondModel.getFacets().get(0);
        
        secondModel.compute(vis);
        Map<MeshFacet, List<Double>> map = vis.getDistances();
        
        assertTrue(map.containsKey(secondFacet));
        List<Double> results = map.get(secondFacet);
        for (int i = 0; i < secondFacet.getNumberOfVertices(); i++) {
            assertEquals(expectedDist, results.get(i));
        }
    }

    protected void testNearestPoints(HausdorffDistance vis, MeshModel primaryModel, MeshModel measuredModel) {
        MeshFacet measuredFacet = measuredModel.getFacets().get(0);
        MeshFacet primaryFacet = primaryModel.getFacets().get(0);

        measuredModel.compute(vis);
        Map<MeshFacet, List<Point3d>> map = vis.getNearestPoints();

        if(vis.getStrategy() == Strategy.POINT_TO_POINT_DISTANCE_ONLY){
            assertTrue(map.isEmpty());
        } else {
            assertTrue(map.containsKey(measuredFacet));
            List<Point3d> results = map.get(measuredFacet);
            for (int i = 0; i < measuredFacet.getNumberOfVertices(); i++) {
                assertEquals(primaryFacet.getVertex(i).getPosition(), results.get(i));
            }
        }
    }
    
    
    @Test
    public void visitToVerticesTest() {
        MeshModel firstModel = getTrivialModel(1, 1);
        MeshModel secondModel = getTrivialModel(1.5, 1);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false, false), firstModel, secondModel, 0.5);
        testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, false, false), secondModel, firstModel, 0.5);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), firstModel, secondModel, 0.5);
        testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), secondModel, firstModel, 0.5);
    }

    @Test
    public void visitToVerticesBehindMeshTest() {
        MeshModel firstModel = getTrivialModel(1, 1);
        MeshModel secondModel = getTrivialModel(-1.5, 1);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false, false), firstModel, secondModel, 2.5);
        testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, false, false), secondModel, firstModel, 2.5);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), firstModel, secondModel, 2.5);
        testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), secondModel, firstModel, 2.5);
    }

    @Test
    public void visitToVerticesRelativeDistanceTest() {
        MeshModel firstModel = getTrivialModel(1, 1);
        MeshModel secondModel = getTrivialModel(1.5, 1);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false, false), firstModel, secondModel, -0.5);
        testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false, false), secondModel, firstModel, 0.5);
    }

    @Test
    public void visitToVerticesBehindMeshRelativeDistanceTest() {
        MeshModel firstModel = getTrivialModel(1, 1);
        MeshModel secondModel = getTrivialModel(-1.5, 1);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false, false), firstModel, secondModel, 2.5);
        testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, true, false, false), secondModel, firstModel, -2.5);
    }
    
    @Test
    public void concurrencyTest() {
        MeshModel firstModel = getTrivialModel(1, 1);
        MeshModel secondModel = getTrivialModel(1.5, 1);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, true, false), firstModel, secondModel, 0.5);
        testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_POINT, false, true, false), secondModel, firstModel, 0.5);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true, false), firstModel, secondModel, 0.5);
        testDist(new HausdorffDistance(secondModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true, false), secondModel, firstModel, 0.5);
    }
    
    @Test
    public void concurrencyThreeFacetsTest() {
        MeshModel firstModel = getTrivialModel(1, 1);
        firstModel.addFacet(getTrivialFacet(3, 1));
        firstModel.addFacet(getTrivialFacet(4, 1));
        MeshModel secondModel = getTrivialModel(1.5, 1);
        
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, true, false), firstModel, secondModel, 0.5);
        testDist(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, true, false), firstModel, secondModel, 0.5);
    }


    @Test
    public void correspondentNearestPointTest() {
        MeshModel firstModel = getTrivialModel(1, 1);
        MeshModel secondModel = getTrivialModel(1, 1);
        MeshModel thirdModel = getTrivialModel(1.5, 1);
        testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT_DISTANCE_ONLY, false, false, false), firstModel, secondModel);
        testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false, false), firstModel, secondModel);
        testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_POINT, false, false, false), firstModel, thirdModel);
        testNearestPoints(new HausdorffDistance(firstModel, Strategy.POINT_TO_TRIANGLE_APPROXIMATE, false, false, false), firstModel, secondModel);
    }

}
