package ch.epfl.isochrone.gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JViewport;
import javax.swing.SpinnerDateModel;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MouseInputAdapter;

import ch.epfl.isochrone.geo.PointOSM;
import ch.epfl.isochrone.geo.PointWGS84;
import ch.epfl.isochrone.tiledmap.CachedTileProvider;
import ch.epfl.isochrone.tiledmap.ColorTable;
import ch.epfl.isochrone.tiledmap.IsochroneTileProvider;
import ch.epfl.isochrone.tiledmap.OSMTileProvider;
import ch.epfl.isochrone.tiledmap.TileProvider;
import ch.epfl.isochrone.tiledmap.TransparentTileProvider;
import ch.epfl.isochrone.timetable.Date;
import ch.epfl.isochrone.timetable.Date.Month;
import ch.epfl.isochrone.timetable.FastestPathTree;
import ch.epfl.isochrone.timetable.Graph;
import ch.epfl.isochrone.timetable.SecondsPastMidnight;
import ch.epfl.isochrone.timetable.Service;
import ch.epfl.isochrone.timetable.Stop;
import ch.epfl.isochrone.timetable.TimeTable;
import ch.epfl.isochrone.timetable.TimeTableReader;

public final class IsochroneTL {
    private static final String OSM_TILE_URL = "http://b.tile.openstreetmap.org/";
    private static final int INITIAL_ZOOM = 11;
    private static final PointWGS84 INITIAL_POSITION = new PointWGS84(
            Math.toRadians(6.476), Math.toRadians(46.613));
    private static final String INITIAL_STARTING_STOP_NAME = "Lausanne-Flon";
    private static final int INITIAL_DEPARTURE_TIME = SecondsPastMidnight
            .fromHMS(6, 8, 0);
    private static final Date INITIAL_DATE = new Date(1, Month.OCTOBER, 2013);
    private static final int WALKING_TIME = 5 * 60;
    private static final double WALKING_SPEED = 1.25;

    private final TiledMapComponent tiledMapComponent;

    private Point pointSouris;
    private Point pointCarte = new Point(0, 0);
    private TimeTable tableHoraire;
    private TimeTableReader lecteurHoraire;
    private Set<Service> services;
    private Graph graph;
    private Date date=INITIAL_DATE;
    private int heure=INITIAL_DEPARTURE_TIME;
    private FastestPathTree arbre;
    
    private JComboBox<Stop> menuArret;
    private JSpinner spinnerDate;
    private Stop depart;
    
    private TileProvider trTileProvider;
    private ColorTable tableCouleur;

    public IsochroneTL() throws IOException {
        TileProvider bgTileProvider = new CachedTileProvider(
                new OSMTileProvider(new URL(OSM_TILE_URL)));
        tiledMapComponent = new TiledMapComponent(INITIAL_ZOOM);

        lecteurHoraire = new TimeTableReader("/time-table/");
        tableHoraire = lecteurHoraire.readTimeTable();

        depart = null;
        for (Stop s : tableHoraire.stops()) {
            if (s.name().equals(INITIAL_STARTING_STOP_NAME)) {
                depart = s;
            }
        }
        
        services = tableHoraire.servicesForDate(INITIAL_DATE);
        graph = lecteurHoraire.readGraphForServices(tableHoraire.stops(),
                services , WALKING_TIME, WALKING_SPEED);

        arbre = graph.fastestPaths(depart, INITIAL_DEPARTURE_TIME);

        List<Color> listeCouleur = new ArrayList<Color>();
        listeCouleur.add(new Color(255, 0, 0));
        listeCouleur.add(new Color(255, 122, 0));
        listeCouleur.add(new Color(255, 255, 0));
        listeCouleur.add(new Color(122, 255, 0));
        listeCouleur.add(new Color(0, 255, 0));
        listeCouleur.add(new Color(0, 122, 122));
        listeCouleur.add(new Color(0, 0, 255));
        listeCouleur.add(new Color(0, 0, 122));
        listeCouleur.add(new Color(0, 0, 0));
        tableCouleur = new ColorTable(300, listeCouleur);

        trTileProvider = new CachedTileProvider(
                new TransparentTileProvider(0.5, new IsochroneTileProvider(arbre,
                        tableCouleur, WALKING_SPEED)));

        tiledMapComponent.addTileProvider(bgTileProvider);
        tiledMapComponent.addTileProvider(trTileProvider);
    }

    private JComponent createCenterPanel() {
        final JViewport viewPort = new JViewport();
        viewPort.setView(tiledMapComponent);
        PointOSM startingPosOSM = INITIAL_POSITION.toOSM(tiledMapComponent.zoom());
        viewPort.setViewPosition(new Point(startingPosOSM.roundedX(), startingPosOSM.roundedY()));
        
        tiledMapComponent.setSize(tiledMapComponent.getPreferredSize());
        
        tiledMapComponent.view=viewPort;

        final JPanel copyrightPanel = createCopyrightPanel();

        final JLayeredPane layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize(new Dimension(400, 300));

        layeredPane.add(viewPort, new Integer(0));
        layeredPane.add(copyrightPanel, new Integer(1));

        layeredPane.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                Rectangle newBounds = layeredPane.getBounds();
                
                viewPort.setBounds(newBounds);
                copyrightPanel.setBounds(newBounds);

                viewPort.revalidate();
                copyrightPanel.revalidate();
            }
        });
        
        layeredPane.addMouseListener(new MouseInputAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                pointSouris = e.getPoint();
                pointCarte = viewPort.getViewPosition();
            }
        });
        
        layeredPane.addMouseMotionListener(new MouseInputAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                pointCarte.x += pointSouris.x - e.getPoint().x;
                pointCarte.y += pointSouris.y - e.getPoint().y;
                pointSouris = e.getPoint();
                viewPort.setViewPosition(pointCarte);
            }
        });
        
        layeredPane.addMouseWheelListener(new MouseInputAdapter() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                int newZoom = tiledMapComponent.zoom()-e.getWheelRotation();
                
                if (newZoom<10)
                    newZoom=10;
                
                if (newZoom>19)
                    newZoom=19;
                
                pointCarte = viewPort.getViewPosition();
                
                pointCarte.x += e.getPoint().x;
                pointCarte.y += e.getPoint().y;

                pointCarte.x = (int)(pointCarte.x*Math.pow(2, newZoom-tiledMapComponent.zoom()));
                pointCarte.y = (int)(pointCarte.y*Math.pow(2, newZoom-tiledMapComponent.zoom()));
                
                pointCarte.x -= e.getPoint().x;
                pointCarte.y -= e.getPoint().y;
                
                viewPort.setViewPosition(pointCarte);
                tiledMapComponent.setZoom(newZoom);
                tiledMapComponent.repaint();
            }
        });

        // TODO déplacement de la carte à la souris
        // TODO zoom de la carte à la souris (molette)

        JPanel centerPanel = new JPanel(new BorderLayout());
        centerPanel.add(layeredPane, BorderLayout.CENTER);
        return centerPanel;
    }
    
    @SuppressWarnings("deprecation")
    private JComponent createTopPanel() {
        

        JPanel topPanel = new JPanel(new FlowLayout());
        
        JLabel etiquetteDepart = new JLabel("Depart");
        
        Vector<Stop> vecteurStop = new Vector<Stop>();
        
        depart = null; // peut etre a enleve
        for (Stop s : tableHoraire.stops())
        {
            vecteurStop.add(s);
            
            if (s.name().equals(INITIAL_STARTING_STOP_NAME))
                depart = s;
        }
        
       Collections.sort(vecteurStop);
       // a savoir ce que c'est...
        
        menuArret = new JComboBox<Stop>(vecteurStop);
        menuArret.setSelectedItem(depart);
        
        ActionListener listener = new ActionListener(){

            @Override
            public void actionPerformed(ActionEvent e) {
                setArret((Stop)menuArret.getSelectedItem());
            }
            
        };
        menuArret.addActionListener(listener);
        
        JSeparator separateur = new JSeparator();
        
        JLabel etiquetteDate = new JLabel("Date et Heure");
        
        SpinnerDateModel model = new SpinnerDateModel();
        java.util.Date utilDate = INITIAL_DATE.toJavaDate();
        utilDate.setHours(SecondsPastMidnight.hours(INITIAL_DEPARTURE_TIME));
        utilDate.setMinutes(SecondsPastMidnight.minutes(INITIAL_DEPARTURE_TIME));
        utilDate.setSeconds(SecondsPastMidnight.seconds(INITIAL_DEPARTURE_TIME));
        model.setValue(utilDate);
        
        spinnerDate = new JSpinner(model);
        ChangeListener cl = new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent e) {
                setDateEtHeure((java.util.Date)spinnerDate.getModel().getValue());
            }
        };
        model.addChangeListener(cl);
        
        
        topPanel.add(etiquetteDepart);
        topPanel.add(menuArret);
        topPanel.add(separateur);
        topPanel.add(etiquetteDate);
        topPanel.add(spinnerDate);
         
        return topPanel;
    }

    private JPanel createCopyrightPanel() {
        Icon tlIcon = new ImageIcon(getClass().getResource(
                "/images/tl-logo.png"));
        String copyrightText = "Données horaires 2013. Source : Transports publics de la région lausannoise / Carte : © contributeurs d'OpenStreetMap";
        JLabel copyrightLabel = new JLabel(copyrightText, tlIcon,
                SwingConstants.CENTER);
        copyrightLabel.setOpaque(true);
        copyrightLabel.setForeground(new Color(1f, 1f, 1f, 0.6f));
        copyrightLabel.setBackground(new Color(0f, 0f, 0f, 0.4f));
        copyrightLabel.setBorder(BorderFactory.createEmptyBorder(3, 0, 5, 0));

        JPanel copyrightPanel = new JPanel(new BorderLayout());
        copyrightPanel.add(copyrightLabel, BorderLayout.PAGE_END);
        copyrightPanel.setOpaque(false);
        return copyrightPanel;
    }

    private void start() {
        
        
        // full Sceen MAC OS X
        if (System.getProperty("os.name").indexOf("Mac OS X") >= 0) {
            System.setProperty(
                    "com.apple.mrj.application.apple.menu.about.name",
                    "Isochrome TL");

        }

        JFrame frame = new JFrame("Isochrone TL");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        
        
        // full Sceen MAC OS X
        if (System.getProperty("os.name").indexOf("Mac OS X") >= 0) {
            String appleFull = "com.apple.eawt.FullScreenUtilities";
            String CanFullSceen = "setWindowCanFullScreen";

            try {
                Class<?> claz = Class.forName(appleFull);
                Method method = claz.getMethod(CanFullSceen, new Class<?>[] {
                        Window.class, boolean.class });
                method.invoke(null, frame, true);
            } catch (Throwable te) {
                te.printStackTrace();
            }
            
        
            
            
        }
        
        ImageIcon img = new ImageIcon("/images/tl-logo.png");
        
        
        
        // mettre Windows et Linux 
        

        frame.getContentPane().setLayout(new BorderLayout());
        frame.getContentPane().add(createTopPanel(), BorderLayout.PAGE_START);
        frame.getContentPane().add(createCenterPanel(), BorderLayout.CENTER);

        frame.pack();
        frame.setVisible(true);
        frame.setIconImage(img.getImage());
    }
    
    private void setDateEtHeure(java.util.Date utilDate)
    {
        if (!utilDate.equals(date.toJavaDate()))
        {
            int newHeure = SecondsPastMidnight.fromJavaDate(utilDate);
            
            Date newDate = new Date(utilDate);
            
            if (newHeure>=SecondsPastMidnight.fromHMS(0, 0, 0) && newHeure<=SecondsPastMidnight.fromHMS(4, 0, 0))
            {
                newHeure += SecondsPastMidnight.fromHMS(24, 0, 0);
                newDate = newDate.relative(-1);
            }
            
            if (!newDate.equals(date))
            {
                date = newDate;
                updateServicesEtGraph();
                updateArbreTrajetEtIsochrone();
            }
            else
            {
                if (newHeure!=heure)
                {
                    heure=newHeure;
                    updateArbreTrajetEtIsochrone();
                }
            }
            
        }
    }
    
    private void setArret(Stop s)
    {
        if (!s.equals(depart))
        {
            depart = s;
            updateArbreTrajetEtIsochrone();
            
        }
    }
    
    private void updateServicesEtGraph()
    {
        Set<Service> newServices = tableHoraire.servicesForDate(date);
        
        if (!newServices.equals(services))
        {
            services = newServices;
            
            try {
                graph = lecteurHoraire.readGraphForServices(tableHoraire.stops(),
                        services , WALKING_TIME, WALKING_SPEED);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    private void updateArbreTrajetEtIsochrone()
    {
        arbre = graph.fastestPaths(depart, heure);
        trTileProvider = new CachedTileProvider(
                new TransparentTileProvider(0.5, new IsochroneTileProvider(arbre,
                        tableCouleur, WALKING_SPEED)));
        
        tiledMapComponent.replaceTileProvider(1, trTileProvider);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    new IsochroneTL().start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
