/*
* Copyright (C) 2018-2022 Andrew Gegg
 *
 *	This file is part of the Gardeners Notebook application
 *
  * The Gardeners 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/gpl.html>.
 */

/*
	Change log
	2.1.0   allow direct addition by typing 
                Remove support for Detail_2
    2.4.0   Display products as a tree by ProductCategory
	2.6.1   Code tidy-up
    2.7.0   Use standard comment edit support
            Add a context menu to copy in selected products
    2.8.0   Permit in-line addition/edit of items - it should look like a shopping list!
    2.9.5   Modifications for Maven
            Add code to set product name column to a sensible initial size
    2.9.6	When a Diary entry is added/changed, make sure updated comments are shown
    3.0.4	Use new convenience class NotebookBeanDeleter for deletion.
    		Set first row of catalogue selected.
 */

package uk.co.gardennotebook;

import java.io.IOException;
import java.util.ResourceBundle;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TextFieldTreeTableCell;
//import java.util.Optional;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
//import javafx.scene.control.Alert;
//import javafx.scene.control.ButtonType;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableRow;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.WindowEvent;
import uk.co.gardennotebook.fxbean.ProductBean;
import uk.co.gardennotebook.fxbean.ProductBrandBean;
import uk.co.gardennotebook.fxbean.ProductCategoryBean;
import uk.co.gardennotebook.fxbean.PurchaseBean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;
import uk.co.gardennotebook.fxbean.ShoppingListBean;
import uk.co.gardennotebook.spi.GNDBException;
import uk.co.gardennotebook.spi.NotebookEntryType;

/**
	*	Controller class for create/update of Shopping List
	*
	*	@author Andy Gegg
	*	@version	3.0.4
	*	@since	2.1.0
 */
final class ShoppingListCat extends GridPane implements INotebookLoadable
{
	private static final Logger LOGGER = LogManager.getLogger();

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

	//	an observable property that the caller can monitor when a new item is being created
	private SimpleObjectProperty<PurchaseBean> newBean;

	//	an observable property that the caller can monitor when an item is deleted
	private SimpleObjectProperty<Object> deletedBean;

	@FXML
	private ResourceBundle resources;

	@FXML
	private Button btnChange;
	@FXML
	private Button btnDelete;

	// shopping list items
	@FXML
	private TableView<ShoppingListBean> tblCatalogue;
	@FXML
	private TableColumn<ShoppingListBean, ProductCategoryBean> tbvShopProductCategory;  //  2.8.0
	@FXML
	private TableColumn<ShoppingListBean, ProductBrandBean> tbvShopProductBrand;    //  2.8.0
	@FXML
	private TableColumn<ShoppingListBean, String> tbvShopProductName;
	@FXML
	private TableColumn<ShoppingListBean, String> tbvShopProductDetail;
	@FXML
	private TableColumn<ShoppingListBean, String> tbvShopNonSpecific;
	@FXML
	private TableColumn<ShoppingListBean, ShoppingListBean> tbvShopComment;
	@FXML
	private MenuItem ctxmnuDelete;

	// products to drag&drop on the ShoppingList table
	@FXML
	private TreeTableView<ProductTreeBean> tbvProducts;
	@FXML
	private TreeTableColumn<ProductTreeBean, String> tbvProdProductBrand;
	@FXML
	private TreeTableColumn<ProductTreeBean, String> tbvProdProductName;
	@FXML
	private TreeTableColumn<ProductTreeBean, String> tbvProdProductDetail_1;
//	@FXML
//	private TableColumn<ProductBean, String> tbvProdProductDetail_2;
	@FXML
	private TreeTableColumn<ProductTreeBean, String> tbvProdProductDescription;
	@FXML
	private TreeTableColumn<ProductTreeBean, ProductTreeBean> tbvProdComment;

    //  2.7.0
    @FXML
    private MenuItem ctxmnuProdCopyToSL;

	//	handles addition of multiple shopping list items
	private final ChangeListener<Boolean> shoppingListAdder = new ChangeListener<>()
    {
		@Override
		public void changed(ObservableValue<? extends Boolean> obj, Boolean oldVal, Boolean newVal)
        {
            LOGGER.debug("ShoppingListCat: shoppingListAdder: changed(): obj: {}, oldVal: {}, newVal: {}", obj, oldVal, newVal);
			if (newVal != null) 
			{
                ShoppingListBean slb_new = new ShoppingListBean();
                slb_new.isReadyProperty().addListener(this);
                tblCatalogue.getItems().add(slb_new);
                obj.removeListener(shoppingListAdder);
			}
		}
	};

	ShoppingListCat()
	{
		FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/ShoppingListCat.fxml"),
			ResourceBundle.getBundle("notebook") );
		fxmlLoader.setRoot(this);
		fxmlLoader.setController(this);
		try {
			fxmlLoader.load();	// NB initialize is called from in here
		} catch (IOException exception) {
			throw new RuntimeException(exception);
		}
	}

	/*
	* Initializes the controller class.
	*/
	@FXML
	private void initialize()
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("initialize()");
		
		tbvShopProductCategory.setCellValueFactory(cdf -> cdf.getValue().productCategoryProperty());
        tbvShopProductCategory.setCellFactory(x -> new NotebookDropDownCellType<>((s,t)-> new ProductCategoryCombo(t), ProductCategoryBean::getName){
            @Override
            protected boolean mayEdit(ShoppingListBean rowItem, ProductCategoryBean cellItem)
            {
                if (rowItem == null) return false;
                if (!rowItem.getNonspecificItem().isEmpty()) return false;
                return true;
            }

// Trying to mark the name column as required if only the category is given
//            @Override
//            protected void onActionExtra(ShoppingListBean rowItem, ProductCategoryBean cellItem)
//            {
//                if (rowItem == null) return;
//                this.getTableView().refresh();
//                return;
//            }
        });

        tbvShopProductBrand.setCellValueFactory(cdf -> cdf.getValue().productBrandProperty());
        tbvShopProductBrand.setCellFactory(x -> new NotebookDropDownCellType<>((s,t)-> new ProductBrandCombo(t), ProductBrandBean::getName){
				@Override
				protected boolean mayEdit(ShoppingListBean rowItem, ProductBrandBean cellItem)
				{// NB rowItem is NULL unless we are in edit mode
                    if (rowItem == null) return false;
                    if (!rowItem.getNonspecificItem().isEmpty()) return false;
                    return true;
				}
        });

        // this needs care.  The column is overloaded - it shows EITHER the PlantSpecies OR the Product.name value
        // For the PS, use the PlantSpeciesCombo - which can happily create a new PS.
        // For the name, show a combo of extant names but do NOT create a new Product at this point
		tbvShopProductName.setCellValueFactory(cdf -> cdf.getValue().nameProperty());
		tbvShopProductName.setCellFactory(x -> new NotebookEditCellType<>(){
				@Override
				protected boolean mayEdit(ShoppingListBean rowItem, String cellItem)
				{// NB rowItem is NULL unless we are in edit mode
					LOGGER.debug("tbvShopProductName: mayEdit(): rowItem: {}, cellItem: {}", rowItem, cellItem);
                    if (rowItem == null) return false;
					LOGGER.debug("tbvShopProductName: mayEdit(): nonspecificItem: {}", rowItem.getNonspecificItem());
                    if (!rowItem.getNonspecificItem().isEmpty()) return false;
					LOGGER.debug("tbvShopProductName: mayEdit(): hasProductCategory(): {}", rowItem.hasProductCategory());
                    if (!rowItem.hasProductCategory()) return false;
                    return true;
				}
				
				@Override
				void updateViewMode(ShoppingListBean rowItem, String cellItem)
				{// NB rowItem is NULL unless we are in edit mode
					LOGGER.debug("tbvShopProductName: updateViewMode(): rowItem: {}, cellItem: {}", rowItem, cellItem);
//					LOGGER.debug("name: updateViewMode(): item.isNew: {}", cellItem.isNew());
//					LOGGER.debug("name: updateViewMode(): item.productCategory: {}", cellItem.getProductCategory());
//					LOGGER.debug("name: updateViewMode(): item.productCategory.isNew: {}", cellItem.getProductCategory().isNew());
					setGraphic(null);
					setText(null);
					if (cellItem == null)
						return;

					if (isEditing() && mayEdit(rowItem, cellItem))
					{
						VBox vBox = new VBox();
						if (rowItem.getProductCategory().isPlantLike())
						{
							
							PlantSpeciesCombo cb = new PlantSpeciesCombo(rowItem.getPlantSpecies());
							cb.setEditable(true);
                            cb.setMandatory(true);
							cb.setOnAction(ev -> {
                                    LOGGER.debug("plant species: setOnAction: selection: {}", cb.getSelectionModel().getSelectedItem());
                                    if (cb.getValue() == null) return;
                                    rowItem.setPlantSpecies(cb.getValue());
                                    LOGGER.debug("ShoppingListCat: productName(PS): updateViewMode(): before test for save");
                                    commitEdit(rowItem.getName());
								});
							vBox.getChildren().add(cb);
						}
						else
						{
							ProductNameCombo cb = new ProductNameCombo(rowItem.getProductCategory());
							cb.setEditable(true);
                            cb.setMandatory(true);
							cb.setOnAction(ev -> {
                                    LOGGER.debug("NOT plant species: setOnAction: selection: {}", cb.getSelectionModel().getSelectedItem());
                                    rowItem.setName(cb.getValue());
                                    commitEdit(rowItem.getName());
								});
							vBox.getChildren().add(cb);
						}
						setGraphic(vBox);
					}
					else
					{
                        setText(cellItem);
					}
// Trying to mark the name column as required if only the category is given
//                    if (rowItem != null && rowItem.hasProductCategory() && rowItem.getName().isBlank())
//                    {
//                        setStyle("-fx-border-color: red;");
//                    }

				}
		});


		tbvShopProductDetail.setCellValueFactory(cdf -> cdf.getValue().nameDetailProperty());
		tbvShopProductDetail.setCellFactory(x -> new NotebookEditCellType<>(){
				@Override
				protected boolean mayEdit(ShoppingListBean rowItem, String cellItem)
				{// NB rowItem is NULL unless we are in edit mode
                    if (rowItem == null) return false;
                    if (!rowItem.getNonspecificItem().isEmpty()) return false;
                    if (!rowItem.hasProductCategory()) return false;
                    if (rowItem.nameProperty().getValue() == null || rowItem.nameProperty().getValue().isBlank()) return false;
                    return true;
				}
				
				@Override
				void updateViewMode(ShoppingListBean rowItem, String cellItem)
				{// NB rowItem is NULL unless we are in edit mode
                    LOGGER.traceEntry("tbvShopProductDetail: updateViewMode(): rowItem: {}, cellItem: {}", rowItem, cellItem);
					setGraphic(null);
					setText(null);
					if (cellItem == null)
						return;

					// cannot change the secondary name of an existing product
					if (isEditing() && mayEdit(rowItem, cellItem))
					{
						VBox vBox = new VBox();
						if (rowItem.productCategoryProperty().get().isPlantLike())
						{							
							PlantVarietyCombo cb = new PlantVarietyCombo(rowItem.getPlantSpecies(), rowItem.getPlantVariety());
							cb.setEditable(true);
							cb.setOnAction(ev -> {
                                    LOGGER.debug("plant variety: setOnAction: selection: {}", cb.getSelectionModel().getSelectedItem());
                                    rowItem.setPlantVariety(cb.getValue());
                                    commitEdit(rowItem.getNameDetail()); //  ProductBean sets the value correctly
								});
							vBox.getChildren().add(cb);
						}
						else
						{
                            //  2.0.1
							ProductDetailCombo cb = new ProductDetailCombo(rowItem.getProductCategory(), rowItem.getProduct());
							cb.setEditable(true);
							cb.setOnAction(ev -> {
                                    LOGGER.debug("NOT plant species: setOnAction: selection: {}", cb.getSelectionModel().getSelectedItem());
                                    rowItem.setNameDetail(cb.getValue());
                                    LOGGER.debug("NOT plant species: setOnAction: just AFTER settting value in rowItem.productBean");
                                    commitEdit(rowItem.getNameDetail());
                                    LOGGER.debug("NOT plant species: setOnAction: just AFTER commitEdit");
								});
							vBox.getChildren().add(cb);
						}
						setGraphic(vBox);
					}
					else
					{
                        setText(cellItem);
					}

				}
		});


		tbvShopNonSpecific.setCellValueFactory(cdf -> cdf.getValue().nonspecificItemProperty());
		tbvShopNonSpecific.setCellFactory(x -> new NotebookEditCellType<>()
		{
			@Override
			protected boolean mayEdit(ShoppingListBean rowItem, String cellItem)
			{// NB rowItem is NULL unless we are in edit mode
				LOGGER.trace("tbvShopNonSpecific: mayEdit(): rowItem: {}", rowItem);
				if (rowItem == null) return false;
				LOGGER.trace("tbvShopNonSpecific: mayEdit(): rowItem: {}, rowItem.hasProductCategory(): {}, rowItem.isNew(): {}", rowItem, rowItem.hasProductCategory(), rowItem.isNew());
				return (!rowItem.hasProductCategory() || rowItem.isNew());
			}

			@Override
			void updateViewMode(ShoppingListBean rowItem, String cellItem)
			{// NB rowItem is NULL unless we are in edit mode
				LOGGER.trace("tbvShopNonSpecific: updateViewMode(): rowItem: {}, cellItem: {}", rowItem, cellItem);
				setGraphic(null);
				setText(null);
				if (cellItem == null)
					return;
				LOGGER.trace("tbvShopNonSpecific: updateViewMode(): about to show TextField");
				if (isEditing() && mayEdit(rowItem, cellItem))
				{
					VBox vBox = new VBox();
					TextField tf = new TextField(rowItem.getNonspecificItem());
					tf.setOnAction(ev -> {
						rowItem.setNonspecificItem(tf.getText());
						commitEdit(tf.getText());
					});
					vBox.getChildren().add(tf);
					setGraphic(vBox);
				}
				else
				{
					setText(cellItem);
				}
			}
		});
        
		tbvShopComment.setCellValueFactory(cdf -> new SimpleObjectProperty<>(cdf.getValue()));
		tbvShopComment.setCellFactory(x -> new EditorCommentTableCell<>(resources));  //  2.9.6
		try {
			tblCatalogue.getItems().setAll(ShoppingListBean.fetchAll());
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		ShoppingListBean slb_new = new ShoppingListBean();
		slb_new.isReadyProperty().addListener(shoppingListAdder);
		tblCatalogue.getItems().add(slb_new);

		tblCatalogue.setColumnResizePolicy(NotebookResizer.using(tblCatalogue));
		
		tbvProdProductBrand.setCellValueFactory(cdf -> cdf.getValue().getValue().productBrandProperty());
        
		tbvProdProductName.setCellValueFactory(cdf -> cdf.getValue().getValue().nameProperty());
        
		tbvProdProductDetail_1.setCellValueFactory(cdf -> cdf.getValue().getValue().nameDetail_1Property());
        
//		tbvProdProductDetail_2.setCellValueFactory(cdf -> cdf.getValue().nameDetail_2Property());

		tbvProdProductDescription.setCellValueFactory(cdf -> cdf.getValue().getValue().descriptionProperty());
		tbvProdProductDescription.setCellFactory(TextFieldTreeTableCell.forTreeTableColumn());
        
		tbvProdComment.setCellValueFactory(cdf -> cdf.getValue().valueProperty());
		tbvProdComment.setCellFactory(c -> new ProductTreeCommentCell(resources));
         
		tbvProducts.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        tbvProducts.setRowFactory((ev -> new ProdListTableRow()));

        loadProductTree();

		//	2.9.5
		final ObservableList<TreeItem<ProductTreeBean>> items = tbvProducts.getRoot().getChildren();
		if (!items.isEmpty() && items.size() < 50)
		{
			double nameWidth = new Text(tbvProdProductName.getText()).getLayoutBounds().getWidth();
			for (TreeItem<ProductTreeBean> b : items)
			{
				final Text t = new Text(b.
						getValue().
						nameProperty().
						getValue());
				final double wid = t.getLayoutBounds().getWidth();
				if (wid > nameWidth) nameWidth = wid;
			}
			tbvProdProductName.setPrefWidth(nameWidth+30);
		}

		tbvProducts.setColumnResizePolicy(NotebookResizer.using(tbvProducts));
		tbvProducts.getSortOrder().addAll(  //tbvProdProductCategory,
											tbvProdProductName,
											tbvProdProductDetail_1, 
//											tbvProdProductDetail_2,
											tbvProdProductBrand);
		
		// only allow change and delete if there is a row selected
		btnChange.disableProperty().bind(tblCatalogue.getSelectionModel().selectedItemProperty().isNull());
		btnDelete.setDisable(true);
		tblCatalogue.getSelectionModel().selectedItemProperty().addListener((obs, old, nval) ->{
					try
					{
						btnDelete.setDisable( (nval == null) || !(nval.canDelete()) );
					} catch (GNDBException ex) {
						PanicHandler.panic(ex);
					}
				});

		tblCatalogue.getSelectionModel().select(0);
		Platform.runLater(() -> tblCatalogue.requestFocus());	//	this enables keyboard navigation with the up/down arrows

        LOGGER.traceExit(log4jEntryMsg);
	}

    private void loadProductTree()
    {
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("loadProductTree()");
    
        TreeItem<ProductTreeBean> root = new TreeItem<>();
        ObservableList<ProductCategoryBean> cats = FXCollections.emptyObservableList();
        try {
            cats = ProductCategoryBean.fetchAll();
        } catch (GNDBException ex) {
            PanicHandler.panic(ex);
        }
        for (var cat : cats)
        {
            TreeItem<ProductTreeBean> branch = new TreeItem<>(new ProductTreeBean(cat));
            root.getChildren().add(branch);
            ObservableList<ProductBean> prods = FXCollections.emptyObservableList();
            try {
                prods = ProductBean.fetchAll(cat);
            } catch (GNDBException ex) {
                PanicHandler.panic(ex);
            }
            for (var prod : prods)
            {
                TreeItem<ProductTreeBean> leaf = new TreeItem<>(new ProductTreeBean(prod));
                branch.getChildren().add(leaf);
            }
        }
		tbvProducts.setRoot(root);
		LOGGER.traceExit(log4jEntryMsg);
    }

	@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 ctxmnuOnShowing(WindowEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuOnShowing()");
		ShoppingListBean ixBean = tblCatalogue.getSelectionModel().selectedItemProperty().get();
		try
		{
			ctxmnuDelete.setDisable( (ixBean == null) || !(ixBean.canDelete()) );
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuOnShowing()

	@FXML
	private void ctxmnuAddOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuAddOnAction()");
		ShoppingListEditor tabCon = new ShoppingListEditor();
		loadTab.accept(resources.getString("tab.shoppinglist"), tabCon);
		tabCon.newBeanProperty().addListener((obs, oldVal, newVal) -> tblCatalogue.getItems().add(tblCatalogue.getItems().size()-1, newVal));
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuAddOnAction()

	@FXML
	private void ctxmnuChangeOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuChangeOnAction()");
		ShoppingListBean ixBean = tblCatalogue.getSelectionModel().selectedItemProperty().get();
		if (ixBean == null)
		{
			LOGGER.debug("thisValueBean is null");
			LOGGER.traceExit(log4jEntryMsg);
			return;
		}

		ShoppingListEditor tabCon = new ShoppingListEditor(ixBean);
		loadTab.accept(resources.getString("tab.shoppinglist"), tabCon);
		tabCon.deletedBeanProperty().addListener((obs, oldVal, newVal) -> tblCatalogue.getItems().remove(ixBean));
		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuChangeOnAction()

	@FXML
	private void ctxmnuDeleteOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuDeleteOnAction()");
		ShoppingListBean piBean = tblCatalogue.getSelectionModel().getSelectedItem();
		if (piBean == null)
		{
			return;
		}
		
//		boolean canDelete = false;
//		try {
//			canDelete = piBean.canDelete();
//		} catch (GNDBException ex) {
//			PanicHandler.panic(ex);
//		}
//		if (!canDelete)
//		{
//			Alert checkDelete = new Alert(Alert.AlertType.INFORMATION, resources.getString("alert.cannotdelete"), ButtonType.OK);
//			Optional<ButtonType> result = checkDelete.showAndWait();
//			LOGGER.traceExit(log4jEntryMsg);
//			return;
//		}
//
//		Alert checkDelete = new Alert(Alert.AlertType.CONFIRMATION, resources.getString("alert.confirmdelete"), ButtonType.NO, ButtonType.YES);
//		Optional<ButtonType> result = checkDelete.showAndWait();
//		LOGGER.debug("after delete dialog: result:{}, result.get:{}",result, result.get());
//		if (result.isPresent() && result.get() == ButtonType.YES)
//		{
//			LOGGER.debug("after delete confirmed");
//			try {
//				piBean.delete();
//			} catch (GNDBException ex) {
//				PanicHandler.panic(ex);
//			}
//			tblCatalogue.getItems().remove(piBean);
//		}
		NotebookBeanDeleter<ShoppingListBean> deleterImpl = new NotebookBeanDeleter<>(resources);
		if (deleterImpl.deleteItemImpl(piBean))
		{
			tblCatalogue.getItems().remove(piBean);
		}

		LOGGER.traceExit(log4jEntryMsg);
	}	//	ctxmnuDeleteOnAction()


	@FXML
	private void btnAddOnAction(ActionEvent event)
	{
		ctxmnuAddOnAction(event);
	}

	@FXML
	private void btnChangeOnAction(ActionEvent event)
	{
		ctxmnuChangeOnAction(event);
	}

	@FXML
	private void btnDeleteOnAction(ActionEvent event)
	{
		ctxmnuDeleteOnAction(event);
	}
	
    /*
     * If no actual product is selected, disable the Copy action.
     * 
     * @param event 
     */
    @FXML
    private void ctxmnuProdOnShowing(WindowEvent event)
    {
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuProdOnShowing");

        ctxmnuProdCopyToSL.setDisable(true);
        if (tbvProducts.getSelectionModel().isEmpty())
        {
            return;
        }
        //  can only D&D Products, not Categories
        for (var pr : tbvProducts.getSelectionModel().getSelectedItems())
        {
            if (pr.getValue().getNodeType() == NotebookEntryType.PRODUCT)
            {
                ctxmnuProdCopyToSL.setDisable(false);
                return;
            }
        }
    }
    
    @FXML
    private void ctxmnuProdCopyToSLOnAction(ActionEvent event)
    {
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("ctxmnuProdCopyToSLOnAction()");
        
        dropProdOnSL();
        tblCatalogue.refresh();
    }

	@FXML
	private void tbvItemsOnDragOver(DragEvent ev)
	{
		Dragboard db = ev.getDragboard();
		if ( db.hasString() && 
			(db.getString() != null) &&
			(!db.getString().isEmpty()) &&
			(db.getString().startsWith("tbvProducts:")))
		{
			ev.acceptTransferModes(TransferMode.COPY);
		}
		ev.consume();
		
	}

	@FXML
	private void tbvItemsOnDragDropped(DragEvent ev)
	{
		Dragboard db = ev.getDragboard();
		boolean success = false;
		if ( db.hasString() && 
			(db.getString() != null) &&
			(!db.getString().isEmpty()) &&
			(db.getString().startsWith("tbvProducts:")))
		{
			success = true;
			dropProdOnSL();
		}
		ev.setDropCompleted(success);
		ev.consume();
	}
	
	/*
	 * Create ShoppingList items using the selected Products
	 */
	private void dropProdOnSL()
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("dropProdOnSL()");

        var prods = tbvProducts.getSelectionModel().getSelectedItems();
		for (var pr : prods)
		{
            if (pr.getValue().getNodeType() == NotebookEntryType.PRODUCT)
            {
                ShoppingListBean pib_new = new ShoppingListBean();
                pib_new.setProduct(pr.getValue().getProductBean());
                try {
                    pib_new.save();
                } catch (GNDBException ex) {
                    PanicHandler.panic(ex);
                }
                LOGGER.debug("dropProdOnSL(): just before add to list");
                tblCatalogue.getItems().add(tblCatalogue.getItems().size()-1, pib_new);
                LOGGER.debug("dropProdOnSL(): just after add to list");
            }
		}
	}

	SimpleObjectProperty<PurchaseBean> newBeanProperty()
	{
		if (newBean == null)
		{
			newBean = new SimpleObjectProperty<>();
		}
		return newBean;
	}

	SimpleObjectProperty<Object> deletedBeanProperty()
	{
		if (deletedBean == null)
		{
			deletedBean = new SimpleObjectProperty<>();
		}
		return deletedBean;
	}
				
    /*
     * Implement Drag&Drop to copy Product items to the PurchaseItems list.
     * Main benefit is a decent row image when copying.
     * 
     * @since 2.7.0
     */
    private class ProdListTableRow extends TreeTableRow<ProductTreeBean>
    {
        ProdListTableRow()
        {
            setOnDragDetected(ev -> {
                if (tbvProducts.getSelectionModel().isEmpty())
                {
                    ev.consume();
                    return;
                }
                //  can only D&D Products, not Categories
                int size = 0;
                for (var pr : tbvProducts.getSelectionModel().getSelectedItems())
                {
                    if (pr.getValue().getNodeType() == NotebookEntryType.PRODUCT)
                    {
                        size++;
                    }
                }
                if (size <= 0)
                {
                    ev.consume();
                    return;
                }
                Dragboard db = startDragAndDrop(TransferMode.COPY);
                db.setDragView(snapshot(null, null));
                ClipboardContent content = new ClipboardContent();
                content.putString("tbvProducts:"+size);
                db.setContent(content);
                ev.consume();
            });
            
        }
    }

}
