/*
 * Copyright (C) 2018, 2019, 2021, 2022 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.0.1   Add function to preload the cache with PlantVarieties
    2.9.6	When a Diary entry is added/changed, make sure updated comments are shown
    3.0.0	Java syntax upgrades
    		Support for crop rotation groups
 */

package uk.co.gardennotebook;

import uk.co.gardennotebook.fxbean.*;
import uk.co.gardennotebook.spi.TrugServer;
import uk.co.gardennotebook.spi.NotebookEntryType;
import uk.co.gardennotebook.spi.IPlantSpecies;
import uk.co.gardennotebook.spi.IPlantSpeciesLister;
import uk.co.gardennotebook.spi.INotebookEntry;
import uk.co.gardennotebook.spi.IPlantVariety;
import uk.co.gardennotebook.spi.ITrug;
//import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
//import javafx.beans.InvalidationListener;
//import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
//import javafx.beans.property.SimpleObjectProperty;
//import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

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;

/**
 *	An aggregate and facade on the various plant specific classes.
 *	Used to show the plant catalogue
 * 
	*	@author	Andy Gegg
	*	@version	3.0.0
	*	@since	1.0
 */
final class PlantCatalogueBean {
	
	private static final Logger LOGGER = LogManager.getLogger();
    
	private PlantSpeciesBean plantSpecies = null;
	private PlantVarietyBean plantVariety = null;
//	private PlantSynonymBean plantSynonym = null;
	
	private final NotebookEntryType nodeType;	// PS for PlantSpecies, PV for PlantVariety, PY for PlantSynonym
	
	private ObservableList<PlantCatalogueBean> speciesChildren = null;
//	private ObservableList<PlantCatalogueBean> varietyChildren = null;
	
	private StringProperty commonName = null;
	private StringProperty latinName = null;
	private StringProperty description = null;
	private StringProperty utility = null;
	private StringProperty hardiness = null;
	private StringProperty lifeType = null;
	private StringProperty plantType = null;
//	private SimpleObjectProperty<ObservableList<CommentBean>> comments = null;	//	2.9.6
	private ReadOnlyBooleanWrapper canDeleteProperty = null;
	
	PlantCatalogueBean(IPlantSpecies val)
	{
		this(new PlantSpeciesBean(val));
	}
	
	PlantCatalogueBean(PlantSpeciesBean val)
	{
		plantSpecies = val;
		nodeType = NotebookEntryType.PLANTSPECIES;
		commonName = plantSpecies.commonNameProperty();
		latinName = plantSpecies.latinNameProperty();
		description = plantSpecies.descriptionProperty();
		utility = plantSpecies.utilityProperty();
		hardiness = plantSpecies.hardinessProperty();
		lifeType = plantSpecies.lifeTypeProperty();
		plantType = plantSpecies.plantTypeProperty();
	}
	
	PlantCatalogueBean(IPlantVariety val)
	{
		this(new PlantVarietyBean(val));
	}
	
	PlantCatalogueBean(PlantVarietyBean val)
	{	
		plantVariety = val;
		nodeType = NotebookEntryType.PLANTVARIETY;
		commonName = plantVariety.commonNameProperty();
		latinName = plantVariety.latinNameProperty();
		description = plantVariety.descriptionProperty();
		utility = plantVariety.utilityProperty();
		hardiness = plantVariety.hardinessProperty();
		lifeType = plantVariety.lifeTypeProperty();
		plantType = plantVariety.plantTypeProperty();
	}
	
    /**
     * Return the PlantSpecies wrapped as PlantCatalogueBeans
     * 
     * @return a list of PlantCatalogueBeans wrapping PlantSpecies
     */
	static ObservableList<PlantCatalogueBean> getPlantCatalogue()
	{
        return getPlantCatalogue(null,false);
	}
	
    /**
     * Return the PlantSpecies wrapped as PlantCatalogueBeans
     * 
     * @param   preLoadVars if true, also read the PlantVarieties into the cache.
     *                      NB the PlantVarieties are NOT returned in the list
     * 
     * @return a list of PlantCatalogueBeans wrapping PlantSpecies
     * 
     * @since 2.0.1
     */
	static ObservableList<PlantCatalogueBean> getPlantCatalogue(boolean preLoadVars)
	{
		return getPlantCatalogue(null, preLoadVars);
	}

	/**
	 * Return the PlantSpecies wrapped as PlantCatalogueBeans
	 *
	 * @param crg 	only return plant species in this crop rotation group.
	 *              A null value will return all plant species.
	 * @param   preLoadVars if true, also read the PlantVarieties into the cache.
	 *                      NB the PlantVarieties are NOT returned in the list
	 *
	 * @return a list of PlantCatalogueBeans wrapping PlantSpecies
	 *
	 * @since 3.0.0
	 */
	static ObservableList<PlantCatalogueBean> getPlantCatalogue(CropRotationGroupBean crg, boolean preLoadVars)
	{
		LOGGER.debug("getPlantCatalogue(): crg: {}, preLoadVars: {}", crg, preLoadVars);

		ITrug server = TrugServer.getTrugServer().getTrug();
		if (preLoadVars)
		{
			try {
				server.getPlantVarietyLister().fetch();
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
		}
		IPlantSpeciesLister gal = server.getPlantSpeciesLister();
		if (crg != null)
		{
			gal = gal.cropRotationGroup(crg.get().get());
		}
		ObservableList<PlantCatalogueBean> pc = FXCollections.observableArrayList();

		//	need to keep these 2 steps separate or the compiler gets confused (is it addAll(Collection) or addAll(T ...)?)
		List<PlantCatalogueBean> ll = Collections.emptyList();
		try {
			ll = gal.fetch().stream().map(PlantCatalogueBean::new).toList();
		} catch (GNDBException ex) {
			PanicHandler.panic(ex);
		}
		pc.addAll(ll);

		return pc;
	}

	INotebookBean getItem()
	{
		switch (nodeType)
		{
			case PLANTSPECIES: return plantSpecies;
			case PLANTVARIETY: return plantVariety;
			default:
				LOGGER.debug("getItem(): Unknown node type: {}", nodeType);
				return null;
		}
	}
	
	INotebookEntry getBaseItem()
	{
		switch (nodeType)
		{
			case PLANTSPECIES: return plantSpecies.get().orElse(null);
			case PLANTVARIETY: return plantVariety.get().orElse(null);
			default:
				LOGGER.debug("getBaseItem(): Unknown node type: {}", nodeType);
				return null;
		}
	}
	
	NotebookEntryType getItemType()
	{
		return nodeType;
	}
	
	public final ReadOnlyBooleanProperty canDeleteProperty()
	{
		if (canDeleteProperty == null)
		{

			try
			{
				return switch (nodeType)
						{
							case PLANTSPECIES -> plantSpecies.canDeleteProperty();
							case PLANTVARIETY -> plantVariety.canDeleteProperty();
							default -> {
								LOGGER.debug("getItemType(): Unknown node type: {}", nodeType);
								yield  null;
							}
						};
			} catch (GNDBException ex) {
				PanicHandler.panic(ex);
			}
		}
		return canDeleteProperty.getReadOnlyProperty();
	}
	
	String getCommonName()
	{
		return commonNameProperty().getValue();
	}
	
	void setCommonName(String val)
	{
		commonNameProperty().setValue(val);
	}
	
	StringProperty commonNameProperty()
	{
		return commonName;
	}

	final String getLatinName()
	{
		return latinNameProperty().getValue();
	}
	
	final void setLatinName(String val)
	{
		latinNameProperty().setValue(val);
	}
	
	StringProperty latinNameProperty()
	{
		return latinName;
	}
	
	final String getDescription()
	{
		return descriptionProperty().getValue();
	}
	
	final void setDescription(String val)
	{
		descriptionProperty().setValue(val);
	}
	
	StringProperty descriptionProperty()
	{
		return description;
	}
	
	String getUtility()
	{
		return utilityProperty().getValue();
	}
	
	void setUtility(String val)
	{
		utilityProperty().setValue(val);
	}
	
	StringProperty utilityProperty()
	{
		return utility;
	}

	String getHardiness()
	{
		return hardinessProperty().getValue();
	}
	
	void setHardiness(String val)
	{
		hardinessProperty().setValue(val);
	}
	
	StringProperty hardinessProperty()
	{
		return hardiness;
	}

	String getLifeType()
	{
		return lifeTypeProperty().getValue();
	}
	
	void setLifeType(String val)
	{
		lifeTypeProperty().setValue(val);
	}
	
	StringProperty lifeTypeProperty()
	{
		return lifeType;
	}

	String getPlantType()
	{
		return plantTypeProperty().getValue();
	}
	
	void setPlantType(String val)
	{
		plantTypeProperty().setValue(val);
	}
	
	StringProperty plantTypeProperty()
	{
		return plantType;
	}

	//	2.9.6
	final ObservableList<CommentBean> getComments()
	{
		return switch (nodeType)
				{
					case PLANTSPECIES -> plantSpecies.getComments();
					case PLANTVARIETY -> plantVariety.getComments();
					default -> {
						LOGGER.debug("getCommentList(): Unknown node type: {}", nodeType);
						yield FXCollections.emptyObservableList();
					}
				};
	}

	ObservableList<PlantCatalogueBean> getChildren()
	{
		switch (nodeType)
		{
			case PLANTSPECIES: 
				if (speciesChildren == null)
				{
					LOGGER.debug("getChildren(): fetching children of species: speciesChildren is null");
					speciesChildren = FXCollections.observableArrayList();
					ObservableList<PlantVarietyBean> theKids = FXCollections.emptyObservableList();
					try {
						theKids = plantSpecies.getPlantVariety();
					} catch (GNDBException ex) {
						PanicHandler.panic(ex);
					}
					theKids.addListener(new ListChangeListener<PlantVarietyBean>(){
						@Override
						public void onChanged(ListChangeListener.Change<? extends PlantVarietyBean> c) {
							LOGGER.debug("getChildren(): ListChangeListener: onChanged()");
							while (c.next())
							{
								LOGGER.debug("getChildren(): ListChangeListener: onChanged(): top of loop");
								if (c.wasPermutated())
								{
									LOGGER.debug("getChildren(): ListChangeListener: onChanged(): wasPermutated()");
									return;
								}
								if (c.wasRemoved())
								{
									LOGGER.debug("getChildren(): ListChangeListener: onChanged(): wasRemoved()");
									LOGGER.debug("getChildren(): ListChangeListener: onChanged(): from: {}, to:{}", c.getFrom(), c.getTo());
									speciesChildren.remove(c.getFrom(), c.getTo());
									return;
								}
								if (c.wasAdded())
								{
									LOGGER.debug("PlantCatalogueBean: getChildren(): ListChangeListener: onChanged(): wasAdded()");
									int from = c.getFrom();
									for (PlantVarietyBean pv : c.getAddedSubList())
									{
										LOGGER.debug("getChildren(): ListChangeListener: onChanged(): wasAdded(): PV: {}", pv);
										speciesChildren.add(from++, new PlantCatalogueBean(pv));
									}
									return;
								}
							}
						}
					});
//	3.0.4 duplicate code!
//					ObservableList<PlantVarietyBean> pvbList = FXCollections.emptyObservableList();
//					try {
//						pvbList = plantSpecies.getPlantVariety();
//					} catch (GNDBException ex) {
//						PanicHandler.panic(ex);
//					}
//					for (PlantVarietyBean pv : pvbList)
					for (PlantVarietyBean pv : theKids)
					{
						LOGGER.debug("have child (variety): {}", pv);
						speciesChildren.add(new PlantCatalogueBean(pv));
					}
					LOGGER.debug("speciesChildren: "+ speciesChildren);
				}
				return speciesChildren;
			case PLANTVARIETY:
				return FXCollections.emptyObservableList();
			default:
				LOGGER.debug("getChildren(): Unknown node type: {}", nodeType);
				return FXCollections.emptyObservableList();
		}
	}
	
	boolean isLeaf()
	{
		// this is called from isLeaf() on the TreeItem A LOT so actually getting the children is delayed until requested
		switch (nodeType)
		{
			case PLANTSPECIES: 
				if (speciesChildren == null)
				{
					return false;
				}
				return speciesChildren.isEmpty();
			case PLANTVARIETY:
				return true;
		}
				LOGGER.debug("isLeaf(): Unknown node type: {}", nodeType);
		return true;
	}
	
	@Override
	public String toString()
	{
		return switch (nodeType)
				{
					case PLANTSPECIES -> "PlantCatalogueBean of: " + plantSpecies.toString();
					case PLANTVARIETY -> "PlantCatalogueBean of: " + plantVariety.toString();
					default -> "Unknown type: " + nodeType;
				};
	}
}
