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);
        }
    }

}
