package seng302.utilities;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import javafx.geometry.Point2D;
import org.junit.Test;
import seng302.model.GeoPoint;

/**
 * http://www.geoplaner.com/ For plotting geo points for visualisation
 * To test methods in GeoUtility.
 * Use this site to calculate distances
 * https://rechneronline.de/geo-coordinates/#distance
 * Created by Haoming on 28/04/17.
 */
public class GeoUtilityTest {


    //Line in x = y
    private Point2D linePoint1 = new Point2D(0, 0);
    private Point2D linePoint2 = new Point2D(1, 1);

    private Point2D arbitraryPoint1 = new Point2D(1, 0);     //Point below x = y
    private Point2D arbitraryPoint2 = new Point2D(0, 1);     //Point above x = y
    private Point2D arbitraryPoint3 = new Point2D(2, 2);     //Point on x = y

    private GeoPoint p1 = new GeoPoint(57.670333, 11.827833);
    private GeoPoint p2 = new GeoPoint(57.671524, 11.844495);
    private GeoPoint p3 = new GeoPoint(57.670822, 11.843392);
    private GeoPoint p4 = new GeoPoint(25.694829, 98.392049);
    private GeoPoint p5 = new GeoPoint(57.671829, 11.842049);

    private double toleranceRate = 0.01;


    @Test
    public void getBearing() throws Exception {
        double expected, actual;

        actual = GeoUtility.getBearing(p1, p2);
        expected = 82;
        assertEquals(expected, actual, expected * toleranceRate);

        actual = GeoUtility.getBearing(p1, p3);
        expected = 86;
        assertEquals(expected, actual, expected * toleranceRate);

        actual = GeoUtility.getBearing(p2, p4);
        expected = 78;
        assertEquals(expected, actual, expected * toleranceRate);
    }

    @Test
    public void getGeoCoordinate() throws Exception {
        GeoPoint expected, actual;

        actual = GeoUtility.getGeoCoordinate(p1, 82.0, 1000.0);
        expected = p2;
        assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
        assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);

        actual = GeoUtility.getGeoCoordinate(p1, 86.0, 927.0);
        expected = p3;
        assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
        assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);

        actual = GeoUtility.getGeoCoordinate(p2, 78.0, 7430180.0);
        expected = p4;
        assertEquals(expected.getLat(), actual.getLat(), expected.getLat() * toleranceRate);
        assertEquals(expected.getLng(), actual.getLng(), expected.getLng() * toleranceRate);
    }


    @Test
    public void testGetDistance() throws Exception {
        double expected, actual;

        actual = GeoUtility.getDistance(p1, p2);
        expected = 1000;
        assertEquals(expected, actual, expected * toleranceRate);

        actual = GeoUtility.getDistance(p1, p3);
        expected = 927;
        assertEquals(expected, actual, expected * toleranceRate);

        actual = GeoUtility.getDistance(p2, p4);
        expected = 7430180;
        assertEquals(expected, actual, expected * toleranceRate);

    }

    @Test
    public void testLineFunction() {

        Integer lineFunctionResult1 = GeoUtility
            .lineFunction(linePoint1, linePoint2, arbitraryPoint1);
        Integer lineFunctionResult2 = GeoUtility
            .lineFunction(linePoint1, linePoint2, arbitraryPoint2);
        Integer lineFunctionResult3 = GeoUtility
            .lineFunction(linePoint1, linePoint2, arbitraryPoint3);

        //Point1 and Point2 are on opposite sides
        assertEquals(Math.abs(lineFunctionResult1), Math.abs(lineFunctionResult2));
        assertNotEquals(lineFunctionResult1, lineFunctionResult2);

        //Point3 is on the line
        assertEquals((long) lineFunctionResult3, 0L);
    }

    @Test
    public void testMakeArbitraryVectorPoint() {

        //Make a point (1,0) from point (0,0)
        Point2D newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 0d, 1d);
        Point2D expected = new Point2D(1, 0);

        assertEquals(expected.getX(), newPoint.getX(), 1E-6);
        assertEquals(expected.getY(), newPoint.getY(), 1E-6);

        newPoint = GeoUtility.makeArbitraryVectorPoint(linePoint1, 90d, 1d);
        expected = new Point2D(0, 1);

        assertEquals(expected.getX(), newPoint.getX(), 1E-6);
        assertEquals(expected.getY(), newPoint.getY(), 1E-6);
    }

    @Test
    public void testIsPointInTriangle() {
        GeoPoint v1 = new GeoPoint(57.670333, 11.842833);
        GeoPoint v2 = new GeoPoint(57.671524, 11.844495);
        GeoPoint v3 = new GeoPoint(57.671829, 11.842049);
        GeoPoint p1 = new GeoPoint(57.670822, 11.843192); // inside triangle
        GeoPoint p2 = new GeoPoint(57.670892, 11.843642); // outside triangle

        // benchmark test. 100,000 calculations for 0.336 seconds
//        long startTime = System.nanoTime();
//        for (int i = 0; i < 100000; i++) {
//            assertTrue(GeoUtility.isPointInTriangle(v1, v2, v3, p1));
//        }

        // test for different orders of vertices, which should not affect the result
        assertTrue(GeoUtility.isPointInTriangle(v2, v1, v3, p1));
        assertTrue(GeoUtility.isPointInTriangle(v3, v1, v2, p1));

        assertFalse(GeoUtility.isPointInTriangle(v1, v2, v3, p2));

    }


    @Test
    public void testCheckCrossedGate() {
        GeoPoint mark1 = new GeoPoint(37.40937, -122.62233);
        GeoPoint mark2 = new GeoPoint(37.40938, -122.62154);
        GeoPoint location1 = new GeoPoint(37.40964, -122.62196);
        GeoPoint location2 = new GeoPoint(37.40910, -122.62189);
        GeoPoint location3 = new GeoPoint(37.40949, -122.62202);
        GeoPoint location4 = new GeoPoint(37.40927, -122.62152);

        // M1 -> M3 enters from CCW side
        assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location1, location2) == 2);
        // M1 -> M3 doesn't across
        assertFalse(GeoUtility.checkCrossedLine(mark1, mark2, location1, location3) > 0);
        // M2 -> M3 enters from CW side
        assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location2, location3) == 1);
        // order changes intersect direction
        assertTrue(GeoUtility.checkCrossedLine(mark2, mark1, location2, location3) == 2);
        assertTrue(GeoUtility.checkCrossedLine(mark1, mark2, location3, location2) == 2);
    }

    @Test
    public void testDirtyMiddlePoint() {
        GeoPoint result = GeoUtility.getDirtyMidPoint(p1, p2);
        assertEquals(57.6709285, result.getLat(), result.getLat() * toleranceRate);
        assertEquals(11.836164, result.getLng(), result.getLng() * toleranceRate);
    }

    @Test
    public void testKnotsToMMS() {
        Double result = GeoUtility.knotsToMMS(1.94384);
        assertEquals(1000, result, result * toleranceRate);
    }

    @Test
    public void testMMSToKnots() {
        Double result = GeoUtility.mmsToKnots(1000.0);
        assertEquals(1.94384, result, result * toleranceRate);
    }
}