package ch.epfl.isochrone.timetable;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import ch.epfl.isochrone.geo.PointWGS84;

/**
 * Classe servant à lire les tableau de TL et de les convertir au format codé
 * précédemment.
 * 
 * @author Charles Thiebaut (238935)
 * @author Arnaud Delamare (234841)
 * 
 */
public final class TimeTableReader {
    private final String prefixe;

    /**
     * Constructeur permettant de définir le préfixe de l'aborescence des
     * fichiers à lire.
     * 
     * @param l
     *            'aborescence du fichier dans lequelle les fichiers à lire son
     *            stocker
     */
    public TimeTableReader(String baseResourceName) {
        this.prefixe = baseResourceName;
    }

    /**
     * @throw IOException en cas d'erreur d'entrée-sortie ou d'autres exceptions
     *        en cas d'erreur de format de données.
     * @return le tableau horaire remplis avec les donnés consernant les arrêts
     *         ainsi que les services.
     */
    public TimeTable readTimeTable() throws IOException {
        TimeTable.Builder tb = readStops(new TimeTable.Builder());

        tb = readServices(tb);

        return tb.build();
    }

    /**
     * @throw IOException en cas d'erreur d'entrée-sortie ou d'autres exceptions
     *        en cas d'erreur de format de donsnées.
     * @return un graph qui correspond aux informations entrées en paramètre et
     *         aux données fournies dans les .cvs
     */
    public Graph readGraphForServices(Set<Stop> stops, Set<Service> services,
            int walkingTime, double walkingSpeed) throws IOException {
        Graph.Builder gb = new Graph.Builder(stops);

        InputStream stopsStream = getClass().getResourceAsStream(
                prefixe + "stop_times.csv");

        BufferedReader reader = new BufferedReader(new InputStreamReader(
                stopsStream, StandardCharsets.UTF_8));
        String line;
        String champs[];
        Stop stop1 = null, stop2 = null;
        while ((line = reader.readLine()) != null) {
            champs = line.split(";");
            for (Service s : services) {
                if (s.name().equals(champs[0])) {
                    stop1 = null;
                    stop2 = null;
                    for (Stop st : stops) {
                        if (st.name().equals(champs[1])) {
                            stop1 = st;
                        }
                        if (st.name().equals(champs[3])) {
                            stop2 = st;
                        }
                    }

                    if (stop1 != null && stop2 != null) { // n'arrive
                                                          // normalement jamais
                        gb.addTripEdge(stop1, stop2,
                                Integer.parseInt(champs[2]),
                                Integer.parseInt(champs[4]));
                    }
                }

            }
        }
        reader.close();

        gb.addAllWalkEdges(walkingTime, walkingSpeed);

        return gb.build();
    }

    /**
     * @throw IOException en cas d'erreur d'entrée-sortie ou d'autres exceptions
     *        en cas d'erreur de format de données.
     * @return un tableau horaire remplis avec les donnés concernant les arrêts.
     */
    private TimeTable.Builder readStops(TimeTable.Builder tb)
            throws IOException {
        InputStream stopsStream = getClass().getResourceAsStream(
                prefixe + "stops.csv");

        BufferedReader reader = new BufferedReader(new InputStreamReader(
                stopsStream, StandardCharsets.UTF_8));
        String line;
        String champs[];
        while ((line = reader.readLine()) != null) {
            champs = line.split(";");
            tb.addStop(new Stop(champs[0], new PointWGS84(Math.toRadians(Double
                    .parseDouble(champs[2])), Math.toRadians(Double
                    .parseDouble(champs[1])))));
        }
        reader.close();

        return tb;
    }

    /**
     * @throw IOException en cas d'erreur d'entrée-sortie ou d'autres exceptions
     *        en cas d'erreur de format de données.
     * @return le tableau horaire remplis avec les donnés concernant les
     *         services.
     */
    private TimeTable.Builder readServices(TimeTable.Builder tb)
            throws IOException {
        InputStream serviceStream = getClass().getResourceAsStream(
                prefixe + "calendar.csv");

        BufferedReader reader = new BufferedReader(new InputStreamReader(
                serviceStream, StandardCharsets.UTF_8));
        List<Service.Builder> sbListe = new ArrayList<Service.Builder>();
        Service.Builder sb;
        String line;
        String champs[];
        Date.DayOfWeek[] tab = Date.DayOfWeek.values();

        while ((line = reader.readLine()) != null) {
            champs = line.split(";");
            sb = new Service.Builder(champs[0], stringToDate(champs[8]),
                    stringToDate(champs[9]));

            for (int i = 1; i <= 7; i++) {
                if (Integer.parseInt(champs[i]) == 1) {
                    sb.addOperatingDay(tab[i - 1]);
                }
            }

            sbListe.add(sb);
        }
        reader.close();

        InputStream serviceStream2 = getClass().getResourceAsStream(
                prefixe + "calendar_dates.csv"); // lit les jours exceptionnels

        BufferedReader reader2 = new BufferedReader(new InputStreamReader(
                serviceStream2, StandardCharsets.UTF_8));

        while ((line = reader2.readLine()) != null) {
            champs = line.split(";");

            for (Service.Builder sb2 : sbListe) {
                if (sb2.name().equals(champs[0])) {
                    if (Integer.parseInt(champs[2]) == 1) {
                        sb2.addIncludedDate(stringToDate(champs[1]));
                    } else {
                        sb2.addExcludedDate(stringToDate(champs[1]));
                    }
                }
            }
        }
        reader2.close();

        for (Service.Builder sb2 : sbListe) {
            tb.addService(sb2.build());
        }

        return tb;
    }

    /**
     * @param s
     *            la chaine de caractère que l'on souhaite convertir au format
     *            Date.
     * @return la chaine de caractère passé en argument au format Date.
     */
    private Date stringToDate(String s) {
        return new Date(Integer.parseInt(s.substring(6, 8)), Integer.parseInt(s
                .substring(4, 6)), Integer.parseInt(s.substring(0, 4)));
    }

}
