/*
 * Copyright (C) 2017 John Garner <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.pikatimer.race;

import static com.pikatimer.participant.CustomAttributeType.TIME;
import com.pikatimer.participant.Participant;
import com.pikatimer.participant.ParticipantDAO;
import com.pikatimer.util.Formatters;
import com.pikatimer.util.IntegerEditingCell;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.ComboBoxListCell;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import javafx.util.Pair;
import org.controlsfx.control.ToggleSwitch;

/**
 * FXML Controller class
 *
 * @author John Garner <[email protected]>
 */
public class FXMLAwardCategoryController {

    @FXML TextField awardTitleTextField;
    
    @FXML ComboBox<AwardCategoryType> awardTypeComboBox;
    
    @FXML ToggleSwitch chipToggleSwitch;
    @FXML ToggleSwitch pullToggleSwitch;
    
    @FXML ToggleSwitch visibleAwardsToggleSwitch;
    @FXML ToggleSwitch visibleOverallToggleSwitch;
    
    
    @FXML ComboBox<AwardDepthType> depthTypeComboBox;
    @FXML TextField depthTextField;
    @FXML VBox depthVBox;
    
    @FXML TableView<AwardDepth> depthTableView;
    @FXML TableColumn<AwardDepth,Integer> depthStartTableColumn;
    @FXML TableColumn<AwardDepth,String> depthEndTableColumn;
    @FXML TableColumn<AwardDepth,Integer> depthTableColumn;
    
    
    @FXML Button depthDeleteButton;
    @FXML Button depthAddButton;
    
    @FXML HBox mastersHBox;
    @FXML TextField mastersAgeTextField;
    
    AwardCategory awardCategory;
    
    // Custom Award Stuff
    @FXML GridPane customGridPane;
    
    // Timing location 
    @FXML ComboBox<AwardTimingPoint> timingPointComboBox;
    
    // Filters
    @FXML ToggleSwitch filterToggleSwitch;
    @FXML VBox filterVBox;
    @FXML TableView<AwardFilter> filterTableView;
    ObservableList<Pair<String,String>> customAttributesList = FXCollections.observableArrayList();
    ObservableList<String> customAttributesDisplayList = FXCollections.observableArrayList();
    @FXML TableColumn<AwardFilter,String> filterAttributeTableColumn;
    @FXML TableColumn<AwardFilter,String> filterTypeTableColumn;
    @FXML TableColumn<AwardFilter,String> filterReferenceValueTableColumn;
    @FXML Button filterAddButton;
    @FXML Button filterDeleteButton;
    
    // Groupings
    @FXML ToggleSwitch subdivideToggleSwitch;
    @FXML VBox subdivideVBox;
    @FXML ListView<CustomAttribute> subdivideListView;
    ObservableList<CustomAttribute> availableCustomAttributesList = FXCollections.observableArrayList(CustomAttribute.extractor());
    ObservableList<CustomAttribute> subdivideCustomAttributesList = FXCollections.observableArrayList(CustomAttribute.extractor());
    @FXML Button subdivideAddButton;
    @FXML Button subdivideDeleteButton;
    
    @FXML ToggleSwitch skewToggleSwitch;
    @FXML HBox skewControlHBox;
    @FXML ComboBox<String> skewOpComboBox;
    @FXML ComboBox<String> skewAttributeComboBox;
    ObservableList<String> skewableAttributes = FXCollections.observableArrayList();
    Map<String,Integer> skewableAttributesMap = new HashMap();
    
    
    ObservableList<AwardTimingPoint> availableTimingPointsList = FXCollections.observableArrayList(AwardTimingPoint.extractor());
    
    private final RaceDAO raceDAO = RaceDAO.getInstance();
    
    public void initialize() {
        // TODO
        System.out.println("FXMLAwardCategoryController initialized");
        awardTypeComboBox.setItems(FXCollections.observableArrayList(AwardCategoryType.values()));
        depthTypeComboBox.setItems(FXCollections.observableArrayList(AwardDepthType.values()));
    }    
    
    public void setAwardCategory(AwardCategory a){
        awardCategory = a;
        
        // Populate the existing values
        awardTitleTextField.textProperty().set(awardCategory.getName());
        chipToggleSwitch.setSelected(awardCategory.getChip());
        pullToggleSwitch.setSelected(awardCategory.getPull());
        
        visibleAwardsToggleSwitch.setSelected(awardCategory.getVisible());
        visibleOverallToggleSwitch.setSelected(awardCategory.getVisibleOverall());
        
        depthTypeComboBox.getSelectionModel().select(awardCategory.getDepthType());
        
        depthTextField.setTextFormatter(Formatters.integerFormatter());
        depthTextField.setText(awardCategory.getDepth().toString());
        
        TextFormatter<String> AGMformatter = new TextFormatter<>( change -> {
            change.setText(change.getText().replaceAll("[^0-9]", ""));
            return change; 
        });
        mastersAgeTextField.setTooltip(new Tooltip("Sets the starting age for the Masters categories."));  
        mastersAgeTextField.setTextFormatter(AGMformatter);
        
        mastersAgeTextField.setText(awardCategory.getMastersAge().toString());
        
//        @FXML TableColumn<AwardDepth,Integer> depthStartTableColumn;
//        @FXML TableColumn<AwardDepth,String> depthEndTableColumn;
//        @FXML TableColumn<AwardDepth,Integer> depthTableColumn;
        depthEndTableColumn.editableProperty().set(false);
        depthEndTableColumn.setCellValueFactory(value -> value.getValue().endCountProperty());
        
        depthStartTableColumn.setCellValueFactory(value -> value.getValue().startCountProperty().asObject());
        depthStartTableColumn.setCellFactory(col -> new IntegerEditingCell());
        depthStartTableColumn.setOnEditCommit(e -> {
            try {
                e.getRowValue().startCountProperty().setValue(e.getNewValue());
                awardCategory.recalcCustomDepths();
            } catch (Exception ex) {
                System.out.println("depthStartTableColumn.setOnEditCommit Oops....");
                e.getRowValue().startCountProperty().setValue(e.getOldValue());
            }
            
            raceDAO.updateAwardCategory(awardCategory);
        });
        
        depthTableColumn.setCellValueFactory(value -> value.getValue().depthProperty().asObject());
        depthTableColumn.setCellFactory(col -> new IntegerEditingCell());
        depthTableColumn.setOnEditCommit(e -> {
            try {
                e.getRowValue().depthProperty().setValue(e.getNewValue());
            } catch (Exception ex) {
                System.out.println("depthStartTableColumn.setOnEditCommit Oops....");
                e.getRowValue().depthProperty().setValue(e.getOldValue());
            }
            raceDAO.updateAwardCategory(awardCategory);
        });
        
        // Hide the custom box if we are not custom
        awardTypeComboBox.getSelectionModel().select(awardCategory.getType());
        switch (awardCategory.getType()) {
            case CUSTOM:
                customGridPane.setVisible(true);
                customGridPane.setManaged(true);
                mastersHBox.setVisible(false);
                mastersHBox.setManaged(false);
                break;
            case MASTERS:
                customGridPane.setVisible(false);
                customGridPane.setManaged(false);
                mastersHBox.setVisible(true);
                mastersHBox.setManaged(true);
                break;
            default:
                customGridPane.setVisible(false);
                customGridPane.setManaged(false);
                mastersHBox.setVisible(false);
                mastersHBox.setManaged(false);
                break;
        }
        
        // Hide the depth VBox if we are using a fixed award depth
        if (awardCategory.getDepthType().equals(AwardDepthType.FIXED)){
            depthVBox.setVisible(false);
            depthVBox.setManaged(false);
            depthTextField.setVisible(true);
            depthTextField.setManaged(true);        
        } else {
            depthVBox.setVisible(true);
            depthVBox.setManaged(true);
            depthTextField.setVisible(false);
            depthTextField.setManaged(false);
        }
        
        depthTableView.setItems(awardCategory.customDepthProperty());

        
        // Now set the controls up so that they update when things change
        awardTitleTextField.textProperty().addListener((obs, prevVal, newVal) -> {
            if (newVal != null && !newVal.isEmpty()){
                awardCategory.setName(newVal);
                raceDAO.updateAwardCategory(awardCategory);
            }
        });
        
        chipToggleSwitch.selectedProperty().addListener((obs,  prevVal,  newVal) -> {
             awardCategory.setChip(newVal);
             raceDAO.updateAwardCategory(awardCategory);
        });
        pullToggleSwitch.selectedProperty().addListener((obs,  prevVal,  newVal) -> {
             awardCategory.setPull(newVal);
             raceDAO.updateAwardCategory(awardCategory);
        });
        
        visibleAwardsToggleSwitch.selectedProperty().addListener((obs,  prevVal,  newVal) -> {
             awardCategory.setVisible(newVal);
             raceDAO.updateAwardCategory(awardCategory);
        });
        visibleOverallToggleSwitch.selectedProperty().addListener((obs,  prevVal,  newVal) -> {
             awardCategory.setVisibleOverall(newVal);
             raceDAO.updateAwardCategory(awardCategory);
        });
        
        awardTypeComboBox.getSelectionModel().selectedItemProperty().addListener((obs,  prevVal,  newVal) -> {
            switch (newVal) {
                case CUSTOM:
                    customGridPane.setVisible(true);
                    customGridPane.setManaged(true);
                    mastersHBox.setVisible(false);
                    mastersHBox.setManaged(false);
                    break;
                case MASTERS:
                    customGridPane.setVisible(false);
                    customGridPane.setManaged(false);
                    mastersHBox.setVisible(true);
                    mastersHBox.setManaged(true);
                    break;
                default:
                    customGridPane.setVisible(false);
                    customGridPane.setManaged(false);
                    mastersHBox.setVisible(false);
                    mastersHBox.setManaged(false);
                    break;
            }
            awardCategory.setType(newVal);
            raceDAO.updateAwardCategory(awardCategory);
        });
        
        depthTypeComboBox.getSelectionModel().selectedItemProperty().addListener((obs,  prevVal,  newVal) -> {
            awardCategory.setDepthType(newVal);
            if (AwardDepthType.FIXED.equals(awardCategory.getDepthType())){
                depthVBox.setVisible(false);
                depthVBox.setManaged(false);
                depthTextField.setVisible(true);
                depthTextField.setManaged(true);        
            } else {
                 if(awardCategory.customDepthProperty().isEmpty()){
                    AwardDepth ad = new AwardDepth();
                    ad.setDepth(3);
                    ad.setStartCount(1);
                    awardCategory.addCustomDepth(ad);
                }
                depthVBox.setVisible(true);
                depthVBox.setManaged(true);
                depthTextField.setVisible(false);
                depthTextField.setManaged(false);
            }
            
            raceDAO.updateAwardCategory(awardCategory);
        });
        
        depthDeleteButton.setOnAction(action -> {
            depthDelete();
        });
        depthAddButton.setOnAction(action -> {
            depthAdd();
        });
        //
        depthTextField.textProperty().addListener((obs, prevVal, newVal) -> {
            if (newVal != null && !newVal.isEmpty() ){
                try {
                    awardCategory.setDepth(Integer.parseUnsignedInt(newVal));
                    raceDAO.updateAwardCategory(awardCategory);
                } catch (Exception e) {
                    Platform.runLater(() -> {
                        depthTextField.textProperty().set(prevVal);
                    });
                }
            }
        });
        mastersAgeTextField.textProperty().addListener((obs, prevVal, newVal) -> {
            if (newVal != null && !newVal.isEmpty() ){
                try {
                    awardCategory.setMastersAge(Integer.parseUnsignedInt(newVal));
                    raceDAO.updateAwardCategory(awardCategory);
                } catch (Exception e) {
                    Platform.runLater(() -> {
                        mastersAgeTextField.textProperty().set(prevVal);
                    });
                }
            }
        });
        
        filterVBox.visibleProperty().bind(filterToggleSwitch.selectedProperty());
        filterVBox.managedProperty().bind(filterToggleSwitch.selectedProperty());
        
        filterToggleSwitch.setSelected(awardCategory.getFiltered());
        filterToggleSwitch.selectedProperty().addListener((obs,  prevVal,  newVal) -> {
             awardCategory.setFiltered(newVal);
             raceDAO.updateAwardCategory(awardCategory);
        });
        
        filterDeleteButton.setOnAction(action -> {
            filterDelete();
        });
        filterDeleteButton.disableProperty().bind(filterTableView.getSelectionModel().selectedItemProperty().isNull());
        filterAddButton.setOnAction(action -> {
            filterAdd();
        });
        
        rebuildAttributeLists();
        ParticipantDAO.getInstance().getCustomAttributes().addListener((ListChangeListener) listener -> {
            System.out.println("Custom Attributes changed...");
            rebuildAttributeLists();
        });
        
        filterAttributeTableColumn.setCellValueFactory(value -> {
            String key = value.getValue().attributeProperty().getValue();
            for(Pair<String,String> k: customAttributesList) {
                if (k.getKey().equals(key)) return new SimpleStringProperty(k.getValue());
            };
            return new SimpleStringProperty(key);
        });
        filterAttributeTableColumn.setCellFactory(ComboBoxTableCell.forTableColumn(customAttributesDisplayList));
        filterAttributeTableColumn.setOnEditCommit(e -> {
            String value = e.getNewValue();
            String key = e.getOldValue();
            for(Pair<String,String> k: customAttributesList) {
                if (k.getValue().equals(value)) key = k.getKey() ;
            };
            e.getRowValue().attributeProperty().setValue(key);
            raceDAO.updateAwardCategory(awardCategory);
        });
        
        filterTypeTableColumn.setCellValueFactory(value -> value.getValue().comparisonTypeProperty());
        filterTypeTableColumn.setCellFactory(ComboBoxTableCell.forTableColumn("=",">","<",">=","<=","!=","=~"));
        filterTypeTableColumn.setOnEditCommit(e -> {
            e.getRowValue().comparisonTypeProperty().setValue(e.getNewValue());
            raceDAO.updateAwardCategory(awardCategory);
        });
        filterReferenceValueTableColumn.setCellValueFactory(value -> value.getValue().referenceValueProperty());
        filterReferenceValueTableColumn.setCellFactory(TextFieldTableCell.forTableColumn());
        filterReferenceValueTableColumn.setOnEditCommit(e -> {
            e.getRowValue().referenceValueProperty().setValue(e.getNewValue());
            raceDAO.updateAwardCategory(awardCategory);
        });
        
        filterTableView.setItems(awardCategory.filtersProperty());
        filterTableView.setPlaceholder(new Label("No filters defined yet..."));
        
        subdivideToggleSwitch.setSelected(awardCategory.getSubdivided());
        subdivideToggleSwitch.selectedProperty().addListener((obs,  prevVal,  newVal) -> {
             awardCategory.setSubdivided(newVal);
             raceDAO.updateAwardCategory(awardCategory);
        });
        
        // build the subdivideCustomAttributesList
        awardCategory.subDivideProperty().forEach(s -> {});
        
        subdivideListView.setItems(subdivideCustomAttributesList);
        subdivideListView.setCellFactory(ComboBoxListCell.forListView(availableCustomAttributesList));
        
        
        awardCategory.subDivideProperty().forEach(s -> {
            availableCustomAttributesList.stream().filter((k) -> (k.key.getValue().equals(s))).forEachOrdered((k) -> {
                subdivideCustomAttributesList.add(k);
            });
        });
        subdivideVBox.visibleProperty().bind(subdivideToggleSwitch.selectedProperty());
        subdivideVBox.managedProperty().bind(subdivideToggleSwitch.selectedProperty());
        subdivideDeleteButton.setOnAction(action -> {
            subDelete();
        });
        subdivideDeleteButton.disableProperty().bind(subdivideListView.getSelectionModel().selectedItemProperty().isNull());
        subdivideAddButton.setOnAction(action -> {
            subAdd();
        });
        
        subdivideListView.setOnEditCommit((ListView.EditEvent<CustomAttribute> t) -> {
            System.out.println("setOnEditCommit " + t.getIndex());
            
            if(t.getIndex() >= 0 && t.getIndex() < t.getSource().getItems().size()) {
                CustomAttribute ca = t.getSource().getItems().get(t.getIndex()); 
                if (t.getNewValue().key.toString().isEmpty()) {
                    //we never saved this so just remove it
                    subdivideCustomAttributesList.remove(ca);
                } else {
                    // rebuild the backing list
                    
                    subdivideCustomAttributesList.remove(ca);
                    subdivideCustomAttributesList.add(t.getIndex(), t.getNewValue());
                    awardCategory.subDivideProperty().clear();
                    awardCategory.updateSubdivideList();
                    subdivideCustomAttributesList.forEach( cca -> {
                        if (cca.key.isEmpty().get()) return;
                        System.out.println("Current splitBy attribute: " + cca.key.getValueSafe());
                        awardCategory.subDivideProperty().add(cca.key.getValueSafe());
                    });
                    awardCategory.updateSubdivideList();
                    raceDAO.updateAwardCategory(awardCategory);
                }
            } else {
                System.out.println("Timing setOnEditCommit event out of index: " + t.getIndex());
            }
        });

        timingPointComboBox.setItems(availableTimingPointsList);
        
        rebuildTimingPointList();
        
        
        // rebuild the list when we add or remove splits or segments
        awardCategory.getRaceAward().getRace().splitsProperty().addListener((ListChangeListener) listener -> {rebuildTimingPointList();});
        awardCategory.getRaceAward().getRace().unsortedSegmentsProperty().addListener((ListChangeListener) listener -> {
            System.out.println("awardCategory: segments changed...");
            rebuildTimingPointList();
        });
        
        timingPointComboBox.getSelectionModel().selectedItemProperty().addListener((obs,  prevVal,  newVal) -> {
            if (newVal != null && !newVal.id.getValue().equals(awardCategory.getTimingPointID())) {
                awardCategory.setTimingPointID(newVal.id.get());
                awardCategory.setTimingPointType(newVal.type.get());
                raceDAO.updateAwardCategory(awardCategory);
            }
        });
        
        
        skewToggleSwitch.setSelected(awardCategory.getSkewed());
        skewToggleSwitch.selectedProperty().addListener((obs,  prevVal,  newVal) -> {
             awardCategory.setSkewed(newVal);
             raceDAO.updateAwardCategory(awardCategory);
        });
        
        skewOpComboBox.setItems(FXCollections.observableArrayList("+","-"));
        skewOpComboBox.getSelectionModel().select(awardCategory.getSkewType());
        skewOpComboBox.getSelectionModel().selectedItemProperty().addListener((obs,  prevVal,  newVal) -> {
            if (newVal != null && !newVal.equals(prevVal)) {
                awardCategory.setSkewType(newVal);
                raceDAO.updateAwardCategory(awardCategory);
            }
        });
        skewAttributeComboBox.setItems(skewableAttributes);
        skewableAttributesMap.keySet().forEach(key -> {
            if (skewableAttributesMap.get(key).equals(awardCategory.getSkewAttribute())) skewAttributeComboBox.getSelectionModel().select(key);
        });
        skewAttributeComboBox.getSelectionModel().selectedItemProperty().addListener((obs,  prevVal,  newVal) -> {
            if (newVal != null && !newVal.equals(skewableAttributesMap.get(newVal))) {
                awardCategory.setSkewAttribute(skewableAttributesMap.get(newVal));
                raceDAO.updateAwardCategory(awardCategory);
            }
        });
        skewToggleSwitch.visibleProperty().bind(Bindings.size(skewableAttributes).greaterThan(0));
        skewToggleSwitch.managedProperty().bind(Bindings.size(skewableAttributes).greaterThan(0));
        skewControlHBox.visibleProperty().bind(skewToggleSwitch.selectedProperty());
        skewControlHBox.managedProperty().bind(skewToggleSwitch.selectedProperty());
    }
    
    public void subAdd(){
        CustomAttribute ca = new CustomAttribute("","Select...");
        subdivideCustomAttributesList.add(ca);
        //subdivideListView.getSelectionModel().select(subdivideCustomAttributesList.indexOf(ca));
        //subdivideListView.edit(subdivideCustomAttributesList.indexOf(ca));
    }
    
    public void subDelete(){
        CustomAttribute ca = subdivideListView.getSelectionModel().getSelectedItem();
        subdivideCustomAttributesList.remove(ca);
        if (ca.key.isEmpty().get()) return;
        awardCategory.subDivideProperty().remove(ca.key.getValue());
        awardCategory.updateSubdivideList();
        raceDAO.updateAwardCategory(awardCategory);
    }
    
    public void upPriority(ActionEvent fxevent){
        if (awardCategory.getPriority()> 0) {
            awardCategory.getRaceAward().awardCategoriesProperty().remove(awardCategory);
            awardCategory.getRaceAward().awardCategoriesProperty().add(awardCategory.getPriority()-1, awardCategory);
            awardCategory.getRaceAward().recalcPriorities();
            awardCategory.getRaceAward().awardCategoriesProperty().forEach(a -> raceDAO.updateAwardCategory(a));
        }
    }
    public void lowerPriority(ActionEvent fxevent){
        awardCategory.getRaceAward().awardCategoriesProperty().remove(awardCategory);
        awardCategory.getRaceAward().awardCategoriesProperty().add(awardCategory.getPriority()+1, awardCategory);
        awardCategory.getRaceAward().recalcPriorities();
        awardCategory.getRaceAward().awardCategoriesProperty().forEach(a -> raceDAO.updateAwardCategory(a));

    }
    
    public void filterAdd(){
        awardCategory.addFilter(new AwardFilter("","=",""));
        raceDAO.updateAwardCategory(awardCategory);
    }
    
    public void filterDelete(){
        awardCategory.deleteFilter(filterTableView.getSelectionModel().getSelectedItem());
        raceDAO.updateAwardCategory(awardCategory);
    }
    
    public void removeAward(ActionEvent fxevent){
        awardCategory.getRaceAward().removeAwardCategory(awardCategory);
        raceDAO.removeAwardCategory(awardCategory);
    }
    
    public void depthAdd(){
        AwardDepth ad = new AwardDepth();
        int size = awardCategory.getCustomDepthList().size();
        if (size>1) {
            Integer p1 = awardCategory.getCustomDepthList().get(size-2).getStartCount();
            Integer p2 = awardCategory.getCustomDepthList().get(size-1).getStartCount();
            if (p1 == 1) p1=0;
            ad.setStartCount(p2 + p2-p1);
            ad.setDepth(awardCategory.getCustomDepthList().get(size-1).getDepth());
        } else if (size == 1) {
            Integer p1 = awardCategory.getCustomDepthList().get(size-1).getStartCount();
            if (p1 == 1) p1=0;
            ad.setStartCount(p1 + 10);
            ad.setDepth(awardCategory.getCustomDepthList().get(size-1).getDepth());
        } else {
            ad.setDepth(3);
            ad.setStartCount(1);
        }
        
        awardCategory.addCustomDepth(ad);
        raceDAO.updateAwardCategory(awardCategory);
    }
    public void depthDelete(){
        awardCategory.removeCustomDepth(depthTableView.getSelectionModel().getSelectedItem());
        raceDAO.updateAwardCategory(awardCategory);
    }
    
    private void rebuildTimingPointList(){
        availableTimingPointsList.clear();
        
        // Finish line
        AwardTimingPoint finish = new AwardTimingPoint("Finish","FINISH",0);
        availableTimingPointsList.setAll(finish);
        Race r = awardCategory.getRaceAward().getRace();
        // Splits
        r.getSplits().forEach(s -> {
            System.out.println("AwardCategoryController: rebuildTimingPointList: split " + s.getSplitName() + " -> " + s.getPosition());
            if (s.getPosition() == 1 ) return; // Skip the start
            if (s.getPosition() == r.getSplits().size()) return;  // Skip the finish
            availableTimingPointsList.add(new AwardTimingPoint(s.splitNameProperty(),"SPLIT",s.getID()));
        });
        // Segments
        r.getSegments().forEach(s ->{
            System.out.println("AwardCategoryController: rebuildTimingPointList: Segment " + s.getSegmentName()+ " -> " + s.getID());
            availableTimingPointsList.add(new AwardTimingPoint(s.segmentNameProperty(),"SEGMENT",s.getID()));
        });
        
        AwardTimingPoint tmpTp = new AwardTimingPoint("",awardCategory.getTimingPointType(),awardCategory.getTimingPointID());
        availableTimingPointsList.forEach(tp -> {
            if (tp.equals(tmpTp)) timingPointComboBox.getSelectionModel().select(tp);
        });
    }
    
    private void rebuildAttributeLists(){
        availableCustomAttributesList.clear();
        availableCustomAttributesList.add(new CustomAttribute("AG","Age Group"));
        
        customAttributesList.clear();
        customAttributesDisplayList.clear();
        customAttributesList.add(new Pair("AG","Age Group"));
        customAttributesDisplayList.add("Age Group");
        
        skewableAttributes.clear();
        skewableAttributesMap.clear();
        
        Participant.getAvailableAttributes().keySet().stream().sorted().forEach(k -> {
            customAttributesList.add(new Pair(k,Participant.getAvailableAttributes().get(k)));
            customAttributesDisplayList.add(Participant.getAvailableAttributes().get(k));
            availableCustomAttributesList.add(new CustomAttribute(k,Participant.getAvailableAttributes().get(k)));
        });
        ParticipantDAO.getInstance().getCustomAttributes().forEach(ca -> {
            customAttributesList.add(new Pair(ca.getID().toString(),ca.getName()));
            availableCustomAttributesList.add(new CustomAttribute(ca.getID().toString(),ca.nameProperty()));
            customAttributesDisplayList.add(ca.getName());
            if (ca.getAttributeType().equals(TIME)) {
                skewableAttributes.add(ca.getName());
                skewableAttributesMap.put(ca.getName(), ca.getID());
            };
        });
        
        skewableAttributesMap.keySet().forEach(key -> {
            if (skewableAttributesMap.get(key).equals(awardCategory.getSkewAttribute())) skewAttributeComboBox.getSelectionModel().select(key);
        });
    }

    private static class AwardTimingPoint{
        StringProperty name = new SimpleStringProperty("");
        StringProperty type = new SimpleStringProperty("FINISH");
        IntegerProperty id = new SimpleIntegerProperty(0);
        
        public AwardTimingPoint() {
            
        }
        public AwardTimingPoint(String n, String t, Integer i){
            name.setValue(n);
            type.setValue(t);
            id.setValue(i);
        }
    
        public AwardTimingPoint(StringProperty n, String t, Integer i){
            name = n;
            type.setValue(t);
            id.setValue(i);
        }
        
        @Override
        public String toString(){
            return name.getValueSafe();
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 73 * hash + Objects.hashCode(this.type.get());
            hash = 73 * hash + Objects.hashCode(this.id.get());
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final AwardTimingPoint other = (AwardTimingPoint) obj;
            if (!Objects.equals(this.type.get(), other.type.get())) {
                return false;
            }
            if (!Objects.equals(this.id.get(), other.id.get())) {
                return false;
            }
            return true;
        }
        
        public static Callback<AwardTimingPoint, Observable[]> extractor() {
            return (AwardTimingPoint ca) -> new Observable[]{ca.name};
        }
    
    }
    
    private static class CustomAttribute {
        StringProperty name = new SimpleStringProperty("");
        StringProperty key = new SimpleStringProperty("");
        
        public CustomAttribute() {
        }
        public CustomAttribute(String k, String v){
            name.setValue(v);
            key.setValue(k);
        }
        public CustomAttribute(String k, StringProperty v){
            name = v;
            key.setValue(k);
        }
        
        @Override
        public String toString(){
            return name.getValueSafe();
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 59 * hash + Objects.hashCode(this.key);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final CustomAttribute other = (CustomAttribute) obj;
            if (!Objects.equals(this.key, other.key)) {
                return false;
            }
            return true;
        }
        
        
        public static Callback<CustomAttribute, Observable[]> extractor() {
            return (CustomAttribute ca) -> new Observable[]{ca.name};
        }
        
    }
    
}