/*
 *  Licensed to GraphHopper GmbH under one or more contributor
 *  license agreements. See the NOTICE file distributed with this work for
 *  additional information regarding copyright ownership.
 *
 *  GraphHopper GmbH licenses this file to you under the Apache License,
 *  Version 2.0 (the "License"); you may not use this file except in
 *  compliance with the License. You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.graphhopper.matching;

import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.graphhopper.GraphHopper;
import com.graphhopper.config.LMProfile;
import com.graphhopper.config.Profile;
import com.graphhopper.matching.gpx.Gpx;
import com.graphhopper.reader.osm.GraphHopperOSM;
import com.graphhopper.routing.util.CarFlagEncoder;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.storage.index.Snap;
import com.graphhopper.util.Helper;
import com.graphhopper.util.PMap;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

import static com.graphhopper.matching.MapMatchingTest.fetchStreets;
import static org.junit.Assert.*;

/**
 * @author Peter Karich
 */
public class MapMatching2Test {
    private static final String GH_LOCATION = "../target/mapmatchingtest2-gh";
    private XmlMapper xmlMapper = new XmlMapper();

    @Before
    @After
    public void clean() {
        Helper.removeDir(new File(GH_LOCATION));
    }

    @Test
    public void testIssue13() throws IOException {
        CarFlagEncoder encoder = new CarFlagEncoder();
        GraphHopper hopper = new GraphHopperOSM();
        hopper.setDataReaderFile("../map-data/map-issue13.osm.gz");
        hopper.setGraphHopperLocation(GH_LOCATION);
        hopper.setEncodingManager(EncodingManager.create(encoder));
        hopper.setProfiles(new Profile("my_profile").setVehicle("car").setWeighting("fastest"));
        hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("my_profile"));
        hopper.getRouterConfig().setLMDisablingAllowed(true);
        hopper.importOrLoad();

        MapMatching mapMatching = new MapMatching(hopper, new PMap().putObject("profile", "my_profile"));

        Gpx gpx = xmlMapper.readValue(getClass().getResourceAsStream("/issue-13.gpx"), Gpx.class);
        MatchResult mr = mapMatching.match(gpx.trk.get(0).getEntries());

        // make sure no virtual edges are returned
        int edgeCount = hopper.getGraphHopperStorage().getAllEdges().length();
        for (EdgeMatch em : mr.getEdgeMatches()) {
            assertTrue("result contains virtual edges:" + em.getEdgeState().toString(),
                    em.getEdgeState().getEdge() < edgeCount);
            validateEdgeMatch(em);
        }

        // create street names
        assertEquals(Arrays.asList("", "", "", "", "", ""),
                fetchStreets(mr.getEdgeMatches()));
        assertEquals(mr.getGpxEntriesLength(), mr.getMatchLength(), 2.5);
        assertEquals(28790, mr.getMatchMillis(), 50);
    }

    @Test
    public void testIssue70() throws IOException {
        CarFlagEncoder encoder = new CarFlagEncoder();
        GraphHopper hopper = new GraphHopperOSM();
        hopper.setDataReaderFile("../map-data/issue-70.osm.gz");
        hopper.setGraphHopperLocation(GH_LOCATION);
        hopper.setEncodingManager(EncodingManager.create(encoder));
        hopper.setProfiles(new Profile("my_profile").setVehicle("car").setWeighting("fastest"));
        hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("my_profile"));
        hopper.getRouterConfig().setLMDisablingAllowed(true);
        hopper.importOrLoad();

        MapMatching mapMatching = new MapMatching(hopper, new PMap().putObject("profile", "my_profile"));

        Gpx gpx = xmlMapper.readValue(getClass().getResourceAsStream("/issue-70.gpx"), Gpx.class);
        MatchResult mr = mapMatching.match(gpx.trk.get(0).getEntries());

        assertEquals(Arrays.asList("Милана Видака", "Милана Видака", "Милана Видака",
                "Бранка Радичевића", "Бранка Радичевића", "Здравка Челара"),
                fetchStreets(mr.getEdgeMatches()));
        // TODO: length/time

        for (EdgeMatch edgeMatch : mr.getEdgeMatches()) {
            validateEdgeMatch(edgeMatch);
        }
    }

    @Test
    public void testIssue127() throws IOException {
        CarFlagEncoder encoder = new CarFlagEncoder();
        GraphHopper hopper = new GraphHopperOSM();
        hopper.setDataReaderFile("../map-data/map-issue13.osm.gz");
        hopper.setGraphHopperLocation(GH_LOCATION);
        hopper.setEncodingManager(EncodingManager.create(encoder));
        hopper.setProfiles(new Profile("my_profile").setVehicle("car").setWeighting("fastest"));
        hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("my_profile"));
        hopper.getRouterConfig().setLMDisablingAllowed(true);
        hopper.importOrLoad();

        MapMatching mapMatching = new MapMatching(hopper, new PMap().putObject("profile", "my_profile"));

        // query with two identical points
        Gpx gpx = xmlMapper.readValue(getClass().getResourceAsStream("/issue-127.gpx"), Gpx.class);
        MatchResult mr = mapMatching.match(gpx.trk.get(0).getEntries());

        // make sure no virtual edges are returned
        int edgeCount = hopper.getGraphHopperStorage().getAllEdges().length();
        for (EdgeMatch em : mr.getEdgeMatches()) {
            assertTrue("result contains virtual edges:" + em.getEdgeState().toString(),
                    em.getEdgeState().getEdge() < edgeCount);
            validateEdgeMatch(em);
        }

        assertEquals(0, mr.getMatchMillis(), 50);
    }

    private void validateEdgeMatch(EdgeMatch edgeMatch) {
        for (State state : edgeMatch.getStates()) {
            if (state.getSnap().getSnappedPosition() == Snap.Position.TOWER) {
                if (state.getSnap().getClosestNode() != edgeMatch.getEdgeState().getAdjNode()
                        && state.getSnap().getClosestNode() != edgeMatch.getEdgeState().getAdjNode()) {
                    fail();
                }
            } else {
                if (state.getSnap().getClosestEdge().getEdge() != edgeMatch.getEdgeState().getEdge()) {
                    fail();
                }
            }
        }
    }

}
