package ch.epfl.isochrone.timetable;

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

/**
* Classe servant à stocker des services.
* 
* @author Charles Thiebaut (238935) 
* @author Arnaud Delamare (234841)
*
*/
public class Service {

    private final String name;
    private final Date startingDate;
    private final Date endingDate;
    private final Set<Date.DayOfWeek> operatingDays;
    private final Set<Date> excludedDates;
    private final Set<Date> includedDates;
    
    /**
     * @param name
     *       le nom du service. 
     * @param startingDate 
     *       debut de la période de service. 
     * @param endingDate 
     *       fin de la période de service. 
     * @param operatingDays
     *        jour où le service fonctionne.
     * @param excludedDates
     *        jour où le service n'est pas distribuer. 
     * @param includedDates
     *        jour où le server est exeptionnelement distribué. 
     * @throws IllegalArgumentException
     *         si la date de fin est antérieure à la date de début, si une des dates listées dans les exceptions est en dehors de la plage de validité du service, ou si l'intersection entre les dates exclus et incluses n'est pas valide. 
     *
     */  
    public Service(String name, Date startingDate, Date endingDate, Set<Date.DayOfWeek> operatingDays, Set<Date> excludedDates, Set<Date> includedDates)
    {
        if (startingDate.compareTo(endingDate)==1)
        {
            throw new IllegalArgumentException();
        }
        
        Iterator<Date> IteratorExcludedDates = excludedDates.iterator();
        Date d;
        
        while (IteratorExcludedDates.hasNext())
        {
            d=IteratorExcludedDates.next();
            if (startingDate.compareTo(d)==1 || endingDate.compareTo(d)==-1)
            {
                throw new IllegalArgumentException();
            }
        }
        
        Iterator<Date> IteratorIncludedDates = includedDates.iterator();
        
        while (IteratorIncludedDates.hasNext())
        {
            d=IteratorIncludedDates.next();
            if (startingDate.compareTo(d)==1 || endingDate.compareTo(d)==-1)
            {
                throw new IllegalArgumentException();
            }
        }
        
        for (Date d1 : includedDates)
        {
            if (excludedDates.contains(d1))
            {
                throw new IllegalArgumentException();
            }
        }
        
        this.name=name;
        this.startingDate=startingDate;
        this.endingDate=endingDate;
        this.operatingDays=new HashSet<Date.DayOfWeek>(operatingDays);
        this.excludedDates=new HashSet<Date>(excludedDates);
        this.includedDates=new HashSet<Date>(includedDates);
    }
    
    /**
     * @return le nom du service. 
     */  
    public String name()
    {
        return this.name;
    }
    
    /**
     * @param ok
     *        definie si le service est bien operant à la date passé en argument.
     * @param date
     *        date pour laquelle on veut vérifier si le service est opérant. 
     * @return
     *        faulse si le service n'est pas distribuer au jour passé en argument
     *        True si le service est distribuer au jour passé en argument.               
     */  
    public boolean isOperatingOn(Date date)
    {
        boolean ok=false;
        
        if ((startingDate.compareTo(date)==0 || startingDate.compareTo(date)==-1) && (endingDate.compareTo(date)==0 || endingDate.compareTo(date)==1))
        {
            for (Date.DayOfWeek d : operatingDays)
            {
                if (date.dayOfWeek().equals(d))
                {
                    ok=true;
                }
            }
            
            if (ok==true)
            {
                for (Date d : excludedDates)
                {
                    if (d.equals(date))
                    {
                        return false;
                    }
                }
            }
            else
            {
                for (Date d : includedDates)
                {
                    if (d.equals(date))
                    {
                        return true;
                    }
                }
            }
            
        }
        
        return ok;
    }
    
    /**
     * @return le nom du service. 
     */  
    public String toString()
    {
        return name;
    }
    
    /**
     * class qui sert de builder pour un service. 
     */  
    public static class Builder {
        private final String name;
        private final Date startingDate;
        private final Date endingDate;
        private Set<Date.DayOfWeek> operatingDays;
        private Set<Date> excludedDates;
        private Set<Date> includedDates;
        
        /**
         * @param name
         *        le nom du sevice. 
         * @param startingDate 
         *        la date du début du service.
         * @param endingDate
         *        la date de fin du service. 
         * @throw IllegalArgumentException
         *        si la date de début est postérieure à la date de fin.        
         */  
        public Builder(String name, Date startingDate, Date endingDate)
        {
            if (startingDate.compareTo(endingDate)==1)
            {
                throw new IllegalArgumentException();
            }
            
            this.name=name;
            this.startingDate=startingDate;
            this.endingDate=endingDate;
            this.operatingDays=new HashSet<Date.DayOfWeek>();             
            this.excludedDates=new HashSet<Date>();
            this.includedDates=new HashSet<Date>();
        }
        
        /**
         * @return le nom du service. 
         */  
        public String name()
        {
            return this.name;
        }
        
        /**
         * methode qui ajoute un jour où le service est distribué
         * @param day
         *        le jour que l'on souhaite ajouté au date où le service est distribué.
         * @return le builder modifié. 
         */
        public Builder addOperatingDay(Date.DayOfWeek day)
        {
            operatingDays.add(day);
            
            return this;
        }
        
        /**
         * methode qui ajoute un jour où le service n'est pas distribué
         * @param date 
         *        date que l'on souhaite ajouté aux dates excluses.
         * @throw IllegalArgumentException
         *        si la date est antérieure a la date de début du service ou si elle est postérieure à la date de fin. 
         * @return le builder modifié.
         */  
        public Builder addExcludedDate(Date date)
        {
            if (startingDate.compareTo(date)==1 || endingDate.compareTo(date)==-1)  // pas dans plage de validite
            {
                throw new IllegalArgumentException();
            }
            
            Iterator<Date> IteratorIncludedDates = includedDates.iterator();
            Date d;
            
            while (IteratorIncludedDates.hasNext())
            {
                d=IteratorIncludedDates.next();
                if (date.compareTo(d)==0)
                {
                    throw new IllegalArgumentException();
                }
            }
            
            excludedDates.add(date);
            
            return this;
        }
        
        /**
         * methode qui ajoute un jour où le service est (exceptionnelement) distribué
         * @param date 
         *        date que l'on souhaite ajouté aux dates incluses.
         * @throw IllegalArgumentException
         *        si la date est antérieure a la date de début du service ou si elle est postérieure à la date de fin. 
         * @return le builder modifié.     
         */  
        public Builder addIncludedDate(Date date)
        {
            if (startingDate.compareTo(date)==1 || endingDate.compareTo(date)==-1)
            {
                throw new IllegalArgumentException();
            }
            
            Iterator<Date> IteratorExcludedDates = excludedDates.iterator();
            Date d;
            
            while (IteratorExcludedDates.hasNext())
            {
                d=IteratorExcludedDates.next();
                if (date.compareTo(d)==0)
                {
                    throw new IllegalArgumentException();
                }
            }
            
            includedDates.add(date);
            
            return this;
        }
        /**
         *@return le service construit via le builder.      
         */  
        public Service build()
        {
            return new Service(name, startingDate, endingDate, operatingDays, excludedDates, includedDates);
        }
    }

}


