/*
 * 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.9.6	Show correct repeat quantifier when editing existing reminder
    3.0.1	CommentAdder handling moved to separate class
    3.0.2	Check before Cancel if changes have been made
    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.
 */

package uk.co.gardennotebook;

import java.io.IOException;
//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.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
//import javafx.scene.text.Text;
//import javafx.scene.control.Alert;
//import javafx.scene.control.ButtonType;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import uk.co.gardennotebook.fxbean.CommentBean;
import javafx.scene.layout.GridPane;
import javafx.scene.control.DatePicker;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import uk.co.gardennotebook.fxbean.ReminderBean;
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 create/update of Reminder items
	*
	*	@author Andy Gegg
	*	@version	3.1.0
	*	@since	1.0
 */
final public class ReminderEditor extends GridPane implements INotebookLoadable
{

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

	private final ReminderBean thisValueBean;

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

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

	private boolean addingBean;

	@FXML
	private ResourceBundle resources;

	@FXML
	private Button btnSave;
	@FXML
	private Button btnDelete;
	@FXML
	private SafeDatePicker dtpRepeatUntil;
	@FXML
	private SafeDatePicker dtpShowFrom;
	@FXML
	private CheckBox chkSingleShot;
	@FXML
	private Spinner<Integer> numRepeatQuantifier;
	@FXML
	private PlantSpeciesCombo parentPlantSpecies;
	@FXML
	private PlantVarietyCombo parentPlantVariety;
	@FXML
	private HusbandryClassCombo parentHusbandryClass;
	@FXML
	private GroundworkActivityCombo parentGroundworkActivity;
	@FXML
	private ReminderIntervalCombo fldRepeatInterval;
	@FXML
	private TextField fldDescription;

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

	ReminderEditor()
	{
		this(null);
	}

	ReminderEditor(ReminderBean initialVal)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("constructor()");
		this.thisValueBean = (initialVal != null ? initialVal : new ReminderBean());
		this.addingItem = (initialVal == null);
		FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/ReminderEditor.fxml"),
			ResourceBundle.getBundle("notebook") );
		fxmlLoader.setRoot(this);
		fxmlLoader.setController(this);
		LOGGER.debug("constructor(): before load");
		try {
			fxmlLoader.load();	// NB initialize is called from in here
		} catch (IOException exception) {
			throw new RuntimeException(exception);
		}
		LOGGER.traceExit(log4jEntryMsg);
	}

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

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

		thisValueBean.setSaveRequired(true);
		dtpRepeatUntil.valueProperty().bindBidirectional(thisValueBean.repeatUntilProperty());
		dtpRepeatUntil.setConverter(null);	// resets to default converter
//        //  2.3.0
//        //  if user tabs out after editing, make sure the DatePicker updates
//        dtpRepeatUntil.getEditor().focusedProperty().addListener((obj, wasFocused, isFocused)->{
//            if (wasFocused && !isFocused)
//            {
//                try
//                {
//                    dtpRepeatUntil.setValue(dtpRepeatUntil.getConverter().fromString(dtpRepeatUntil.getEditor().getText()));
//                } catch (DateTimeParseException e) {
//                    dtpRepeatUntil.getEditor().setText(dtpRepeatUntil.getConverter().toString(dtpRepeatUntil.getValue()));
//                }
//            }
//        });

		dtpShowFrom.valueProperty().bindBidirectional(thisValueBean.showFromProperty());
		dtpShowFrom.setConverter(null);	// resets to default converter
		dtpShowFrom.setMandatory();
//        //  2.3.0
//        //  if user tabs out after editing, make sure the DatePicker updates
//        dtpShowFrom.getEditor().focusedProperty().addListener((obj, wasFocused, isFocused)->{
//            if (wasFocused && !isFocused)
//            {
//                try
//                {
//                    dtpShowFrom.setValue(dtpShowFrom.getConverter().fromString(dtpShowFrom.getEditor().getText()));
//                } catch (DateTimeParseException e) {
//                    dtpShowFrom.getEditor().setText(dtpShowFrom.getConverter().toString(dtpShowFrom.getValue()));
//                }
//            }
//        });

		chkSingleShot.selectedProperty().bindBidirectional(thisValueBean.singleShotProperty()); //  2.4.0

		numRepeatQuantifier.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 100, thisValueBean.repeatQuantifierProperty().get()));
		//	2.9.6 do this AFTER setting the initial value or the value is set to the default (1)
		thisValueBean.repeatQuantifierProperty().bind(numRepeatQuantifier.valueProperty());	// NB bidirectional binding NOT allowed on Spinners!

		parentPlantSpecies.valueProperty().bindBidirectional(thisValueBean.plantSpeciesProperty());
		parentPlantVariety.valueProperty().bindBidirectional(thisValueBean.plantVarietyProperty());
		parentPlantVariety.plantSpeciesProperty().bind(parentPlantSpecies.valueProperty());
		parentHusbandryClass.valueProperty().bindBidirectional(thisValueBean.husbandryClassProperty());
		parentGroundworkActivity.valueProperty().bindBidirectional(thisValueBean.groundworkActivityProperty());
		fldRepeatInterval.setResources(resources);
		fldRepeatInterval.valueProperty().bindBidirectional(thisValueBean.repeatIntervalProperty());
		fldDescription.textProperty().bindBidirectional(thisValueBean.descriptionProperty());
		fldDescription.setTextFormatter(MaxLengthTextFormatter.getFormatter(1024));

		// must have a date for first (or only) display
		// must specify groundwork or husbandry class
		// if husbandry there must be a plant species
		// there must be a description - or it's an empty reminder! (this is a bit dubious; the code deliberately allows blank desc)
		// for repeated displays, there must be a repeat interval (using a spinner forces a legal value for the quantifier)
		btnSave.disableProperty().bind(dtpShowFrom.valueProperty().isNull()
			.or(parentGroundworkActivity.valueProperty().isNull().and(parentHusbandryClass.valueProperty().isNull()))
				.or(parentHusbandryClass.valueProperty().isNotNull().and(parentPlantSpecies.valueProperty().isNull()))
						.or(fldRepeatInterval.valueProperty().isNull().and(chkSingleShot.selectedProperty().not()))
							.or(fldDescription.textProperty().isEmpty()));
		try {
			btnDelete.setDisable(addingItem || !(this.thisValueBean.canDelete()));
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}

		
// ToDoList processing actually allows a Reminder to have BOTH HusbandryClass and GroundworkActivity - can't see it ever being used, but...		

		numRepeatQuantifier.disableProperty().bind(chkSingleShot.selectedProperty());
		fldRepeatInterval.disableProperty().bind(chkSingleShot.selectedProperty());
		dtpRepeatUntil.disableProperty().bind(chkSingleShot.selectedProperty());

		Platform.runLater(() -> parentHusbandryClass.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;
		}

		// need to explicitly unbind the spinner or thisValueBean.cancelEdit() faults: 'cannot set bound value' in thisValueBean.setValues()
		thisValueBean.repeatQuantifierProperty().unbind();
		thisValueBean.cancelEdit();
	}

	@FXML
	/**
	 * Don't allow Repeat Until to be before Show From.
	 *
	 * @param event	not used
	 * @since 3.1.0
	 */
	private void dtpShowFromOnAction(ActionEvent event)
	{
		EntryMessage log4jEntryMsg = LOGGER.traceEntry("dtpShowFromOnAction(): value: {}", dtpShowFrom.getValue());
		if (dtpShowFrom.getValue() == null) return;
		dtpRepeatUntil.setMinValidDate(dtpShowFrom.getValue());
		if (dtpRepeatUntil.getValue().isBefore(dtpShowFrom.getValue()))
		{
			dtpRepeatUntil.setValue(dtpShowFrom.getValue());
		}
	}

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

		// need to explicitly unbind the spinner or thisValueBean.cancelEdit() faults: 'cannot set bound value' in thisValueBean.setValues()
		thisValueBean.repeatQuantifierProperty().unbind();
		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())
		{
			final NotebookEditorCanceller<ReminderBean> 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<ReminderBean> deleterImpl = new NotebookBeanDeleter<>(resources, ()->thisValueBean.repeatQuantifierProperty().unbind());
		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;
		}
		else
		{
			// need to explicitly unbind the spinner or thisValueBean.save() faults: 'cannot set bound value' in thisValueBean.setValues()
			thisValueBean.repeatQuantifierProperty().unbind();
			try {
				thisValueBean.save();
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
			newBeanProperty().setValue(thisValueBean);
		}
		clearTab.accept(this);
		LOGGER.traceExit(log4jEntryMsg);
	}	//	btnSaveOnAction()

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

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

}
