/*
 * Copyright (C) 2018-2021, 2023 Andrew Gegg
 *
 *	This file is part of the Garden Notebook application
 *
 * The Garden Notebook application 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/>.
 */

/*
	Change log
	2.1.0   Better preset date range options than just 'This year'
            Pre-load varieties to reduce numerous small DB reads
    2.3.0   Purchase selection by product
    2.5.0   Support for Sales
    2.6.1   Fixed bug whereby empty Purchases were not shown if there are no Brands
    3.0.0	Support for Locations
    		Support for Journals
	3.0.1	Handle date range selection better
			Bodge fix for JavaFX bug which can't handle short form years
	3.0.2	Use Date::commitValue() (new in 18)
	3.0.3	Add Copy Date button
    3.1.0   Use SafeDatePicker
    3.1.2	Quick search for plant species
 */

package uk.co.gardennotebook;

import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.*;
import javafx.scene.control.cell.*;
import javafx.scene.text.Text;
import uk.co.gardennotebook.fxbean.*;
import uk.co.gardennotebook.spi.IPlantSpecies;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.layout.AnchorPane;
import javafx.util.StringConverter;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;
import uk.co.gardennotebook.spi.GNDBException;


/**
 * Controller class for the Diary Selection tab.
 *
 * @implNote
*	tgbPlantsClearAllOnAction bug fix for version 1.1.1

* @author Andy Gegg
*	@version	3.1.2
*	@since	1.0
 */
final class DiarySelector extends AnchorPane implements INotebookLoadable
{
	private static final Logger LOGGER = LogManager.getLogger();

	@FXML
	private SafeDatePicker dtpFrom;
	@FXML
	private SafeDatePicker dtpTo;
	@FXML
	private Button btnFetch;

	private Consumer<Node> loadSplit;
	private Consumer<Node> clearSplit;
	private BiConsumer<String, Node> loadTab;
	private Consumer<Node> clearTab;

	// Husbandry pane
	@FXML
	private TitledPane tpHusbandry;
	@FXML
	private ToggleButton tgbHusbandrySelectAll;
	@FXML
	private ToggleButton tgbHusbandryClearAll;
	@FXML
	private TableView<HusbandryClassBean> lvHusbandryClass;
	@FXML
	private TableColumn<HusbandryClassBean,?> lvHusbandryClassCheck;
	@FXML
	private TableColumn<HusbandryClassBean,String> lvHusbandryClassName;
	@FXML
	private TableColumn<HusbandryClassBean,String> lvHusbandryClassDesc;
	private List<HusbandryClassBean> husbandryClasses;
	private Map<HusbandryClassBean, SimpleBooleanProperty> husbandryClassSelection;
	// will be true if all items are selected
	private BooleanBinding areAllHusbandryClassOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	// will be true if no item is selected
	private BooleanBinding areAllHusbandryClassOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));
		
	// GroundworkActivity pane
	@FXML
	private ToggleButton tgbGWASelectAll;
	@FXML
	private ToggleButton tgbGWAClearAll;
	@FXML
	private TableView<GroundworkActivityBean> lvGroundworkActivity;
	@FXML
	private TableColumn<GroundworkActivityBean,?> lvGroundworkActivityCheck;
	@FXML
	private TableColumn<GroundworkActivityBean,String> lvGroundworkActivityName;
	@FXML
	private TableColumn<GroundworkActivityBean,String> lvGroundworkActivityDesc;
	private List<GroundworkActivityBean> groundworkActivities;
	private Map<GroundworkActivityBean, SimpleBooleanProperty> groundworkActivitySelection;
	// will be true if all items are selected
	private BooleanBinding areAllGWAOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	// will be true if no item is selected
	private BooleanBinding areAllGWAOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));

	// Pests & Diseases pane
	@FXML
	private ToggleButton tgbAfflictionSelectAll;
	@FXML
	private ToggleButton tgbAfflictionClearAll;
	@FXML
	private TableView<AfflictionBean> lvAffliction;
	@FXML
	private TableColumn<AfflictionBean,?> lvAfflictionCheck;
	@FXML
	private TableColumn<AfflictionBean,String> lvAfflictionName;
	@FXML
	private TableColumn<AfflictionBean,String> lvAfflictionDesc;
	private List<AfflictionBean> afflictions;
	private Map<AfflictionBean, SimpleBooleanProperty> afflictionSelection;
	// will be true if all items are selected
	private BooleanBinding areAllAfflictionOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	// will be true if no item is selected
	private BooleanBinding areAllAfflictionOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));

	// WeatherCondition pane
	@FXML
	private ToggleButton tgbWeatherSelectAll;
	@FXML
	private ToggleButton tgbWeatherClearAll;
	@FXML
	private Button btnWeatherAdd;
	@FXML
	private TableView<WeatherConditionBean> lvWeather;
	@FXML
	private TableColumn<WeatherConditionBean,?> lvWeatherCheck;
	@FXML
	private TableColumn<WeatherConditionBean,String> lvWeatherName;
	@FXML
	private TableColumn<WeatherConditionBean,String> lvWeatherDesc;
	private List<WeatherConditionBean> weatherConditions;
	private Map<WeatherConditionBean, SimpleBooleanProperty> weatherConditionSelection;
	// will be true if all items are selected
	private BooleanBinding areAllWeatherOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	// will be true if no item is selected
	private BooleanBinding areAllWeatherOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));

	// Purchases - multiple sub panes in a tabbed pane
	@FXML
	private ToggleButton tgbPurchasesSelectAll;
	@FXML
	private ToggleButton tgbPurchasesClearAll;
    
    //  retailers
	@FXML
	private ToggleButton tgbRetailersSelectAll;
	@FXML
	private ToggleButton tgbRetailersClearAll;
	@FXML
	private TableView<RetailerBean> lvRetailers;
	@FXML
	private TableColumn<RetailerBean,?> lvRetailersCheck;
	@FXML
	private TableColumn<RetailerBean,String> lvRetailersName;
	@FXML
	private TableColumn<RetailerBean,String> lvRetailersDesc;
	private List<RetailerBean> retailers;
	private Map<RetailerBean, SimpleBooleanProperty> retailersSelection;
	// will be true if all items are selected
	private BooleanBinding areAllRetailersOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	// will be true if no item is selected
	private BooleanBinding areAllRetailersOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));

    //  product categories
	@FXML
	private ToggleButton tgbProdCatsSelectAll;
	@FXML
	private ToggleButton tgbProdCatsClearAll;
	@FXML
	private ToggleButton tgbProdCatsPlantLike;
	@FXML
	private TableView<ProductCategoryBean> lvProdCats;
	@FXML
	private TableColumn<ProductCategoryBean,?> lvProdCatsCheck;
	@FXML
	private TableColumn<ProductCategoryBean,String> lvProdCatsName;
	@FXML
	private TableColumn<ProductCategoryBean, Boolean> lvProdCatsPlantLike;
	@FXML
	private TableColumn<ProductCategoryBean,String> lvProdCatsDesc;
	private List<ProductCategoryBean> productCategories;
	private Map<ProductCategoryBean, SimpleBooleanProperty> prodCatsSelection;
	// will be true if all items are selected
	private BooleanBinding areAllProdCatsOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	// will be true if no item is selected
	private BooleanBinding areAllProdCatsOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));
	
    //  product brands
	@FXML
	private ToggleButton tgbProdBrandsSelectAll;
	@FXML
	private ToggleButton tgbProdBrandsClearAll;
	@FXML
	private TableView<ProductBrandBean> lvProdBrands;
	@FXML
	private TableColumn<ProductBrandBean,?> lvProdBrandsCheck;
	@FXML
	private TableColumn<ProductBrandBean,String> lvProdBrandsName;
	@FXML
	private TableColumn<ProductBrandBean,String> lvProdBrandsDesc;
	private List<ProductBrandBean> productBrands;
	private Map<ProductBrandBean, SimpleBooleanProperty> prodBrandsSelection;
	// will be true if all items are selected
	private BooleanBinding areAllProdBrandsOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	// will be true if no item is selected
	private BooleanBinding areAllProdBrandsOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));
	
	// Wildlife - there's no selection other than 'all' or 'none'
	@FXML
	private ToggleButton tgbWildlifeSelectAll;
	@FXML
	private ToggleButton tgbWildlifeClearAll;
	@FXML
	private TableView<WildlifeSpeciesBean> lvWildlife;
	@FXML
	private TableColumn<WildlifeSpeciesBean,?> lvWildlifeCheck;
	@FXML
	private TableColumn<WildlifeSpeciesBean,String> lvWildlifeName;
	@FXML
	private TableColumn<WildlifeSpeciesBean,String> lvWildlifeDesc;
	private List<WildlifeSpeciesBean> wildlifeSpecies;
	private Map<WildlifeSpeciesBean, SimpleBooleanProperty> wildlifeSpeciesSelection;
//	// will be true if all items are selected
	private BooleanBinding areAllWildlifeOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
//	// will be true if no item is selected
	private BooleanBinding areAllWildlifeOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));

	// Sales - there's no selection other than 'all' or 'none'
	@FXML
	private ToggleButton tgbSalesSelectAll;
	@FXML
	private ToggleButton tgbSalesClearAll;
//	@FXML
//	private Button btnSaleAdd;
//	// will be true if all items are selected
//	private BooleanBinding areAllSalesOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	private BooleanBinding areAllSalesOn;// = Bindings.not(tgbSalesClearAll.selectedProperty());
//	// will be true if no item is selected
//	private BooleanBinding areAllSalesOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));
	private BooleanBinding areAllSalesOff;// = Bindings.not(tgbSalesSelectAll.selectedProperty());

	// Journal - there's no selection other than 'all' or 'none'
	@FXML
	private ToggleButton tgbJournalSelectAll;
	@FXML
	private ToggleButton tgbJournalClearAll;
	//	@FXML
//	private Button btnJournalAdd;
//	// will be true if all items are selected
//	private BooleanBinding areAllJournalOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	private BooleanBinding areAllJournalOn;// = Bindings.not(tgbSalesClearAll.selectedProperty());
	//	// will be true if no item is selected
//	private BooleanBinding areAllJournalOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));
	private BooleanBinding areAllJournalOff;// = Bindings.not(tgbSalesSelectAll.selectedProperty());

	// Plants pane
	@FXML
	private ToggleButton tgbPlantsSelectAll;
	@FXML
	private ToggleButton tgbPlantsClearAll;
	@FXML
	private TreeView<PlantCatalogueBean> tvPlants;
	private List<IPlantSpecies> plantSpecies;
//	private Map<IPlantSpecies, SimpleBooleanProperty> plantSpeciesSelection;
//	// will be true if all items are selected
//	private BooleanBinding areAllPlantsOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
//	// will be true if no item is selected
//	private BooleanBinding areAllPlantsOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));

		//Locations pane
	@FXML
	private ToggleButton tgbLocationSelectAll;
	@FXML
	private ToggleButton tgbLocationsClearAll;
	@FXML
	private TreeTableView<LocationBean> tvLocations;
	@FXML
	private TreeTableColumn<LocationBean, String>tvLocationsName;
	@FXML
	private TreeTableColumn<LocationBean, Boolean> tvLocationsCheck;
	@FXML
	private TreeTableColumn<LocationBean, String> tvLocationsDesc;
	// the Integer is the DB key for the Location
	private Map<Integer, SimpleBooleanProperty> locationsSelection;
//	private Map<Integer, TreeItem<LocationBean>> locationsCellIndex;
	//	// will be true if all items are selected
	private BooleanBinding areAllLocationsOn = Bindings.and(new SimpleBooleanProperty(true), new SimpleBooleanProperty(true));
	//	// will be true if no item is selected
	private BooleanBinding areAllLocationsOff = Bindings.or(new SimpleBooleanProperty(false), new SimpleBooleanProperty(false));

	// will be true if nothing is selected.  This bound to the DISabled property of the Fetch button (too many not's...)
	// NB cannot bind until all the bound-to bindings are fully defined
	private BooleanBinding isNothingSelected;
	
	@FXML
	private ResourceBundle resources;
	
	DiarySelector()
	{
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/DiarySelector.fxml"),
			ResourceBundle.getBundle("notebook"));
        fxmlLoader.setRoot(this);
        fxmlLoader.setController(this);

        try {
            fxmlLoader.load();
        } catch (IOException exception) {
            throw new RuntimeException(exception);
        }
	}
	
	/**
	 * Initializes the controller class.
	 */
	@FXML
	public void initialize()
	{
		//let user clear the date selection fields
		dtpFrom.getEditor().textProperty().addListener((observable, oldValue, newValue) -> {
			if(newValue.isBlank()) { dtpFrom.setValue(null); }
		});
		//  3.0.1
		//  if user tabs out after editing, make sure the DatePicker updates
//		dtpFrom.getEditor().focusedProperty().addListener((obj, wasFocused, isFocused)->{
//			if (wasFocused && !isFocused)
//			{
//				try
//				{
//					dtpFrom.commitValue();
//				} catch (DateTimeParseException e) {
//					dtpFrom.getEditor().setText(dtpFrom.getConverter().toString(dtpFrom.getValue()));
//				}
//			}
//		});
		dtpTo.getEditor().textProperty().addListener((observable, oldValue, newValue) -> {
			if(newValue.isBlank()) { dtpTo.setValue(null); }
		});
		//  3.0.1
		//  if user tabs out after editing, make sure the DatePicker updates
//		dtpTo.getEditor().focusedProperty().addListener((obj, wasFocused, isFocused)->{
//			if (wasFocused && !isFocused)
//			{
//				try
//				{
//					dtpTo.commitValue();
//				} catch (DateTimeParseException e) {
//					dtpTo.getEditor().setText(dtpTo.getConverter().toString(dtpTo.getValue()));
//				}
//			}
//		});

		// set up the husbandry pane
		loadHusbandryPane();
		
		// set up the groundwork pane
		loadGroundworkPane();
		
		// set up the pests & diseases pane
		loadAfflictionPane();
		
		// set up the weather pane
		loadWeatherPane();

		// set up the purchases pane
		loadPurchasePane();

		// set up the wildlife pane
		loadWildlifePane();

		// set up the sales pane
		loadSalesPane();

		// set up the Journal pane
		loadJournalPane();

		isNothingSelected = Bindings.and(areAllHusbandryClassOff, areAllGWAOff).
                                        and(areAllAfflictionOff).
                                        and(areAllWeatherOff).
                                        and(areAllRetailersOff).
                                        and(areAllProdCatsOff).
                                        and(areAllProdBrandsOff).
                                        and(areAllWildlifeOff).
                                        and(areAllSalesOff).
										and(areAllJournalOff);
		btnFetch.disableProperty().bind(isNothingSelected);

		// set up the plants pane
		loadPlantPane();

		// set up the locations pane
		loadLocationPane();
	}

	private void loadHusbandryPane()
	{
		try {
			husbandryClasses = HusbandryClassBean.fetchAll();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		husbandryClassSelection = new HashMap<>(husbandryClasses.size());
		for (HusbandryClassBean gw : husbandryClasses) 
		{
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(false);
			husbandryClassSelection.put(gw, sbp);
			areAllHusbandryClassOn = areAllHusbandryClassOn.and(sbp);
			areAllHusbandryClassOff = areAllHusbandryClassOff.or(sbp);
		}
		areAllHusbandryClassOff = areAllHusbandryClassOff.not();	
		lvHusbandryClass.getItems().setAll(husbandryClasses);
		lvHusbandryClass.setColumnResizePolicy(NotebookResizer.using(lvHusbandryClass));
		areAllHusbandryClassOn.addListener((ob, prev, curr) -> tgbHusbandrySelectAll.setSelected(curr));
		areAllHusbandryClassOff.addListener((ob, prev, curr) -> tgbHusbandryClearAll.setSelected(curr));

		lvHusbandryClassCheck.setCellFactory(c -> new CheckBoxTableCell<>(ix -> {
					return husbandryClassSelection.get(lvHusbandryClass.getItems().get(ix));
				}
			)
		);
		lvHusbandryClassName.setCellValueFactory(c -> c.getValue().nameProperty());
		lvHusbandryClassName.setCellFactory(TextFieldTableCell.forTableColumn());
		lvHusbandryClassDesc.setCellValueFactory(c -> c.getValue().descriptionProperty());
		lvHusbandryClassDesc.setCellFactory(TextFieldTableCell.forTableColumn());
	}

	private void loadGroundworkPane()
	{
		try {
			groundworkActivities = GroundworkActivityBean.fetchAll();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		groundworkActivitySelection = new HashMap<>(groundworkActivities.size());
		for (GroundworkActivityBean gw : groundworkActivities) 
		{
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(false);
			groundworkActivitySelection.put(gw, sbp);
			areAllGWAOn = areAllGWAOn.and(sbp);
			areAllGWAOff = areAllGWAOff.or(sbp);
		}
		areAllGWAOff = areAllGWAOff.not();
		lvGroundworkActivity.getItems().setAll(groundworkActivities);
		areAllGWAOn.addListener((ob, prev, curr) -> tgbGWASelectAll.setSelected(curr));
		areAllGWAOff.addListener((ob, prev, curr) -> tgbGWAClearAll.setSelected(curr));

		lvGroundworkActivity.setColumnResizePolicy(NotebookResizer.using(lvGroundworkActivity));
		lvGroundworkActivityCheck.setCellFactory(c -> new CheckBoxTableCell<>(ix -> {
					return groundworkActivitySelection.get(lvGroundworkActivity.getItems().get(ix));
				}
			)
		);
		lvGroundworkActivityName.setCellValueFactory(c -> c.getValue().nameProperty());
		lvGroundworkActivityName.setCellFactory(TextFieldTableCell.forTableColumn());
		lvGroundworkActivityDesc.setCellValueFactory(c -> c.getValue().descriptionProperty());
		lvGroundworkActivityDesc.setCellFactory(TextFieldTableCell.forTableColumn());
	}

	private void loadAfflictionPane()
	{
		try {
			afflictions = AfflictionBean.fetchAll();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		afflictionSelection = new HashMap<>(afflictions.size());
		for (AfflictionBean gw : afflictions) 
		{
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(false);
			afflictionSelection.put(gw, sbp);
			areAllAfflictionOn = areAllAfflictionOn.and(sbp);
			areAllAfflictionOff = areAllAfflictionOff.or(sbp);
		}
		areAllAfflictionOff = areAllAfflictionOff.not();
		lvAffliction.getItems().setAll(afflictions);
		areAllAfflictionOn.addListener((ob, prev, curr) -> tgbAfflictionSelectAll.setSelected(curr));
		areAllAfflictionOff.addListener((ob, prev, curr) -> tgbAfflictionClearAll.setSelected(curr));
		
		lvAffliction.setColumnResizePolicy(NotebookResizer.using(lvAffliction));
		lvAfflictionCheck.setCellFactory(c -> new CheckBoxTableCell<>(ix -> {
					return afflictionSelection.get(lvAffliction.getItems().get(ix));
				}
			)
		);
		lvAfflictionName.setCellValueFactory(c -> c.getValue().nameProperty());
		lvAfflictionName.setCellFactory(TextFieldTableCell.forTableColumn());
		lvAfflictionDesc.setCellValueFactory(c -> c.getValue().descriptionProperty());
		lvAfflictionDesc.setCellFactory(TextFieldTableCell.forTableColumn());
	}

	private void loadWeatherPane()
	{
		try {
			weatherConditions = WeatherConditionBean.fetchAll();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		weatherConditionSelection = new HashMap<>(weatherConditions.size());
		for (WeatherConditionBean gw : weatherConditions) 
		{
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(false);
			weatherConditionSelection.put(gw, sbp);
			areAllWeatherOn = areAllWeatherOn.and(sbp);
			areAllWeatherOff = areAllWeatherOff.or(sbp);
		}
		areAllWeatherOff = areAllWeatherOff.not();
		lvWeather.getItems().setAll(weatherConditions);
		areAllWeatherOn.addListener((ob, prev, curr) -> tgbWeatherSelectAll.setSelected(curr));
		areAllWeatherOff.addListener((ob, prev, curr) -> tgbWeatherClearAll.setSelected(curr));
		
		lvWeather.setColumnResizePolicy(NotebookResizer.using(lvWeather));
		lvWeatherCheck.setCellFactory(c -> new CheckBoxTableCell<>(ix -> {
					return weatherConditionSelection.get(lvWeather.getItems().get(ix));
				}
			)
		);
		lvWeatherName.setCellValueFactory(c -> c.getValue().nameProperty());
		lvWeatherName.setCellFactory(TextFieldTableCell.forTableColumn());
		lvWeatherDesc.setCellValueFactory(c -> c.getValue().descriptionProperty());
		lvWeatherDesc.setCellFactory(TextFieldTableCell.forTableColumn());
	}

    private void loadPurchasePane()
    {
        loadRetailerPane();
        loadProductCategoryPane();
        loadProductBrandPane();
        
        //  if all retailers, and all categories and all brands are selected, all purchases are selected
		areAllRetailersOn.addListener((ob, prev, curr) -> tgbPurchasesSelectAll.setSelected(areAllRetailersOn.get() && areAllProdCatsOn.get() && areAllProdBrandsOn.get()));
		areAllProdCatsOn.addListener((ob, prev, curr) -> tgbPurchasesSelectAll.setSelected(areAllRetailersOn.get() && areAllProdCatsOn.get() && areAllProdBrandsOn.get()));
		areAllProdBrandsOn.addListener((ob, prev, curr) -> tgbPurchasesSelectAll.setSelected(areAllRetailersOn.get() && areAllProdCatsOn.get() && areAllProdBrandsOn.get()));

        //  every purchase must have a retailer, every purchase item must have a product which must have a category
        //  so if no retailer is selected, there are no purchases
        //  and if no category is selected there are no items so no purchases
		areAllRetailersOff.addListener((ob, prev, curr) -> tgbPurchasesClearAll.setSelected(areAllRetailersOff.get() || areAllProdCatsOff.get()));
		areAllProdCatsOff.addListener((ob, prev, curr) -> tgbPurchasesClearAll.setSelected(areAllRetailersOff.get() || areAllProdCatsOff.get()));
    }
    
	private void loadRetailerPane()
	{
		try {
			retailers = RetailerBean.fetchAll();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		retailersSelection = new HashMap<>(retailers.size());
		for (RetailerBean gw : retailers) 
		{
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(false);
			retailersSelection.put(gw, sbp);
			areAllRetailersOn = areAllRetailersOn.and(sbp);
			areAllRetailersOff = areAllRetailersOff.or(sbp);
		}
		areAllRetailersOff = areAllRetailersOff.not();
		lvRetailers.getItems().setAll(retailers);
		areAllRetailersOn.addListener((ob, prev, curr) -> tgbRetailersSelectAll.setSelected(curr));
		areAllRetailersOff.addListener((ob, prev, curr) -> tgbRetailersClearAll.setSelected(curr));
		
		lvRetailers.setColumnResizePolicy(NotebookResizer.using(lvRetailers));
		lvRetailersCheck.setCellFactory(c -> new CheckBoxTableCell<>(ix -> {
					return retailersSelection.get(lvRetailers.getItems().get(ix));
				}
			)
		);
		lvRetailersName.setCellValueFactory(c -> c.getValue().nameProperty());
		lvRetailersName.setCellFactory(TextFieldTableCell.forTableColumn());
		lvRetailersDesc.setCellValueFactory(c -> c.getValue().descriptionProperty());
		lvRetailersDesc.setCellFactory(TextFieldTableCell.forTableColumn());
	}

	private void loadProductCategoryPane()
	{
		try {
			productCategories = ProductCategoryBean.fetchAll();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		prodCatsSelection = new HashMap<>(productCategories.size());
		for (ProductCategoryBean gw : productCategories) 
		{
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(false);
			prodCatsSelection.put(gw, sbp);
			areAllProdCatsOn = areAllProdCatsOn.and(sbp);
			areAllProdCatsOff = areAllProdCatsOff.or(sbp);
		}
		areAllProdCatsOff = areAllProdCatsOff.not();
		lvProdCats.getItems().setAll(productCategories);
		areAllProdCatsOn.addListener((ob, prev, curr) -> tgbProdCatsSelectAll.setSelected(curr));
		areAllProdCatsOff.addListener((ob, prev, curr) -> tgbProdCatsClearAll.setSelected(curr));
		
		lvProdCats.setColumnResizePolicy(NotebookResizer.using(lvProdCats));
		lvProdCatsCheck.setCellFactory(c -> new CheckBoxTableCell<>(ix -> {
					return prodCatsSelection.get(lvProdCats.getItems().get(ix));
				}
			)
		);
		lvProdCatsName.setCellValueFactory(c -> c.getValue().nameProperty());
		lvProdCatsName.setCellFactory(TextFieldTableCell.forTableColumn());
		lvProdCatsDesc.setCellValueFactory(c -> c.getValue().descriptionProperty());
		lvProdCatsDesc.setCellFactory(TextFieldTableCell.forTableColumn());
		lvProdCatsPlantLike.setCellFactory(CheckBoxTableCell.forTableColumn(lvProdCatsPlantLike));
		lvProdCatsPlantLike.setCellValueFactory(cdf -> cdf.getValue().plantLikeProperty());
	}

	private void loadProductBrandPane()
	{
		try {
			productBrands = ProductBrandBean.fetchAll();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		prodBrandsSelection = new HashMap<>(productBrands.size());
		for (ProductBrandBean gw : productBrands) 
		{
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(false);
			prodBrandsSelection.put(gw, sbp);
			areAllProdBrandsOn = areAllProdBrandsOn.and(sbp);
			areAllProdBrandsOff = areAllProdBrandsOff.or(sbp);
		}
		areAllProdBrandsOff = areAllProdBrandsOff.not();
		lvProdBrands.getItems().setAll(productBrands);
		areAllProdBrandsOn.addListener((ob, prev, curr) -> tgbProdBrandsSelectAll.setSelected(curr));
		areAllProdBrandsOff.addListener((ob, prev, curr) -> tgbProdBrandsClearAll.setSelected(curr));
//        LOGGER.debug("DiarySelector: loadProductBrandPane(): areAllProdBrandsOn: {}, areAllProdBrandsOff: {}", areAllProdBrandsOn.get(), areAllProdBrandsOff.get());
		
		lvProdBrands.setColumnResizePolicy(NotebookResizer.using(lvProdBrands));
		lvProdBrandsCheck.setCellFactory(c -> new CheckBoxTableCell<>(ix -> {
					return prodBrandsSelection.get(lvProdBrands.getItems().get(ix));
				}
			)
		);
		lvProdBrandsName.setCellValueFactory(c -> c.getValue().nameProperty());
		lvProdBrandsName.setCellFactory(TextFieldTableCell.forTableColumn());
		lvProdBrandsDesc.setCellValueFactory(c -> c.getValue().descriptionProperty());
		lvProdBrandsDesc.setCellFactory(TextFieldTableCell.forTableColumn());
	}

	private void loadWildlifePane()
	{
		try {
			wildlifeSpecies = WildlifeSpeciesBean.fetchAll();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		wildlifeSpeciesSelection = new HashMap<>(wildlifeSpecies.size());
		for (WildlifeSpeciesBean gw : wildlifeSpecies) 
		{
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(false);
			wildlifeSpeciesSelection.put(gw, sbp);
			areAllWildlifeOn = areAllWildlifeOn.and(sbp);
			areAllWildlifeOff = areAllWildlifeOff.or(sbp);
		}
		areAllWildlifeOff = areAllWildlifeOff.not();
		lvWildlife.getItems().setAll(wildlifeSpecies);
		areAllWildlifeOn.addListener((ob, prev, curr) -> tgbWildlifeSelectAll.setSelected(curr));
		areAllWildlifeOff.addListener((ob, prev, curr) -> tgbWildlifeClearAll.setSelected(curr));
		
		lvWildlife.setColumnResizePolicy(NotebookResizer.using(lvWildlife));
		lvWildlifeCheck.setCellFactory(c -> new CheckBoxTableCell<>(ix -> {
					return wildlifeSpeciesSelection.get(lvWildlife.getItems().get(ix));
				}
			)
		);
		lvWildlifeName.setCellValueFactory(c -> c.getValue().nameProperty());
		lvWildlifeName.setCellFactory(TextFieldTableCell.forTableColumn());
		lvWildlifeDesc.setCellValueFactory(c -> c.getValue().descriptionProperty());
		lvWildlifeDesc.setCellFactory(TextFieldTableCell.forTableColumn());
	}
    
    private void loadSalesPane()
    {
        areAllSalesOn = Bindings.not(tgbSalesClearAll.selectedProperty());
        areAllSalesOff = Bindings.not(tgbSalesSelectAll.selectedProperty());
		areAllSalesOn.addListener((ob, prev, curr) -> tgbSalesSelectAll.setSelected(curr));
		areAllSalesOff.addListener((ob, prev, curr) -> tgbSalesClearAll.setSelected(curr));
    }

	private void loadJournalPane()
	{
		areAllJournalOn = Bindings.not(tgbJournalClearAll.selectedProperty());
		areAllJournalOff = Bindings.not(tgbJournalSelectAll.selectedProperty());
		areAllJournalOn.addListener((ob, prev, curr) -> tgbJournalSelectAll.setSelected(curr));
		areAllJournalOff.addListener((ob, prev, curr) -> tgbJournalClearAll.setSelected(curr));
	}

	private void loadPlantPane()
	{
		CheckBoxTreeItem<PlantCatalogueBean> root = new CheckBoxTreeItem<>();
		for (PlantCatalogueBean ps : PlantCatalogueBean.getPlantCatalogue(true))
		{
			PlantCatalogueBeanCheckBoxTreeItem fred = new PlantCatalogueBeanCheckBoxTreeItem(ps);
			root.getChildren().add(fred);
		}
		tvPlants.setRoot(root);

		tvPlants.setCellFactory(c -> new CheckBoxTreeCell<>(
											(TreeItem<PlantCatalogueBean> x) -> { if (x instanceof CheckBoxTreeItem) { return ((CheckBoxTreeItem)x).selectedProperty();}
																					return new SimpleBooleanProperty(true);}
			,
				new StringConverter<>()
				{
					@Override
					public TreeItem<PlantCatalogueBean> fromString(String val)
					{
						return null;
					}

					@Override
					public String toString(TreeItem<PlantCatalogueBean> val)
					{
						return val.getValue().getCommonName();
					}
				})
		);

		tvPlants.setOnKeyTyped(ev -> {
			// Thanks to James_D https://stackoverflow.com/questions/34791652/how-to-add-keylistener-on-a-treeitem-javafx for hint
			LOGGER.debug("onKeyTyped: char: {}", ev.getCharacter());
			final String prefix = ev.getCharacter().toLowerCase();
			final TreeItem<PlantCatalogueBean> plantRoot = tvPlants.getRoot();
			for (var treeItem : plantRoot.getChildren())
			{
				int ix = tvPlants.getRow(treeItem);
				var pcb = treeItem.getValue();
				String name = pcb.getCommonName();
				if (name.toLowerCase().startsWith(prefix))
				{
					tvPlants.scrollTo(ix);
					tvPlants.getSelectionModel().select(ix);
					return;
				}
			}
		});

		((CheckBoxTreeItem)(tvPlants.getRoot())).addEventHandler(CheckBoxTreeItem.<PlantCatalogueBean>checkBoxSelectionChangedEvent(),
			x -> {
				boolean rootSelected = ((CheckBoxTreeItem)x.getSource()).selectedProperty().getValue();
				boolean rootIndet = ((CheckBoxTreeItem)x.getSource()).indeterminateProperty().getValue();
				tgbPlantsSelectAll.setSelected(rootSelected & !rootIndet);
				tgbPlantsClearAll.setSelected(!rootSelected & !rootIndet);
			} );

		if (tgbPlantsSelectAll.isSelected())
        {
            root.setSelected(true);	// NB this causes all the children of all the nodes to be read in	
        }
	}

	private void loadLocationPane()
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("loadLocationPane()");

		List<LocationBean> allLocs = new ArrayList<>();
		try
		{
			allLocs = LocationBean.fetchAll();
		} catch (GNDBException e)
		{
			PanicHandler.panic(e);
		}

		locationsSelection = new HashMap<>(allLocs.size());

		double nameWidth = new Text(tvLocationsName.getText()).getLayoutBounds().getWidth();
		for (LocationBean loc : allLocs)
		{
			final Text t = new Text(loc.getName());
			final double wid = t.getLayoutBounds().getWidth();
			if (wid > nameWidth) nameWidth = wid;
			SimpleBooleanProperty sbp = new SimpleBooleanProperty(loc, "Location: "+loc.getName(), true);
			sbp.addListener((obs, oldVal, newVal) -> {SimpleBooleanProperty xx = (SimpleBooleanProperty) obs;
										LOGGER.debug("location bool: loc: {}", xx.getBean());
										LocationBean evLoc = (LocationBean) ((SimpleBooleanProperty) obs).getBean();
						for (int ix = 0; ix < locationsSelection.size(); ix++)
						{
							LOGGER.debug("search nodes list: ix: {}", ix);
							TreeItem<LocationBean> ti = tvLocations.getTreeItem(ix);
							LOGGER.debug("build nodes list: ti: {}", ti);
							if (ti.getValue().getKey().equals(evLoc.getKey()))
							{
								for (TreeItem<LocationBean> kid : ti.getChildren())
								{
									locationsSelection.get(kid.getValue().getKey()).set(newVal);
								}
								break;
							}
						}
					}
			);
			locationsSelection.put(loc.getKey(), sbp);
			areAllLocationsOn = areAllLocationsOn.and(sbp);
			areAllLocationsOff = areAllLocationsOff.or(sbp);
		}
		tvLocationsName.setPrefWidth(nameWidth + 20);

//		LOGGER.debug("locationsSelection");
//		for (var loc : locationsSelection.keySet())
//		{
//			LOGGER.debug("key: {}", loc);
//			LOGGER.debug("value: {}", locationsSelection.get(loc));
//		}

		tvLocations.setColumnResizePolicy(NotebookResizer.using(tvLocations));

		tvLocationsName.setCellValueFactory(c -> c.getValue().getValue().nameProperty());
		tvLocationsName.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
		tvLocationsCheck.setCellValueFactory(c -> { LOGGER.debug("tvLocationsCheck: value: loc: {}, sbp: {}", c.getValue().getValue(),
																			locationsSelection.get(c.getValue().getValue().getKey()));
													return locationsSelection.get(c.getValue().getValue().getKey());});
		tvLocationsCheck.setCellFactory(c -> new CheckBoxTreeTableCell<>(ix -> {
			return locationsSelection.get(tvLocations.getTreeItem(ix).getValue().getKey()); } ) );

		tvLocationsDesc.setCellValueFactory(c -> c.getValue().getValue().descriptionProperty());
		tvLocationsDesc.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());

//		CheckBoxTreeItem<LocationBean> root = new CheckBoxTreeItem<>();
		TreeItem<LocationBean> root = new TreeItem<>();
		ObservableList<LocationBean> locs = FXCollections.emptyObservableList();

		// get the top level Location(s)
		try {
			locs = LocationBean.fetchTop();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}

		addLocationBranch(root, locs);

		areAllLocationsOff = areAllLocationsOff.not();
		areAllLocationsOn.addListener((ob, prev, curr) -> tgbLocationSelectAll.setSelected(curr));
		areAllLocationsOff.addListener((ob, prev, curr) -> tgbLocationsClearAll.setSelected(curr));
		tvLocations.setRoot(root);
		for (var node : root.getChildren())
		{
			node.setExpanded(true);
		}

//		if (tgbLocationSelectAll.isSelected())
//		{
//			root.setSelected(true);	// NB this causes all the children of all the nodes to be read in
//		}

	}

	private  void addLocationBranch(TreeItem<LocationBean> root, ObservableList<LocationBean> locs)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("addBranch(): locs: {}", locs);

		for (var loc : locs)
		{
			TreeItem<LocationBean> branch = new TreeItem<>(loc);
			root.getChildren().add(branch);
			try {
				addLocationBranch(branch, loc.getSubLocations());
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
		}

	}

//	@FXML
//	/**
//	 * JavaFX bug JDK-8269867 does not recognise short form dates (outside the USA) so 4/7/22 is taken as AD 22 not AD 2022.
//	 * This is a nasty little bodge to make it usable - remove when/if the bug is fixed.
//	 *
//	 * @param event	not used
//	 * @since 3.0.1
//	 */
//	private void dtpFromOnAction(ActionEvent event)
//	{
//		EntryMessage log4jEntryMsg = LOGGER.traceEntry("dtpFromOnAction(): value: {}", dtpFrom.getValue());
//		if (dtpFrom.getValue() == null) return;
//		if (dtpFrom.getValue().getYear() < 100)
//			dtpFrom.setValue((dtpFrom.getValue().plusYears(2000)));
//	}
//
//	@FXML
//	/**
//	 * JavaFX bug JDK-8269867 does not recognise short form dates (outside the USA) so 4/7/22 is taken as AD 22 not AD 2022.
//	 * This is a nasty little bodge to make it usable - remove when/if the bug is fixed.
//	 *
//	 * @param event	not used
//	 * @since 3.0.1
//	 */
//	private void dtpToOnAction(ActionEvent event)
//	{
//		EntryMessage log4jEntryMsg = LOGGER.traceEntry("dtpFromOnAction(): value: {}", dtpTo.getValue());
//		if (dtpTo.getValue() == null) return;
//		if (dtpTo.getValue().getYear() < 100)
//			dtpTo.setValue((dtpTo.getValue().plusYears(2000)));
//	}

	@FXML
	private void btnCopyDateOnAction(ActionEvent event)
	{
		if (dtpFrom.getValue() == null)	return;
		dtpTo.setValue(dtpFrom.getValue());
	}

	@FXML
    private void btnFetchOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("btnFetchOnAction()");
		DiaryBeanLister dil = new DiaryBeanLister();
		LocalDate fromDate = dtpFrom.getValue();
		LocalDate toDate = dtpTo.getValue();
		LOGGER.debug("btnFetchOnAction(): from: {}, to: {}", fromDate, toDate);
		dil.fromDate(fromDate).toDate(toDate);

		if (tgbHusbandrySelectAll.isSelected()) 
		{
			dil.allHusbandryClass();
		}
		else if (!tgbHusbandryClearAll.isSelected())
		{
			dil.husbandryClass(
				husbandryClasses.
					stream().
					filter(gw -> husbandryClassSelection.get(gw).get()).
					collect(Collectors.toList())
			);
		}

		if (tgbGWASelectAll.isSelected()) 
		{
			dil.allGroundworkActivity();
		}
		else if (!tgbGWAClearAll.isSelected())
		{
			dil.groundworkActivity(
				groundworkActivities.
					stream().
					filter(gw -> groundworkActivitySelection.get(gw).get()).
					collect(Collectors.toList())
			);
		}
		
		if (tgbAfflictionSelectAll.isSelected()) 
		{
			dil.allAffliction();
		}
		else if (!tgbAfflictionClearAll.isSelected())
		{
			dil.affliction(
				afflictions.
					stream().
					filter(gw -> afflictionSelection.get(gw).get()).
					collect(Collectors.toList())
			);
		}
		
		if (tgbWeatherSelectAll.isSelected())
		{
			dil.allWeatherCondition();
		}
		else if (!tgbWeatherClearAll.isSelected())
		{
			dil.weatherCondition(
				weatherConditions.
					stream().
					filter(gw -> weatherConditionSelection.get(gw).get()).
					collect(Collectors.toList())
			);
		}
		
		if (tgbWildlifeSelectAll.isSelected())
		{
			dil.allWildlife();
		}
		else if (!tgbWildlifeClearAll.isSelected())
		{
			dil.wildlifeSpecies(
				wildlifeSpecies.
					stream().
					filter(gw -> wildlifeSpeciesSelection.get(gw).get()).
					collect(Collectors.toList())
			);
		}
		
		if (tgbRetailersSelectAll.isSelected())
		{
			dil.allRetailers();
		}
		else if (!tgbRetailersClearAll.isSelected())
		{
			dil.retailer(
				retailers.
					stream().
					filter(gw -> retailersSelection.get(gw).get()).
					collect(Collectors.toList())
			);
		}
		
		if (tgbProdCatsSelectAll.isSelected())
		{
			dil.allProductCategories();
		}
		else if (!tgbProdCatsClearAll.isSelected())
		{
			dil.productCategory(
				productCategories.
					stream().
					filter(gw -> prodCatsSelection.get(gw).get()).
					collect(Collectors.toList())
			);
		}

        //  2.6.1
        // if there are NO Brands, requesting all Purchases does NOT set the 'allBrandsOn' option
        // with the result that a Purchase with no PurchaseItems will not get selected and shown
		if (tgbProdBrandsSelectAll.isSelected() || productBrands.isEmpty())
		{
			dil.allProductBrands();
		}
		else if (tgbProdBrandsClearAll.isSelected())
		{
            dil.noProductBrands();
		}
		else if (!tgbProdBrandsClearAll.isSelected())
		{
			dil.productBrand(
				productBrands.
					stream().
					filter(gw -> prodBrandsSelection.get(gw).get()).
					collect(Collectors.toList())
			);
		}
		
		if (tgbSalesSelectAll.isSelected())
		{
			dil.allSales();
		}

		if (tgbJournalSelectAll.isSelected())
		{
			dil.allJournal();
		}

		if (tgbPlantsSelectAll.isSelected())
		{
			LOGGER.debug("tgbPlantsSelectAll.isSelected is true");
			dil.allPlants();
		}
		else if (!tgbPlantsClearAll.isSelected())
		{
			LOGGER.debug("tgbPlantsClearAll.isSelected is false");
			PlantCatalogueBeanCheckBoxTreeItem pl = (PlantCatalogueBeanCheckBoxTreeItem)(tvPlants.getRoot().getChildren().get(0));	// first entry in the list below the root
			// perform a depth-first walk of the tree
			while (pl != null)
			{
//				LOGGER.debug("in walk");
				CheckBoxTreeItem<PlantCatalogueBean> plOrig = pl;
				if (pl.isSelected())
				{
//					LOGGER.debug("pl.isSelected is true");
					PlantCatalogueBean pci = pl.getValue();
					dil.plantCatalogue(pci);
					pl = (PlantCatalogueBeanCheckBoxTreeItem)(pl.nextSibling());
				}
				else if (pl.isIndeterminate())
				{
//					LOGGER.debug("pl.isIndeterminate is true");
					pl = (PlantCatalogueBeanCheckBoxTreeItem)(pl.getChildren().get(0));
				}
				else	// not selected, no selected children
				{
//					LOGGER.debug("not selected, no selected children");
					pl = (PlantCatalogueBeanCheckBoxTreeItem) (pl.nextSibling());
				}
				
				if (pl == null)
				{
					if (tvPlants.getTreeItemLevel(plOrig) > 1)
					{
						pl = (PlantCatalogueBeanCheckBoxTreeItem)(plOrig.getParent());
						pl = (PlantCatalogueBeanCheckBoxTreeItem)(pl.nextSibling());
					}
				}
			}
		}
		else if (tgbPlantsClearAll.isSelected())
		{
			dil.noPlants();
		}

		if (tgbLocationSelectAll.isSelected())
		{
			dil.allLocations();
		}
		else if (!tgbLocationsClearAll.isSelected())
		{
			List<LocationBean> locs = locationsSelection.keySet().stream().
												filter(ix ->locationsSelection.get(ix).get()).
												map(ix -> (LocationBean)(locationsSelection.get(ix).getBean())).
												toList();
			dil.locations(locs);
		}
		else if (tgbLocationsClearAll.isSelected())
		{
			dil.noLocations();
		}


		DiaryTab customTest = new DiaryTab();
		loadTab.accept(resources.getString("tab.diary"), customTest);
		customTest.setDiary(dil.fetch());
	}	//	btnFetchOnAction()
	
    @FXML
    private void btnThisYearOnAction(ActionEvent event)
	{
		dtpFrom.setValue(LocalDate.now().withDayOfYear(1));
		dtpTo.setValue(null);
	}

    @FXML
    private void btnLastYearOnAction(ActionEvent event)
	{
		dtpFrom.setValue(LocalDate.now().minusYears(1).withDayOfYear(1));
		dtpTo.setValue(LocalDate.now().minusYears(1).withMonth(12).withDayOfMonth(31));
	}

    @FXML
    private void btn12MonthsOnAction(ActionEvent event)
	{
		dtpFrom.setValue(LocalDate.now().minusMonths(12).withDayOfMonth(1));
		dtpTo.setValue(null);
	}

    @FXML
    private void btn6MonthsOnAction(ActionEvent event)
	{
		dtpFrom.setValue(LocalDate.now().minusMonths(6).withDayOfMonth(1));
		dtpTo.setValue(null);
	}

    @FXML
    private void btn3MonthsOnAction(ActionEvent event)
	{
		dtpFrom.setValue(LocalDate.now().minusMonths(3).withDayOfMonth(1));
		dtpTo.setValue(null);
	}

	@FXML
	private void btnbtn1WeekOnAction(ActionEvent event)
	{
		dtpFrom.setValue(LocalDate.now().minusWeeks(1));
		dtpTo.setValue(null);
	}

	@Override
	public void setLoadSplit(Consumer<Node> code)
	{
		loadSplit = code;
	}
	
	@Override
	public void setClearSplit(Consumer<Node> code)
	{
		clearSplit = code;
	}
	
	@Override
	public void setLoadTab(BiConsumer<String, Node> code)
	{
		loadTab = code;
	}

	@Override
	public void setClearTab(Consumer<Node> code) {
		clearTab = code;
	}
	
	@FXML
    private void tgbHusbandrySelectAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : husbandryClassSelection.values())
			sbp.set(true);
	}
	
	@FXML
    private void tgbHusbandryClearAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : husbandryClassSelection.values())
			sbp.set(false);
	}
	
	@FXML
    private void btnHusbandryAddOnAction(ActionEvent event)
	{
		HusbandryEditor tabCon = new HusbandryEditor();
		loadTab.accept(resources.getString("tab.husbandry"), tabCon);
		editorTabSetUp(tabCon);
	}

	@FXML
    private void tgbGWASelectAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : groundworkActivitySelection.values())
			sbp.set(true);
	}
	
	@FXML
    private void tgbGWAClearAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : groundworkActivitySelection.values())
			sbp.set(false);
	}
	
	@FXML
    private void btnGroundworkAddOnAction(ActionEvent event)
	{
		GroundworkEditor tabCon = new GroundworkEditor();
		loadTab.accept(resources.getString("tab.groundwork"), tabCon);
		editorTabSetUp(tabCon);
	}

	@FXML
    private void tgbAfflictionSelectAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : afflictionSelection.values())
			sbp.set(true);
	}
	
	@FXML
    private void tgbAfflictionClearAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : afflictionSelection.values())
			sbp.set(false);
	}
	
	@FXML
    private void btnAfflictionAddOnAction(ActionEvent event)
	{
		AfflictionEventEditor tabCon = new AfflictionEventEditor();
		loadTab.accept(resources.getString("tab.affliction"), tabCon);
		editorTabSetUp(tabCon);
	}

	@FXML
    private void tgbWeatherSelectAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : weatherConditionSelection.values())
			sbp.set(true);
	}
	
	@FXML
    private void tgbWeatherClearAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : weatherConditionSelection.values())
			sbp.set(false);
	}
	
	@FXML
    private void btnWeatherAddOnAction(ActionEvent event)
	{
		WeatherEditor tabCon = new WeatherEditor();
		loadTab.accept(resources.getString("tab.weather"), tabCon);
		editorTabSetUp(tabCon);
	}

	@FXML
    private void tgbPurchasesSelectAllOnAction(ActionEvent event) 
	{
        tgbRetailersSelectAllOnAction(event);
        tgbProdCatsSelectAllOnAction(event);
        tgbProdBrandsSelectAllOnAction(event);
	}
	
	@FXML
    private void tgbPurchasesClearAllOnAction(ActionEvent event)
	{
        tgbRetailersClearAllOnAction(event);
        tgbProdCatsClearAllOnAction(event);
        tgbProdBrandsClearAllOnAction(event);
	}
	
	@FXML
    private void btnPurchaseAddOnAction(ActionEvent event)
	{
		PurchaseEditor tabCon = new PurchaseEditor();
		loadTab.accept(resources.getString("tab.purchase"), tabCon);
		editorTabSetUp(tabCon);
	}

	@FXML
    private void tgbRetailersSelectAllOnAction(ActionEvent event) 
	{
		for (SimpleBooleanProperty sbp : retailersSelection.values())
			sbp.set(true);
	}
	
	@FXML
    private void tgbRetailersClearAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : retailersSelection.values())
			sbp.set(false);
	}
	
	@FXML
    private void tgbProdCatsSelectAllOnAction(ActionEvent event) 
	{
		for (SimpleBooleanProperty sbp : prodCatsSelection.values())
			sbp.set(true);
	}
	
	@FXML
    private void tgbProdCatsClearAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : prodCatsSelection.values())
			sbp.set(false);
	}
	
	@FXML
    private void tgbProdCatsPlantLikeOnAction(ActionEvent event)
	{
        for ( Map.Entry<ProductCategoryBean, SimpleBooleanProperty> me : prodCatsSelection.entrySet())
        {
            ProductCategoryBean pcb = me.getKey();
            var sbp = prodCatsSelection.get(pcb);
            sbp.set(pcb.isPlantLike());
        }
	}
	
	@FXML
    private void tgbProdBrandsSelectAllOnAction(ActionEvent event) 
	{
		for (SimpleBooleanProperty sbp : prodBrandsSelection.values())
			sbp.set(true);
	}
	
	@FXML
    private void tgbProdBrandsClearAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : prodBrandsSelection.values())
			sbp.set(false);
	}
	
	@FXML
    private void tgbWildlifeSelectAllOnAction(ActionEvent event)
	{
		for (SimpleBooleanProperty sbp : wildlifeSpeciesSelection.values())
			sbp.set(true);
	}
	
	@FXML
    private void tgbWildlifeClearAllOnAction(ActionEvent event) 
	{
		for (SimpleBooleanProperty sbp : wildlifeSpeciesSelection.values())
			sbp.set(false);
	}
	
	@FXML
    private void btnWildlifeAddOnAction(ActionEvent event)
	{
		WildlifeEditor tabCon = new WildlifeEditor();
		loadTab.accept(resources.getString("tab.wildlife"), tabCon);
		editorTabSetUp(tabCon);
	}

	@FXML
    private void tgbSalesSelectAllOnAction(ActionEvent event)
	{
	}
	
	@FXML
    private void tgbSalesClearAllOnAction(ActionEvent event) 
	{
	}
	
	@FXML
    private void btnSaleAddOnAction(ActionEvent event)
	{
		SaleEditor tabCon = new SaleEditor();
		loadTab.accept(resources.getString("tab.sales"), tabCon);
		editorTabSetUp(tabCon);
	}

	@FXML
	private void tgbJournalSelectAllOnAction(ActionEvent event)
	{
	}

	@FXML
	private void tgbJournalClearAllOnAction(ActionEvent event)
	{
	}

	@FXML
	private void btnJournalAddOnAction(ActionEvent event)
	{
		JournalEditor tabCon = new JournalEditor();
		loadTab.accept(resources.getString("tab.journal"), tabCon);
		editorTabSetUp(tabCon);
	}

	@FXML
    private void tgbPlantsSelectAllOnAction(ActionEvent event)
	{
		((CheckBoxTreeItem)(tvPlants.getRoot())).setSelected(true);
	}

	@FXML
    private void tgbPlantsClearAllOnAction(ActionEvent event)
	{
		/*
		*	version 1.1.1 change
		*	Just setting the root node NOT selected does NOT clear individual selections
		*	although if they were ALL selected they will ALL be cleared.
		*	Setting them all selected first is a bodge to avoid walking the whole tree.
		*/
		((CheckBoxTreeItem)(tvPlants.getRoot())).setSelected(true);
		((CheckBoxTreeItem)(tvPlants.getRoot())).setSelected(false);
	}

	@FXML
	private void tgbLocationsSelectAllOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("tgbLocationsSelectAllOnAction()");

		for (var x : locationsSelection.keySet())
		{
			SimpleBooleanProperty sbp = locationsSelection.get(x);
			sbp.set(true);
		}
	}

	@FXML
	private void tgbLocationsClearAllOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("tgbLocationsClearAllOnAction()");

		for (var x : locationsSelection.keySet())
		{
			SimpleBooleanProperty sbp = locationsSelection.get(x);
			sbp.set(false);
		}
	}

	private void editorTabSetUp(INotebookLoadable tabCon)
	{
		tabCon.setLoadSplit(loadSplit);
		tabCon.setClearSplit(clearSplit);
		tabCon.setLoadTab(loadTab);
	}

}
