package ch.epfl.isochrone.timetable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Classe servant à représenter l'arbre des trajets les plus rapides
 * 
 * @author Charles Thiebaut (238935)
 * @author Arnaud Delamare (234841)
 * 
 */
public final class FastestPathTree {
    private final Stop startingStop;
    private final Map<Stop, Integer> arrivalTime;
    private final Map<Stop, Stop> predecessor;

    /**
     * construit un arbre des trajets les plus rapides avec l'arrêt de départ,
     * la table des heures d'arrivée et la table des prédécesseurs donnés.
     * 
     * @throw IllegalArgumentException si l'ensemble des clefs de la table des
     *        heures n'est pas égal à celui des clefs de la tables des
     *        prédécesseurs plus l'arrêt de départ.
     * @param startingStop
     *            L'arrêt de déapart
     * @param arrivalTime
     *            la tabée des heures d'arrivée (associant à un certain nombre
     *            d'arrêt l'heure de la première arrivée (en seconde après
     *            minuit)).
     * @param predecessor
     *            la table des prédécesseurs (associant chaque arrêt à son
     *            prédécesseur dans l'arbre)
     */
    public FastestPathTree(Stop startingStop, Map<Stop, Integer> arrivalTime,
            Map<Stop, Stop> predecessor) {
        Set<Stop> set1 = Collections.unmodifiableSet(arrivalTime.keySet());
        
        Set<Stop> set2 = new HashSet<Stop>(predecessor.keySet());
        set2.add(startingStop);
        
        set2 = Collections.unmodifiableSet(set2);

        if (!set1.equals(set2)) {
            throw new IllegalArgumentException();
        }

        this.startingStop = startingStop;
        this.arrivalTime = new HashMap<Stop, Integer>(arrivalTime);
        this.predecessor = new HashMap<Stop, Stop>(predecessor);
    }

    /**
     * @return l'arrêt de départ
     */
    public Stop startingStop() {
        return startingStop;
    }

    /**
     * @return l'heure de départ
     */
    public int startingTime() {
        return arrivalTime.get(startingStop);
    }

    /**
     * @return l'ensemble des arrêts pour lesquels une heure de première arrivée
     *         existe.
     */
    public Set<Stop> stops() {
        return Collections.unmodifiableSet(arrivalTime.keySet());
    }

    /**
     * @return l'heure d'arrivée ou SecondsPastMidnight.INFINITE si l'arrêt
     *         donné n'est pas dans la table des heures d'arrivée passée au
     *         constructeur.
     */
    public int arrivalTime(Stop stop) {
        if (arrivalTime.containsKey(stop)) {
            return arrivalTime.get(stop);
        } else {
            return SecondsPastMidnight.INFINITE;
        }
    }

    /**
     * @throw IllegalArgumentException si l'arrêt passé n'est pas présent dans
     *        la table des heures d'arrivée.
     * @param stop
     *            arrêt auquel au souhaite aller.
     * @return le chamin pour aller de l'arrêt de départ à celui passé en
     *         argument (le première élément de la liste étant l'arrêt de départ
     *         et le dernier celui d'arrivée).
     */
    public List<Stop> pathTo(Stop stop) {
        if (!arrivalTime.containsKey(stop)) {
            throw new IllegalArgumentException();
        }

        List<Stop> liste = new ArrayList<Stop>();

        Stop s = stop;
        liste.add(stop); // liste dans le mauvais sens
        while (!s.equals(startingStop)) {
            s = predecessor.get(s);
            liste.add(s);
        }
        
        Collections.reverse(liste);

        return liste;
    }

    /**
     * class qui sert de Builer pour un FastestPathTree
     */
    public final static class Builder {
        private final Stop startingStop;
        private final Map<Stop, Integer> arrivalTime;
        private final Map<Stop, Stop> predecessor;

        /**
         * qui construit un bâtisseur pour un arbre des trajets les plus rapides
         * avec l'arrêt et l'heure de départ donnés.
         * 
         * @throw IllegalArgumentException si l'heure de départ est négative.
         * @param startingStop
         *            l'arrêt de départ.
         * @param startingTime
         *            l'heure de départ. (l'heure de première arrivée de l'arrêt
         *            de départ est l'heure de départ).
         */
        public Builder(Stop startingStop, int startingTime) {
            if (startingTime < 0) {
                throw new IllegalArgumentException();
            }

            this.startingStop = startingStop;
            this.arrivalTime = new HashMap<Stop, Integer>();
            this.predecessor = new HashMap<Stop, Stop>();

            arrivalTime.put(startingStop, startingTime);
        }

        /**
         * (re)définit l'heure de première arrivée et le prédécesseur de l'arrêt
         * donné dans l'arbre en construction
         * 
         * @throw IllegalArgumentException si l'heure donnée est antérieure à
         *        l'heure de départ.
         * @param stop
         *            l'arrêt dont l'on souhaite modifié les données
         * @param time
         *            la nouvelle première heure d'arrivée
         * @param predecessor
         *            le nouveau prédecesseur
         * @return le builder modifié
         */
        public Builder setArrivalTime(Stop stop, int time, Stop predecessor) {
            if (time < arrivalTime.get(startingStop)) {
                throw new IllegalArgumentException();
            }

            this.arrivalTime.put(stop, time);
            this.predecessor.put(stop, predecessor);

            return this;
        }

        /**
         * retourne l'heure de première arrivée à l'arrêt donné
         * 
         * @param stop
         *            l'arrêt pour lequel on souhaite obtenir la première
         *            arrivée.
         * @return l'heure d'arrivée à l'arrêt souhaite ou
         *         SecondsPastMidnight.INFINITE si aucune heure d'arrivée n'a
         *         été attribuée à cet arrêt jusqu'ici.
         */
        public int arrivalTime(Stop stop) {
            if (arrivalTime.containsKey(stop)) {
                return arrivalTime.get(stop);
            } else {
                return SecondsPastMidnight.INFINITE;
            }
        }

        /**
         * @return le FastestPathTree construit via le Builder.
         */
        public FastestPathTree build() {
            return new FastestPathTree(startingStop, arrivalTime, predecessor);
        }
    }
}
