// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.public_transport;

import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;

public abstract class AStarAlgorithm {
    // The following abstract functions and subclasses must be overridden by a class using
    // AStarAlgorithm.

    public abstract static class Vertex implements Comparable<Vertex> {
        @Override
        public abstract int compareTo(Vertex v);
    }

    public abstract static class Edge {
        public abstract Vertex getBegin();

        public abstract Vertex getEnd();

        public abstract double getLength();
    }

    public abstract Vector<Edge> getNeighbors(Vertex vertex);

    public abstract double estimateDistance(Vertex vertex);

    // end of interface to override -------------------------------------------

    public AStarAlgorithm(Vertex begin, Vertex end) {
        this.begin = begin;
        this.end = end;
        openList = new TreeMap<>();
        closedList = new TreeSet<>();
        pathTail = new TreeMap<>();
    }

    public Vertex determineCurrentStart() {
        Vertex minVertex = null;
        double minDist = 0;
        Iterator<Map.Entry<Vertex, Double>> iter = openList.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<Vertex, Double> entry = iter.next();
            double distance = entry.getValue().doubleValue() + estimateDistance(entry.getKey());
            if (minVertex == null || distance < minDist) {
                minDist = distance;
                minVertex = entry.getKey();
            }
        }
        if (minVertex != null) {
            System.out.print(openList.get(minVertex).doubleValue());
            System.out.print("\t");
            System.out.println(minDist);
        }

        return minVertex;
    }

    Vector<Edge> shortestPath() {
        // Travel through the network
        Vertex currentStart = begin;
        openList.put(currentStart, 0.0);
        while (currentStart != null && !currentStart.equals(end)) {
            double startDistance = openList.get(currentStart).doubleValue();

            // Mark currentStart as visited.
            openList.remove(currentStart);
            closedList.add(currentStart);

            Iterator<Edge> neighbors = getNeighbors(currentStart).iterator();
            while (neighbors.hasNext()) {
                Edge edge = neighbors.next();

                // Don't walk back.
                if (closedList.contains(edge.getEnd()))
                    continue;

                // Update entry in openList
                Double knownDistance = openList.get(edge.getEnd());
                double distance = startDistance + edge.getLength();

                if (knownDistance == null || distance < knownDistance.doubleValue()) {
                    openList.put(edge.getEnd(), distance);
                    pathTail.put(edge.getEnd(), edge);
                }
            }

            currentStart = determineCurrentStart();
        }

        if (currentStart == null)
            return null;

        // Reconstruct the found path
        Vector<Edge> backwards = new Vector<>();
        Vertex currentEnd = end;
        while (!currentEnd.equals(begin)) {
            backwards.add(pathTail.get(currentEnd));
            currentEnd = pathTail.get(currentEnd).getBegin();
        }

        Vector<Edge> result = new Vector<>();
        for (int i = backwards.size() - 1; i >= 0; --i) {
            result.add(backwards.elementAt(i));
        }
        return result;
    }

    protected Vertex begin;

    protected Vertex end;

    private TreeSet<Vertex> closedList;

    private TreeMap<Vertex, Double> openList;

    private TreeMap<Vertex, Edge> pathTail;
}
