package ch.epfl.isochrone.timetable;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public final class GraphEdge {
    
    private final Stop destination;
    private final int walkingTime;
    public final Set<Integer> packedTrips; // private
    
    
    /**
    *encode en entier un trajet donné par son heure de départ et son heure d'arrivée
    *@throw IllegalArgumentException 
    *       si l'heure de dépard ou d'arrivée est négative ou si elle est supérieure à la limite fixe.
    *@param departureTime
    *       heure de dépard.
    *@param arrivalTime 
    *       heure d'arrivée.
    *@return la date encodé en entier.
    */
    public static int packTrip(int departureTime, int arrivalTime)
    {
        if (departureTime<0 || departureTime>107999 || arrivalTime-departureTime<0 || arrivalTime-departureTime>9999)
        {
            throw new IllegalArgumentException();
        }
        
        return departureTime*10000 + (arrivalTime-departureTime);
    }
    
    /**
    *extrait l'heure de départ d'un trajet encodé
    *@param packedTrip
    *       l'entier du trajet encodé duquel on veut extraire l'heure de dépard.
    *@return l'heure de dépard extraite.
    */
    public static int unpackTripDepartureTime(int packedTrip)
    {
        return packedTrip/10000;
    }
    
    /**
    * extrait la durée du trajet encodé (en secondes). 
    *@param packedTrip
    *       l'entier du trajet encodé duquel on veut extraire la durée du trajet.
    *@return la durée du trajet extraite du trajet encodée (en seconde).
    *
    */
    public static int unpackTripDuration(int packedTrip)
    {
        return packedTrip%10000;
    }
    
    /**
    *extrait l'heure d'arrivée du trajet encodé passé en argument. 
    *@param packedTrip
    *       l'entier du trajet encodé duquel on veut extraire l'heure d'arrivée.
    *@return l'heure d'arrivée à partir du trajet encodé passé en argument.
    */
    public static int unpackTripArrivalTime(int packedTrip)
    {
        return unpackTripDepartureTime(packedTrip)+unpackTripDuration(packedTrip);
    }
    

    /**
    *@param destination
    *       l'arrêt (stop) de destination.
    *@param walkignTime
    *       le temps de marche en secondes, vaut -1 si le trajet est impossible ou trop long a effectue à pied.
    *@param packedTrips
    *       les trajets disponibles. 
    *
    *@throw IllegalArgumentException
    *       si le temps de marche est inférieure à -1.
    */
    public GraphEdge(Stop destination, int walkingTime, Set<Integer> packedTrips)
    {
        if (walkingTime<-1)
        {
            throw new IllegalArgumentException();
        }
        
        this.destination=destination;
        this.walkingTime=walkingTime;
        this.packedTrips=new HashSet<Integer>(packedTrips);
    }
    
    /**
    *@return l'arrêt de la destination. 
    */
    public Stop destination()
    {
        return destination;
    }
    
    /**
    *@param departureTime
    *       l'heure à laquelle on souhaite vérifier la disponibilité des transport.
    *
    *@return la première heure d'arrivee possible à la destination voulue étant donnée une heure de départ.        
    */
    public int earliestArrivalTime(int departureTime)
    {
        int walkingArrivalTime;
        
        if (walkingTime==-1)
        {
            walkingArrivalTime=SecondsPastMidnight.INFINITE;
        }
        else
        {
            walkingArrivalTime=departureTime + walkingTime;
        }
        
        int minTransportTime=SecondsPastMidnight.INFINITE;
        
        Integer[] tabDeparture = new Integer[packedTrips.size()];
        Integer[] tabArrival = new Integer[packedTrips.size()];
        
        Iterator<Integer> iteratorPackedTrips = packedTrips.iterator();
        Integer i;
        
        int j=0;
        while (iteratorPackedTrips.hasNext())
        {
            i=iteratorPackedTrips.next();
            tabDeparture[j]=unpackTripDepartureTime(i);
            tabArrival[j]=unpackTripArrivalTime(i);
            j++;
        }
        
        java.util.Arrays.sort(tabDeparture);
        java.util.Arrays.sort(tabArrival);
        int index = java.util.Arrays.binarySearch(tabDeparture, departureTime);
        
        if (index>=0)
        {
            minTransportTime = tabArrival[index];
        }
        else
        {
            if (-index-1>=tabArrival.length)
            {
                minTransportTime = SecondsPastMidnight.INFINITE;
            }
            else
            {
                minTransportTime = tabArrival[-index-1];
            }
        }
        
        return java.lang.Math.min(walkingArrivalTime, minTransportTime);
        
    }
/**
* class qui sert de Builder pour un GraphEdge
*/
public static class Builder{
    private final Stop destination;
    private int walkingTime;
    private Set<Integer> packedTrips;
    

    /**
    * builder pour un arc ayant l'arrêt donné comme destination, aucun trajet et un temps de marche de -1 (infiniment grand).
    *@param destination
    *       l'arrêt de destination.
    */
    public Builder(Stop destination){
        this.destination = destination; 
        walkingTime = -1; 
        packedTrips = new HashSet<Integer>();
    }
    
    /**
    * méthode qui change le temps de marche la remplaçant par la valeur passé en argument. 
    *@param newWalkingTime
    *       le nouveau temps de marche
    *@throw IllegalArgumentException
    *       si le temps de marche n'est pas valide (<-1)
    *@return le builder modifié. 
    */ 
    public Builder setWalkingTime(int newWalkingTime){
        if(newWalkingTime<-1){
            throw new IllegalArgumentException(); 
        }
        this.walkingTime = newWalkingTime;
        return this;
    }
    

    /**
    * méthode qui un trajet avec une heure de départ et d'arrivée passé en argument et tout deux en secondes depuis minuit. 
    *@param departureTime
    *       l'heure de départ du nouveau trajet
    *@param arrivalTime
    *       l'heure d'arrivée du nouveau trajet.
    *@throw IllegalArgumentException
    *       si l'heure d'arrivée est antérieure à l'heure de départ.
    *@return le builder modifié. 
    */ 
    public Builder addTrip(int departureTime, int arrivalTime){
        if (departureTime<0 || departureTime>107999 || arrivalTime-departureTime<0 || arrivalTime-departureTime>9999)
        {
            throw new IllegalArgumentException();
        }
        
       packedTrips.add(packTrip(departureTime,arrivalTime));
       
       return this; 
    }
    
    /**
    *@return le GraphEdge construit via le builder.
    */
    public GraphEdge build()
    {   
        return new GraphEdge(destination,walkingTime,packedTrips);
    }
    
}
}

