package ai.cogmission.fxmaps.demo;

import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;


import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;
import javafx.stage.Window;


import org.controlsfx.control.CheckComboBox;


import ai.cogmission.fxmaps.model.MarkerType;
import ai.cogmission.fxmaps.model.PersistentMap;
import ai.cogmission.fxmaps.model.Route;
import ai.cogmission.fxmaps.ui.Map;
import ai.cogmission.fxmaps.ui.Map.Mode;
import ai.cogmission.fxmaps.xml.GPXPersistentMap;
import ai.cogmission.fxmaps.xml.GPXReader;
import ai.cogmission.fxmaps.xml.GPXType;
import fxpresso.tidbit.ui.Flyout;

/**
 * <p>
 * Example tool bar for the reference implementation
 * </p><p>
 * Shows how to create and interact with a tool bar in your own map
 * application
 * </p>
 * 
 * @author cogmission
 */
public class RefImplToolBar extends ToolBar {
    private RefImpl parent;
    
    private Map map;
    
    private ToggleButton directionsBtn;
    
    private ComboBox<String> mapCombo;
    private CheckComboBox<String> routeCombo;
    private Flyout mapFlyout;
    private Flyout routeFlyout;
    private ToggleButton mapChooser;
    private ToggleButton routeChooser;
    private CheckBox addWaypointBox;
    private TextField newRouteField;
    
    private static final String DEFAULT_ROUTE_NAME = "temp (change me)";
    private ChangeListener<Flyout.Status> local;
    
    
    /**
     * Constructs a new {@code RefImplToolBar}
     * 
     * @param parent    the parent application
     * @param map       the instance of {@link Map}
     */
    public RefImplToolBar(RefImpl parent, Map map) {
        this.parent = parent;
        this.map = map;
        initMapSelector();
        initRouteSelector();
        initializeToolBar();
        configureToolBar();
    }
    
    /**
     * Create the {@link ToolBar} for controlling map parameters.
     */
    public void initializeToolBar() {
        getItems().addAll(
            directionsBtn = new ToggleButton("Directions"),
            new Separator(),
            mapFlyout = new Flyout(mapChooser = new ToggleButton("Create / Select Map"), getMapControl()),
            new Separator(),
            routeFlyout = new Flyout(routeChooser = new ToggleButton("Create / Select Route"), getRouteControl())
        );
    }
    
    public Button getGPXLoadControl() {
        Button load = new Button("Select GPX File");
        load.setOnAction(e -> {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("Open GPX File");
            fileChooser.getExtensionFilters().addAll(
                    new ExtensionFilter("GPX Files", "*.gpx"),
                    new ExtensionFilter("All Files", "*.*"));
            File selectedFile = fileChooser.showOpenDialog(parent.getPrimaryStage());
            if (selectedFile != null) {
                System.out.println("file = " + selectedFile);
                GPXReader reader = new GPXReader();
                try {
                    GPXPersistentMap gpxMap = reader.read(selectedFile.toURI().toURL());
                    PersistentMap mMap = GPXPersistentMap.asFXMap(gpxMap, GPXType.TRACK);
                    map.getMapStore().getMaps().put(mMap.getName(), mMap);
                    map.getMapStore().selectMap(mMap.getName());
                    parent.createOrSelectMap(mMap.getName());
                    map.setCenter(map.getMapStore().getMap(mMap.getName()).getRoutes().get(0).getOrigin().getLatLon());
                    map.refresh();
                } catch(Exception e1) {
                    e1.printStackTrace();
                }
                
                map.setOverlayVisible(false);
            }
        });
        
        return load;
    }
    
    /**
     * Add the ToolBar's action handlers etc.
     */
    public void configureToolBar() {
        mapFlyout.setFlyoutStyle("-fx-background-color: rgba(0, 0, 0, 0.5); -fx-background-radius: 5,5,5,5;");
        routeFlyout.setFlyoutStyle("-fx-background-color: rgba(0, 0, 0, 0.5); -fx-background-radius: 5,5,5,5;");
        
        directionsBtn.setOnAction(e -> map.setDirectionsVisible(directionsBtn.isSelected()));
        directionsBtn.setSelected(true);
        
        mapChooser.setOnAction(e -> {
            // Protect against clicks during animation and button getting out of sync
            if(mapChooser.isSelected() && mapFlyout.getFlyoutStatusProperty().get() == Flyout.Status.RUNNING) {
                mapChooser.setSelected(false);
                return;
            }else if(!mapChooser.isSelected() && mapFlyout.getFlyoutStatusProperty().get() == Flyout.Status.RUNNING) {
                mapChooser.setSelected(true);
                return;
            }
            
            if(mapFlyout.flyoutShowing()) {
                mapFlyout.dismiss();
            }else{
                mapFlyout.flyout();
                mapCombo.getEditor().requestFocus();
                mapCombo.getEditor().selectAll();
            }
        });
        
        routeChooser.setDisable(true);
        routeChooser.setOnAction(e -> {
            // Protect against clicks during animation and button getting out of sync
            if(routeChooser.isSelected() && routeFlyout.getFlyoutStatusProperty().get() == Flyout.Status.RUNNING) {
                routeChooser.setSelected(false);
                return;
            }else if(!routeChooser.isSelected() && routeFlyout.getFlyoutStatusProperty().get() == Flyout.Status.RUNNING) {
                routeChooser.setSelected(true);
                return;
            }
            
            if(routeFlyout.flyoutShowing()) {
                routeFlyout.dismiss();
            }else{
                routeFlyout.flyout();
//                routeCombo.getEditor().requestFocus();
//                routeCombo.getEditor().selectAll();
            }
        });
    }
    
    /**
     * Initializes the control for choosing/creating maps
     */
    public void initMapSelector() {
        mapCombo = new ComboBox<>();
        mapCombo.setEditable(true);
        mapCombo.setPromptText("Type map name...");
        // Listen for "Enter" key presses
        mapCombo.valueProperty().addListener(getMapSelectionListener());
    }
    
    /**
     * Initializes the control for choosing/creating maps
     */
    public void initRouteSelector() {
        routeCombo = new CheckComboBox<>();
        // Make sure window resizes with combo box size changes due to display additions
        routeCombo.layoutBoundsProperty().addListener((v, o, n) -> {
            Window w = routeFlyout.getFlyoutContainer().getScene().getWindow();
            if(w != null) {
                w.setWidth(w.getWidth() + (n.getWidth() - o.getWidth()));
            }
        });
        
        //routeCombo.setEditable(true);
        //routeCombo.setPromptText("Type route name...");
        //routeCombo.valueProperty().addListener(getRouteSelectionListener());
    }
    
    /**
     * Creates and returns the control for loading maps
     * @return  the control for loading stored maps
     */
    public GridPane getMapControl() {
        GridPane gp = new GridPane();
        gp.setPadding(new Insets(5, 5, 5, 5));
        gp.setHgap(5);
        gp.setVgap(5);
        
        Button gpxLoader = getGPXLoadControl();
        gpxLoader.setPrefHeight(15);
        
        Label l = new Label("Select or enter map name:");
        l.setFont(Font.font(l.getFont().getFamily(), 12));
        l.setTextFill(Color.WHITE);
        
        Button add = new Button("Add");
        add.disableProperty().set(true);
        add.setOnAction(e -> mapCombo.valueProperty().set(mapCombo.getEditor().getText()));
        
        Button clr = new Button("Erase map");
        clr.disableProperty().set(true);
        clr.setOnAction(e -> map.eraseMap());
        
        Button del = new Button("Delete map");
        del.disableProperty().set(true);
        del.setOnAction(e -> {
            map.clearMap();
            map.deleteMap(mapCombo.getEditor().getText());
            mapCombo.getItems().remove(mapCombo.getEditor().getText());
            mapCombo.getSelectionModel().clearSelection();
            mapCombo.getEditor().clear();
            map.setMode(Mode.NORMAL);
            mapChooser.fire();
            map.setOverlayVisible(true);
            routeChooser.setDisable(true);
        });
        
        mapCombo.getEditor().textProperty().addListener((v, o, n) -> {
            if(mapCombo.getEditor().getText() != null && mapCombo.getEditor().getText().length() > 0) {
                add.disableProperty().set(false);
                if(map.getMapStore().getMap(mapCombo.getEditor().getText()) != null) {
                    clr.disableProperty().set(false);
                    del.disableProperty().set(false);
                }else{
                    clr.disableProperty().set(true);
                    del.disableProperty().set(true);
                }
            }else{
                add.disableProperty().set(true);
                clr.disableProperty().set(true);
                del.disableProperty().set(true);
            }
        });
        
        gp.add(gpxLoader, 0, 0, 2, 1);
        gp.add(new Separator(), 0, 1, 2, 1);
        gp.add(l, 0, 2, 2, 1);
        gp.add(mapCombo, 0, 3, 3, 1);
        gp.add(add, 3, 3);
        gp.add(clr, 4, 3);
        gp.add(del, 5, 3);
        
        return gp;
    }
    
    /**
     * Creates and returns the control for loading maps
     * @return  the control for loading stored maps
     */
    public GridPane getRouteControl() {
        GridPane gp = new GridPane();
        gp.setPadding(new Insets(5, 5, 5, 5));
        gp.setHgap(5);
        gp.setVgap(5);
        
        newRouteField = new TextField();
        newRouteField.setDisable(true);
        newRouteField.setOnAction(e -> {
            newRouteField.setDisable(true);
            String text = newRouteField.getText();
            newRouteField.clear();
            if(routeCombo.getItems().contains(text)) return;
            routeCombo.getItems().add(text);
            routeCombo.getCheckModel().check(text);
            Route newRoute = new Route(text);
            map.addRoute(newRoute);
            map.selectRoute(newRoute);
        });
        
        Button addNew = new Button("New");
        addNew.setOnAction(e -> {
            newRouteField.requestFocus();
            newRouteField.setDisable(false);
        });
        
        HBox rsMode = new HBox();
        addWaypointBox = new CheckBox("Add Waypoints");
        addWaypointBox.setTextFill(Color.WHITE);
        addWaypointBox.setOnAction(e -> {
            map.setMode(addWaypointBox.isSelected() ? Mode.ADD_WAYPOINTS : Mode.NORMAL);
            
            if(map.getCurrentRoute() != null) {
                MarkerType.reset(map.getCurrentRoute());
            }
        });
        rsMode.getChildren().add(addWaypointBox);
        Label l = new Label("Select or enter route name:");
        l.setFont(Font.font(l.getFont().getFamily(), 12));
        l.setTextFill(Color.WHITE);
        routeCombo.getCheckModel().getCheckedItems().addListener((Change<? extends String> c) -> {
            // Set the current Route pointer in the map
            alterSelection(c);
            
            map.eraseMap();
            List<String> items = routeCombo.getCheckModel().getCheckedItems();
            List<Route> routes = null;
            PersistentMap selectedMap = map.getMapStore().getMap(map.getMapStore().getSelectedMapName());
            if(items != null && items.size() > 0) {
                routes = items.stream().map(s -> selectedMap.getRoute(s)).collect(Collectors.toList());
            }else{
                return;
            }
            map.displayRoutes(routes);
        });
        Button clr = new Button("Clear route");
        clr.setOnAction(e -> routeCombo.getCheckModel()
            .getCheckedItems().stream().forEach(s -> map.clearRoute(map.getRoute(s))));
        Button del = new Button("Delete route");
        del.setOnAction(e -> routeCombo.getCheckModel()
            .getCheckedItems().stream().forEach(s -> {
                Route r = map.getRoute(s);
                map.eraseRoute(r);
                map.removeRoute(r);
            }));
        
        gp.add(l, 0, 0, 2, 1);
        gp.add(newRouteField, 0, 1, 2, 1);
        gp.add(addNew, 2, 1, 1, 1);
        gp.add(new Separator(), 0, 2, 5, 1);
        gp.add(rsMode, 2, 0, 2, 1);
        gp.add(routeCombo, 0, 3, 2, 1);
        gp.add(clr, 2, 3);
        gp.add(del, 3, 3);
        
        return gp;
    }
    
    /**
     * If a {@link Route} was selected, it will become the Map's currently
     * selected route; if a Route was deselected, the Route with the smallest
     * index will be selected or none will be selected if no other Route was
     * selected when the currently deselected Route was specified.
     * @param c
     */
    public void alterSelection(Change<? extends String> c) {
        // Advance to the first change
        c.next();
        
        if(c.wasAdded()) {
            Route selectedRoute = map.getMapStore().getMap(map.getMapStore().getSelectedMapName()).getRoute(c.getAddedSubList().get(0));
            map.setCurrentRoute(selectedRoute);
        }else{
            List<String> items = routeCombo.getCheckModel().getCheckedItems();
            if(items.size() > 0) {
                Route selectedRoute = map.getMapStore().getMap(map.getMapStore().getSelectedMapName()).getRoute(items.get(0));
                map.setCurrentRoute(selectedRoute);
            }
        }
        
    }
    
    public void mapSelected(PersistentMap persistentMap) {
        if(mapChooser.isSelected()) {
            mapFlyout.getFlyoutStatusProperty().addListener(local = (v, o, n) -> {
                
                if(persistentMap.getRoutes().isEmpty() && n == Flyout.Status.COMPLETE) {
                    
                    if(!routeFlyout.flyoutShowing()) {
                        mapFlyout.getFlyoutStatusProperty().removeListener(local);
                        Platform.runLater(() -> {
                            newRouteField.setDisable(false);
                            newRouteField.setText(DEFAULT_ROUTE_NAME);
                            newRouteField.requestFocus();
                            newRouteField.selectAll();
                        });
                        
                        routeChooser.fire();
                    }
                    
                    addWaypointBox.setSelected(true);
                    map.setMode(Mode.ADD_WAYPOINTS);
                }else if(n == Flyout.Status.COMPLETE){
                    mapFlyout.getFlyoutStatusProperty().removeListener(local);
                    routeCombo.getItems().clear();
                    
                    String[] sa = persistentMap.getRoutes().stream()
                        .map(r -> r.getName())
                        .filter(name -> !routeCombo.getItems().contains(name))
                        .toArray(String[]::new);
                    routeCombo.getItems().addAll(sa);
                    routeCombo.getCheckModel().check(0);
                    
                    map.refresh();
                 }
            });
            
            mapChooser.fire();            
        }
        
        map.refresh();
    }
    
    public void addMaps(Collection<String> mapNames) {
        mapCombo.getItems().addAll(mapNames);
    }
    
    public void addMap(String mapName) {
        if(!containsMap(mapName)) {
            mapCombo.getItems().add(mapName);
        }
    }
    
    public boolean containsMap(String name) {
        return mapCombo.getItems().contains(name);
    }
    
    public void selectMap(String name) {
        routeCombo.getCheckModel().clearChecks();
        routeCombo.getItems().clear();
        
        mapCombo.getSelectionModel().select(name);
        
        // IMPORTANT: Point the MapStore to the desired map selection
        try {
            map.getMapStore().selectMap(name);
        }catch(Exception e) { 
            e.printStackTrace();
        }
    }
    
    public void clearMapSelection() {
        mapCombo.getSelectionModel().clearSelection();
        map.setOverlayVisible(true);
    }
    
    public void removeMapListing(String mapName) {
        mapCombo.getItems().remove(mapName);
    }
    
    /**
     * Returns the handler which responds to the map combo box selection.
     * 
     * @return  the map combo handler
     */
    public ChangeListener<String> getMapSelectionListener() {
        return (v, o, n) -> {
            if(n == null) return;
            parent.createOrSelectMap(n);
            routeChooser.setDisable(false);
        };
    }
    
    /**
     * Adds the Route to the route combo in the toolbar
     * @param routeName the name of the route to add
     */
    public void addRoute(String routeName) {
        if(!containsRoute(routeName)) {
            routeCombo.getItems().add(routeName);
        }
    }
    
    /**
     * Returns a flag indicating whether the route exists in the combo model
     * @param name  the name to check
     * @return  true if it exists, false if not
     */
    public boolean containsRoute(String name) {
        return routeCombo.getItems().contains(name);
    }
     
}