package com.graphhopper.navigation;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.GraphHopper;
import com.graphhopper.config.Profile;
import com.graphhopper.reader.osm.GraphHopperOSM;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.util.Helper;
import com.graphhopper.util.Parameters;
import com.graphhopper.util.TranslationMap;
import com.graphhopper.util.shapes.GHPoint;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

import java.io.File;
import java.util.Locale;

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

public class NavigateResponseConverterTest {

    public static final String DIR = "files";
    private static final String graphFolder = "target/graphhopper-test-car";
    private static final String osmFile = DIR + "/andorra.osm.gz";
    private static GraphHopper hopper;
    private static final String vehicle = "car";
    private static final String profile = "my_car";

    private final TranslationMap trMap = new TranslationMap().doImport();
    private final TranslationMap mtrMap = new NavigateResponseConverterTranslationMap().doImport();
    private final DistanceConfig distanceConfig = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, mtrMap, Locale.ENGLISH);

    @BeforeClass
    public static void beforeClass() {
        // make sure we are using fresh files with correct vehicle
        Helper.removeDir(new File(graphFolder));

        hopper = new GraphHopperOSM().
                setOSMFile(osmFile).
                setStoreOnFlush(true).
                setGraphHopperLocation(graphFolder).
                setEncodingManager(EncodingManager.create(vehicle)).
                setProfiles(new Profile(profile).setVehicle(vehicle).setWeighting("fastest").setTurnCosts(false)).
                importOrLoad();
    }

    @AfterClass
    public static void afterClass() {
        Helper.removeDir(new File(graphFolder));
    }

    @Test
    public void basicTest() {

        GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).
                setProfile(profile));

        ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, mtrMap, Locale.ENGLISH, distanceConfig);

        JsonNode route = json.get("routes").get(0);
        double routeDistance = route.get("distance").asDouble();
        assertTrue("distance wasn't correct:" + routeDistance, routeDistance > 9000);
        assertTrue("distance wasn't correct:" + routeDistance, routeDistance < 9500);

        double routeDuration = route.get("duration").asDouble();
        assertTrue("duration wasn't correct:" + routeDuration, routeDuration > 500);
        assertTrue("duration wasn't correct:" + routeDuration, routeDuration < 600);

        assertEquals("en", route.get("voiceLocale").asText());

        JsonNode leg = route.get("legs").get(0);
        assertEquals(routeDistance, leg.get("distance").asDouble(), .000001);

        JsonNode steps = leg.get("steps");
        JsonNode step = steps.get(0);
        JsonNode maneuver = step.get("maneuver");
        // Intersection coordinates should be equal to maneuver coordinates
        assertEquals(maneuver.get("location").get(0).asDouble(), step.get("intersections").get(0).get("location").get(0).asDouble(), .00001);

        assertEquals("depart", maneuver.get("type").asText());
        assertEquals("straight", maneuver.get("modifier").asText());

        assertEquals("la Callisa", step.get("name").asText());
        double instructionDistance = step.get("distance").asDouble();
        assertTrue(instructionDistance < routeDistance);

        JsonNode voiceInstructions = step.get("voiceInstructions");
        assertEquals(1, voiceInstructions.size());
        JsonNode voiceInstruction = voiceInstructions.get(0);
        assertTrue(voiceInstruction.get("distanceAlongGeometry").asDouble() <= instructionDistance);
        assertEquals("turn sharp left onto la Callisa, then keep left", voiceInstruction.get("announcement").asText());

        JsonNode bannerInstructions = step.get("bannerInstructions");
        assertEquals(1, bannerInstructions.size());
        JsonNode bannerInstruction = bannerInstructions.get(0).get("primary");
        assertEquals("la Callisa", bannerInstruction.get("text").asText());
        assertEquals("turn", bannerInstruction.get("type").asText());
        assertEquals("sharp left", bannerInstruction.get("modifier").asText());
        JsonNode bannerInstructionComponent = bannerInstruction.get("components").get(0);
        assertEquals("la Callisa", bannerInstructionComponent.get("text").asText());

        // Get the second last step (and the last banner/voice instruction)
        step = steps.get(steps.size() - 2);

        voiceInstructions = step.get("voiceInstructions");
        assertEquals(1, voiceInstructions.size());
        voiceInstruction = voiceInstructions.get(0);
        assertTrue(voiceInstruction.get("distanceAlongGeometry").asDouble() < instructionDistance);

        bannerInstructions = step.get("bannerInstructions");
        assertEquals(1, bannerInstructions.size());
        bannerInstruction = bannerInstructions.get(0).get("primary");
        assertEquals("Arrive at destination", bannerInstruction.get("text").asText());

        JsonNode waypointsJson = json.get("waypoints");
        assertEquals(2, waypointsJson.size());
        JsonNode waypointLoc = waypointsJson.get(0).get("location");
        assertEquals(1.536198, waypointLoc.get(0).asDouble(), .001);

    }

    @Test
    public void voiceInstructionsTest() {

        GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).
                setProfile(profile));

        ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, mtrMap, Locale.ENGLISH, distanceConfig);

        JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps");

        // Step 4 is about 240m long
        JsonNode step = steps.get(4);

        JsonNode voiceInstructions = step.get("voiceInstructions");
        assertEquals(2, voiceInstructions.size());
        JsonNode voiceInstruction = voiceInstructions.get(0);
        assertEquals(200, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1);
        assertEquals("In 200 meters At roundabout, take exit 2 onto CS-340, then At roundabout, take exit 2 onto CG-3", voiceInstruction.get("announcement").asText());

        // Step 14 is over 3km long
        step = steps.get(14);

        voiceInstructions = step.get("voiceInstructions");
        assertEquals(4, voiceInstructions.size());
        voiceInstruction = voiceInstructions.get(0);
        assertEquals(2000, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1);
        assertEquals("In 2 kilometers keep right", voiceInstruction.get("announcement").asText());

        voiceInstruction = voiceInstructions.get(3);
        assertEquals("keep right", voiceInstruction.get("announcement").asText());
    }

    @Test
    public void voiceInstructionsImperialTest() {

        GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).
                setProfile(profile));

        ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, mtrMap, Locale.ENGLISH, new DistanceConfig(DistanceUtils.Unit.IMPERIAL, trMap, mtrMap, Locale.ENGLISH));

        JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps");

        // Step 4 is about 240m long
        JsonNode step = steps.get(4);
        JsonNode maneuver = step.get("maneuver");

        JsonNode voiceInstructions = step.get("voiceInstructions");
        assertEquals(2, voiceInstructions.size());
        JsonNode voiceInstruction = voiceInstructions.get(0);
        assertEquals(200, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1);
        assertEquals("In 600 feet At roundabout, take exit 2 onto CS-340, then At roundabout, take exit 2 onto CG-3", voiceInstruction.get("announcement").asText());

        // Step 14 is over 3km long
        step = steps.get(14);
        maneuver = step.get("maneuver");

        voiceInstructions = step.get("voiceInstructions");
        assertEquals(4, voiceInstructions.size());
        voiceInstruction = voiceInstructions.get(0);
        assertEquals(3220, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1);
        assertEquals("In 2 miles keep right", voiceInstruction.get("announcement").asText());

        voiceInstruction = voiceInstructions.get(3);
        assertEquals("keep right", voiceInstruction.get("announcement").asText());
    }

    @Test
    @Ignore
    public void alternativeRoutesTest() {

        GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).
                setProfile(profile).setAlgorithm(Parameters.Algorithms.ALT_ROUTE));

        assertEquals(2, rsp.getAll().size());

        ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, mtrMap, Locale.ENGLISH, distanceConfig);

        JsonNode routes = json.get("routes");
        assertEquals(2, routes.size());

        assertEquals("GraphHopper Route 0", routes.get(0).get("legs").get(0).get("summary").asText());
        assertEquals("Avinguda Sant Antoni, CG-3", routes.get(1).get("legs").get(0).get("summary").asText());
    }

    @Test
    public void voiceInstructionTranslationTest() {

        GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).
                setProfile(profile));

        ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, mtrMap, Locale.ENGLISH, distanceConfig);

        JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps");
        JsonNode voiceInstruction = steps.get(14).get("voiceInstructions").get(0);
        assertEquals("In 2 kilometers keep right", voiceInstruction.get("announcement").asText());

        rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).
                setProfile(profile).setLocale(Locale.GERMAN));

        DistanceConfig distanceConfigGerman = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, mtrMap, Locale.GERMAN);

        json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, mtrMap, Locale.GERMAN, distanceConfigGerman);

        steps = json.get("routes").get(0).get("legs").get(0).get("steps");
        voiceInstruction = steps.get(14).get("voiceInstructions").get(0);
        assertEquals("In 2 Kilometern rechts halten", voiceInstruction.get("announcement").asText());

    }

    @Test
    public void roundaboutDegreesTest() {

        GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).
                setProfile(profile));

        ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, mtrMap, Locale.ENGLISH, distanceConfig);

        JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps");

        JsonNode step = steps.get(5);
        JsonNode bannerInstructions = step.get("bannerInstructions");
        JsonNode primary = bannerInstructions.get(0).get("primary");

        assertEquals("roundabout", primary.get("type").asText());
        assertEquals("right", primary.get("modifier").asText());
        assertEquals(222, primary.get("degrees").asDouble(), 1);

    }

    @Test
    public void testMultipleWaypoints() {

        GHRequest request = new GHRequest();
        request.addPoint(new GHPoint(42.504606, 1.522438));
        request.addPoint(new GHPoint(42.504776, 1.527209));
        request.addPoint(new GHPoint(42.505144, 1.526113));
        request.addPoint(new GHPoint(42.50529, 1.527218));
        request.setProfile(profile);

        GHResponse rsp = hopper.route(request);

        ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, mtrMap, Locale.ENGLISH, distanceConfig);

        //Check that all waypoints are there and in the right order
        JsonNode waypointsJson = json.get("waypoints");
        assertEquals(4, waypointsJson.size());

        JsonNode waypointLoc = waypointsJson.get(0).get("location");
        assertEquals(1.522438, waypointLoc.get(0).asDouble(), .00001);

        waypointLoc = waypointsJson.get(1).get("location");
        assertEquals(1.527209, waypointLoc.get(0).asDouble(), .00001);

        waypointLoc = waypointsJson.get(2).get("location");
        assertEquals(1.526113, waypointLoc.get(0).asDouble(), .00001);

        waypointLoc = waypointsJson.get(3).get("location");
        assertEquals(1.527218, waypointLoc.get(0).asDouble(), .00001);

        //Check that there are 3 legs
        JsonNode route = json.get("routes").get(0);
        JsonNode legs = route.get("legs");
        assertEquals(3, legs.size());

        double duration = 0;
        double distance = 0;

        for (int i = 0; i < 3; i++) {
            JsonNode leg = legs.get(i);

            duration += leg.get("duration").asDouble();
            distance += leg.get("distance").asDouble();

            JsonNode steps = leg.get("steps");
            JsonNode step = steps.get(0);
            JsonNode maneuver = step.get("maneuver");
            assertEquals("depart", maneuver.get("type").asText());

            maneuver = steps.get(steps.size() - 1).get("maneuver");
            assertEquals("arrive", maneuver.get("type").asText());
        }

        // Check if the duration and distance of the legs sum up to the overall route distance and duration
        assertEquals(route.get("duration").asDouble(), duration, 1);
        assertEquals(route.get("distance").asDouble(), distance, 1);
    }

    @Test
    public void testError() {
        GHResponse rsp = hopper.route(new GHRequest(42.554851, 111.536198, 42.510071, 1.548128).
                setProfile(profile));

        ObjectNode json = NavigateResponseConverter.convertFromGHResponseError(rsp);

        assertEquals("InvalidInput", json.get("code").asText());
        assertTrue(json.get("message").asText().startsWith("Point 0 is out of bounds: 42.554851,111.536198"));
    }

}