/*
 * Copyright (C) 2018, 2019, 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/gpl.html>.
 */

/*
	Change log
    2.3.0   Pick up tab-out from DatePicker
    2.4.0   Support tab-out on comments
    2.5.0   Lock PlantSpecies/Variety if item is in a Storyline
    3.0.0	Support Locations
    		Support new Quantity and Severity fields.
    3.0.1	CommentAdder handling moved to separate class
    3.0.2	Check before Cancel if changes have been made
    		Check dates in Storyline entries
    3.0.4	Use new convenience class NotebookBeanDeleter for deletion.
    		Use new convenience class NotebookBeanCanceller to handle edit cancellation.
    		Use new convenience class EditorCommentTableHandler to handle Comment table construction.
    		Set focus on first field.
    3.1.0	Use SafeDatePicker
    		Change constructor for new descendant entries (to get the date)
    3.1.2	Copy in Location from ancestor ('watch for', reminder, 'and then')
 */

package uk.co.gardennotebook;

import java.io.IOException;
import java.time.LocalDate;
//import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
//import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
//import java.time.format.FormatStyle;

import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
//import javafx.beans.value.ChangeListener;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.control.*;
//import javafx.scene.text.Text;
import javafx.scene.layout.GridPane;
import uk.co.gardennotebook.fxbean.*;
import uk.co.gardennotebook.spi.GNDBException;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.EntryMessage;

/**
	*	Controller class for create/update of AfflictionEvent items
	*
	*	@author Andy Gegg
	*	@version	3.1.2
	*	@since	1.0
 */
final public class AfflictionEventEditor extends GridPane implements INotebookLoadable
{

	private static final Logger LOGGER = LogManager.getLogger();

	private final AfflictionEventBean thisValueBean;
    private final boolean newStorylineEntry;

	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<AfflictionEventBean> newBean;
	private final boolean addingItem;

	private LocalDate earliestDate = LocalDate.MIN;

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

	@FXML
	private ResourceBundle resources;

	@FXML
	private Button btnSave;
	@FXML
	private Button btnDelete;
	@FXML
	private SafeDatePicker dtpDate;
	@FXML
	private AfflictionCombo parentAffliction;
	@FXML
	private PlantSpeciesCombo parentPlantSpecies;
	@FXML
	private PlantVarietyCombo parentPlantVariety;
	@FXML
	private LocationCombo parentLocation;
	@FXML
	private TextField fldQuantity;
	@FXML
	private TextField fldSeverity;

	@FXML
	private TableView<CommentBean> tbvComment;
	@FXML
	private TableColumn<CommentBean, CommentBean> tbvCommentDate;
	@FXML
	private TableColumn<CommentBean, String> tbvCommentText;

    /**
     * Use this for a brand new entry.
     */
	AfflictionEventEditor()
	{
		this(null);
	}

	/**
	 * Use this to add a new descendant to an entry
	 *
	 * @param parent	The ancestor for the new entry
	 * @param asParent	Not used - needed to distinguish this constructor from the one to just edit an extant item
	 */
	AfflictionEventEditor(INotebookBean parent, boolean asParent)
	{
		LOGGER.debug("INotebookBean constructor");

		this.addingItem = true;
		newStorylineEntry = true;

		this.thisValueBean = new AfflictionEventBean();
		switch (parent)
		{
			case null -> {
				LOGGER.debug("null");
				throw new IllegalArgumentException("parent must not be null");
			}
			case HusbandryBean hu -> {
				LOGGER.debug("Husbandry: {}", hu);
				this.thisValueBean.setPlantSpecies(hu.getPlantSpecies());
				if (hu.hasPlantVariety())
				{
					this.thisValueBean.setPlantVariety(hu.getPlantVariety());
				}
				this.thisValueBean.setLocation(hu.getLocation());
				earliestDate = hu.getDate();
			}
			case PurchaseItemBean pi -> {
				LOGGER.debug("Purchase Item: {}", pi);
				if (pi.getProductCategory().isPlantLike())
				{
					this.thisValueBean.setPlantSpecies(pi.getPlantSpecies());
					if (pi.hasPlantVariety())
					{
						this.thisValueBean.setPlantVariety(pi.getPlantVariety());
					}
				}
				else
				{
					return;
				}
				earliestDate = pi.getPurchase().getDate();
			}
			case GroundworkBean gw -> {
				LOGGER.debug("Groundwork: {}", gw);
				if (gw.hasPlantSpecies())
				{
					this.thisValueBean.setPlantSpecies(gw.getPlantSpecies());
					if (gw.hasPlantVariety())
					{
						this.thisValueBean.setPlantVariety(gw.getPlantVariety());
					}
				}
				this.thisValueBean.setLocation(gw.getLocation());
				earliestDate = gw.getDate();
			}
			case AfflictionEventBean ae -> {
				LOGGER.debug("Affliction event: {}", ae);
				if (ae.hasPlantSpecies())
				{
					this.thisValueBean.setPlantSpecies(ae.getPlantSpecies());
					if (ae.hasPlantVariety())
					{
						this.thisValueBean.setPlantVariety(ae.getPlantVariety());
					}
				}
				this.thisValueBean.setLocation(ae.getLocation());
				earliestDate = ae.getDate();
			}
			default -> {
				LOGGER.debug("unknown");
				throw new IllegalArgumentException("unexpected parent type: " + parent.getType());
			}
		}

		loadForm();
	}

	/**
     * Use this to edit an existing entry.
     * 
     * @param   initialVal  the AfflictionEvent entry to edit
     */
	AfflictionEventEditor(AfflictionEventBean initialVal)
	{
		this.thisValueBean = (initialVal != null ? initialVal : new AfflictionEventBean());
		this.addingItem = (initialVal == null);
		newStorylineEntry = false;

		loadForm();
	}

	private void loadForm()
	{
		FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/AfflictionEventEditor.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);
		}
	}

	/**
	*	Initialises the controller class.
	*/
	@FXML
	private void initialize()
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("initialize()");

		EditorCommentTableHandler<AfflictionEventBean> cth = new EditorCommentTableHandler<>(resources);
		cth.constructCommentTable(tbvComment, tbvCommentDate, tbvCommentText, thisValueBean);

		thisValueBean.setSaveRequired(true);

		dtpDate.valueProperty().bindBidirectional(thisValueBean.dateProperty());
		//	if this is in a StoryLine, cannot have a date earlier than the mother or later than the eldest daughter
		final EditorDateRangeChecker rangeChecker = new EditorDateRangeChecker(thisValueBean);
		LocalDate minDate = rangeChecker.getMinDate();
		if (earliestDate.isAfter(minDate))
		{
			minDate = earliestDate;
		}
		final LocalDate maxDate = rangeChecker.getMaxDate();
		LOGGER.debug("minDate: {}, maxDate: {}", minDate, maxDate);
		dtpDate.setMandatory().setMinValidDate(minDate).setMaxValidDate(maxDate);

		parentAffliction.valueProperty().bindBidirectional(thisValueBean.afflictionProperty());
		parentPlantSpecies.valueProperty().bindBidirectional(thisValueBean.plantSpeciesProperty());
		parentPlantVariety.valueProperty().bindBidirectional(thisValueBean.plantVarietyProperty());
		parentPlantVariety.plantSpeciesProperty().bind(parentPlantSpecies.valueProperty());
        
        // If this entry is being added as an 'And then...' entry, cannot change the PlantSpecies/Variety
        if (newStorylineEntry)
        {
            LOGGER.trace("newStorylineEntry: locking off PlantSpecies/Variety");
            parentPlantSpecies.setDisable(true);
            parentPlantVariety.setDisable(true);
        }
        // If this entry is in a storyline, cannot change the PlantSpecies/Variety
        try {
            if (!addingItem && (thisValueBean.hasAncestor() || thisValueBean.hasDescendant()) )
            {
                LOGGER.trace("in a story line: locking off PlantSpecies/Variety");
                parentPlantSpecies.setDisable(true);
                parentPlantVariety.setDisable(true);
            }
        } catch (GNDBException ex) {
			PanicHandler.panic(ex);
        }

		parentLocation.valueProperty().bindBidirectional(thisValueBean.locationProperty());
		fldQuantity.textProperty().bindBidirectional(thisValueBean.quantityProperty());
		fldQuantity.setTextFormatter(MaxLengthTextFormatter.getFormatter(255));
		fldSeverity.textProperty().bindBidirectional(thisValueBean.severityProperty());
		fldSeverity.setTextFormatter(MaxLengthTextFormatter.getFormatter(255));

		btnSave.disableProperty().bind(dtpDate.valueProperty().isNull()
			.or(parentAffliction.valueProperty().isNull()));
		try
		{
			btnDelete.setDisable(addingItem || !(this.thisValueBean.canDelete()));
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		Platform.runLater(() -> dtpDate.requestFocus());

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

	@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;
	}

	@Override
	public void clearUpOnClose(Event e)
	{
		if (isCancelDenied())
		{
			e.consume();
			return;
		}

		thisValueBean.cancelEdit();
	}

	@FXML
	private void btnCancelOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("btnCancelOnAction()");
		if (thisValueBean == null)
		{
			LOGGER.debug("thisValueBean is null");
			LOGGER.traceExit(log4jEntryMsg);
			return;
		}

		if (isCancelDenied())
		{
			return;
		}

		thisValueBean.cancelEdit();
		clearTab.accept(this);
		LOGGER.traceExit(log4jEntryMsg);
	}	//	btnCancelOnAction()

	/**
	 * Check if user really wants to quit if changes have been made
	 *
	 * @return	true	user does NOT want to quit
	 */
	private boolean isCancelDenied()
	{
		if (thisValueBean.needSave())
		{
			NotebookEditorCanceller<AfflictionEventBean> cancelChecker = new NotebookEditorCanceller<>(resources);
			return cancelChecker.isCancelDenied(thisValueBean);
		}
		return false;
	}

	@FXML
	private void btnDeleteOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("btnDeleteOnAction()");
		if (thisValueBean == null)
		{
			LOGGER.debug("thisValueBean is null");
			LOGGER.traceExit(log4jEntryMsg);
			return;
		}
		NotebookBeanDeleter<AfflictionEventBean> deleterImpl = new NotebookBeanDeleter<>(resources);
		if (deleterImpl.deleteItemImpl(thisValueBean))
		{
			deletedBeanProperty().setValue(new Object());
			clearTab.accept(this);
		}

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

	@FXML
	private void btnSaveOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("btnSaveOnAction()");
		if (thisValueBean == null)
		{
			LOGGER.debug("thisValueBean is null");
			LOGGER.traceExit(log4jEntryMsg);
			return;
		}
		try
		{
			thisValueBean.save();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		newBeanProperty().setValue(thisValueBean);
		clearTab.accept(this);
		LOGGER.traceExit(log4jEntryMsg);
	}	//	btnSaveOnAction()

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

	SimpleObjectProperty<Object> deletedBeanProperty()
	{
		if (deletedBean == null)
		{
			deletedBean = new SimpleObjectProperty<>();
		}
		return deletedBean;
	}

}
