/*
 * Copyright (C) 2018 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.9.6	When a Diary entry is added/changed, make sure updated comments are shown
 */

package uk.co.gardennotebook.fxbean;

import uk.co.gardennotebook.spi.*;

import java.util.Optional;
import java.time.*;
import javafx.beans.property.StringProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.value.ChangeListener;

import javafx.beans.value.ObservableValue;

/**
	*	A comment on a particular entry.  Often the main content of a husbandry item.
	*
	*@apiNote
	* 	Note that Comments are logically part of the parent - and in some implementations may be
	* 		physically persisted within the parent, so all persistence is to be handled by the
	* 		parent, not from here.
	*
	*	@author	Andy Gegg
	*	@version	2.9.6
	*	@since	1.0
*/
final public class CommentBean
{
	private IComment baseItem = null;

	private boolean newItem = false;
	private final INotebookBean parentItem;

	/*
	*	The comment is associated with a DB record of this type.
<UL>
<LI>PC product category
<LI>BR product brand
<LI>PR product
<LI>SU supplier (retailer)
<LI>PI purchase item
<LI>PU purchase
<LI>SL shopping list
<LI>PS plant species
<LI>PV plant variety
<LI>PN plant note
<LI>PY plant synonym (no longer used)
<LI>HU husbandry
<LI>HC husbandry class
<LI>GA groundwork activity
<LI>GW groundwork
<LI>AC affliction class
<LI>AF affliction
<LI>AE affliction event
<LI>WC weather condition
<LI>WE weather
<LI>WD weather detail
<LI>WS wildlife species
<LI>WI wildlife
<LI>RM reminder
<LI>TD to-do list
<LI>TH story line (thread)(no longer used)
</UL>
	*/
	private final NotebookEntryType parentType;
	private final SimpleObjectProperty<LocalDate> dateProperty = new SimpleObjectProperty<>(this, "date", LocalDate.now());
	private final ChangeListener<LocalDate> dateListener = this::onDateChange;
	private final SimpleStringProperty commentProperty = new SimpleStringProperty(this, "comment", "");
	private final ChangeListener<String> commentListener = this::onCommentChange;
	private final ReadOnlyObjectWrapper<LocalDateTime> lastUpdatedProperty = new ReadOnlyObjectWrapper<>(this, "lastUpdated", LocalDateTime.now());
	private final ReadOnlyObjectWrapper<LocalDateTime> createdProperty = new ReadOnlyObjectWrapper<>(this, "created", LocalDateTime.now());

	/**
	*	Use this constructor to add a new comment(s) to an existing item.
	*
	*	@param	<P>	the type of the parent item
	*	@param	parent	the new comment(s) will be added to this item
	*	@throws	NullPointerException	if the parent parameter is null
	*/
	public <P extends INotebookBean> CommentBean(P parent) throws NullPointerException
	{
		this(parent, null);
	}
	/**
	*	Use this constructor to modify or delete an existing comment on the given item
	*
	*	@param	<P>	the type of the parent item
	*	@param	<T>	the type of the comment affected
	*	@param	parent	the owner of the comment to change
	*	@param	comment	the comment to change or delete; if null a new comment will be added
	*	@throws	NullPointerException	if the parent parameter is null
	*/
	public <P extends INotebookBean, T extends IComment> CommentBean(final P parent, final T comment) throws NullPointerException
	{
		parentItem = parent;
		parentType = parent.getType();
		if(comment == null)
		{
			newItem = true;
			setDefaults();
			addListeners();
			return;
		}

		baseItem = comment;
		newItem = false;
		setValues();

		addListeners();
	}

	/**
	 * Returns the database key of the comment
	 *
	 * @return	the key of the persisted object
	 */
	public int getKey()
	{
		if (newItem) return 0;
		return parentItem.getKey();
	}
	/**
	*	returns the underlying parent of this comment
	*
	*	@return	the underlying parent of this comment
	*/
	public INotebookBean getParent()
	{
		 return getParentValue();
	}

	/**
	*	returns the underlying parent of this comment
	*
	*	@return	the underlying parent of this comment
	*/
	public INotebookBean getParentValue()
	{
		 return parentItem;
	}

	/**
	*	returns the type of the parent of this comment
	*
	*	@return	the type of the parent of this comment
	*/
	public NotebookEntryType getParentType()
	{
		 return parentType;
	}

	/**
	*	returns the underlying Comment, if present
	*
	*	@return	the underlying Comment, if present
	*/
	public Optional<IComment> get()
	{
		 return getValue();
	}

	/**
	*	returns the underlying Comment if present
	*
	*	@return	the underlying Comment, if present
	*/
	public Optional<IComment> getValue()
	{
		 return Optional.ofNullable(baseItem);
	}

	/**
	*	returns the type of object this Bean wraps
	*
	*	@return	the type of object this Bean wraps
	*/
	public NotebookEntryType getType()
	{
		 return NotebookEntryType.COMMENT;
	}

	/**
	*	replace the underlying Comment
	*
	*	@param	item	the new underlying Comment
	*/
	public void setValue(IComment item)
	{
		baseItem = item;
		setValues();
		newItem = (baseItem == null);
	}

	//	2.9.6
	boolean isNew()
	{
		return newItem;
	}

	public LocalDate getDate()
	{
		return dateProperty.get();
	}
	public void setDate(final LocalDate item)
	{
		dateProperty.set(item);
	}
	/**
	*	Wraps the Date value of the Comment
	*
	*	@return	Returns the Date value of the Comment
	*/
	public ObjectProperty<LocalDate> dateProperty()
	{
		return dateProperty;
	}

	/**
	*	Handle changes to the Date value
	*
	*	@throws	GNDBRuntimeException	if the underlying persisted storage engine (e.g. database server) throws an exception
	*				The original error can be retrieved by <code>getCause()</code>
	*/
	private void onDateChange(ObservableValue<? extends LocalDate> obs, LocalDate old, LocalDate nval)
	{
		if (newItem) return;
		if (!(obs instanceof SimpleObjectProperty)) return;

		if (nval == null) return;

		try
		{
			parentItem.changeCommentDate(this, nval);
		} catch (GNDBException ex) {
			throw new GNDBRuntimeException(ex);
		}
	}

	public String getComment()
	{
		return commentProperty.get();
	}
	public void setComment(final String item)
	{
		commentProperty.set(item);
	}
	/**
	*	Wraps the Comment value of the Comment
	*
	*	@return	Returns the Comment value of the Comment
	*/
	public StringProperty commentProperty()
	{
		return commentProperty;
	}

	/**
	*	Handle changes to the Comment value
	*
	*	@throws	GNDBRuntimeException	if the underlying persisted storage engine (e.g. database server) throws an exception
	*				The original error can be retrieved by <code>getCause()</code>
	*/
	private void onCommentChange(ObservableValue<? extends String> obs, String old, String nval)
	{
//		System.out.println("CommentBean: onCommentChange(): old: "+old+", nval: "+nval);
		if (!(obs instanceof SimpleStringProperty)) return;

		if (nval == null || nval.isBlank())	//	2.9.6
		{// disallow blank comment
			setComment(old);
			return;
		}


		if (newItem && (old == null || old.isBlank()))	//	2.9.6
		{// first entry on a new comment
//			System.out.println("CommentBean: onCommentChange(): first entry on a new comment");
			try
			{
				parentItem.addComment(this);    //	2.9.6
			} catch (GNDBException ex) {
				throw new GNDBRuntimeException(ex);
			}
		}
		else
		{
//			System.out.println("CommentBean: onCommentChange(): later entry or on an extant comment");
			try
			{
				parentItem.changeCommentText(this, nval);
			} catch (GNDBException ex) {
				throw new GNDBRuntimeException(ex);
			}
		}
	}

	public LocalDateTime getLastUpdated()
	{
		return lastUpdatedProperty.get();
	}
	/**
	*	Wraps the LastUpdated value of the Comment
	*	Note that this value cannot be changed by the user
	*
	*	@return	Returns the LastUpdated value of the Comment
	*/
	public ReadOnlyObjectProperty<LocalDateTime> lastUpdatedProperty()
	{
		return lastUpdatedProperty.getReadOnlyProperty();
	}

	public LocalDateTime getCreated()
	{
		return createdProperty.get();
	}
	/**
	*	Wraps the Created value of the Comment
	*	Note that this value cannot be changed by the user
	*
	*	@return	Returns the Created value of the Comment
	*/
	public ReadOnlyObjectProperty<LocalDateTime> createdProperty()
	{
		return createdProperty.getReadOnlyProperty();
	}

	private void setDefaults()
	{
		dateProperty.setValue(LocalDate.now());
		commentProperty.setValue("");
		lastUpdatedProperty.setValue(LocalDateTime.now());
		createdProperty.setValue(LocalDateTime.now());
	}

	private void setValues()
	{
		dateProperty.setValue(baseItem.getDate());
		commentProperty.setValue(baseItem.getComment());
		lastUpdatedProperty.setValue(baseItem.getLastUpdated());
		createdProperty.setValue(baseItem.getCreated());
	}

	private void addListeners()
	{
		dateProperty.addListener(dateListener);
		commentProperty.addListener(commentListener);
	}
	@Override
	public String toString()
	{
		return "CommentBean wrapping " + baseItem;
	}

}

