/*
 * Copyright (C) 2018, 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
	3.0.0	Use FlagHandler
			Handle CroppingActual
	3.1.0	Use jakarta implementation of JSON
  */

package uk.co.gardennotebook.mysql;

import java.beans.PropertyChangeListener;

import uk.co.gardennotebook.spi.*;

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

import java.time.*;
import java.util.Optional;

import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonArray;
import jakarta.json.JsonObjectBuilder;
import jakarta.json.JsonObject;

/**
*{@inheritDoc}
*
*	@author	Andy Gegg
*	@version	3.1.0
*	@since	1.0
*/

final class PlantSpecies implements IPlantSpecies
{
	private final FlagHandler<IPlantSpecies> flagHandler;
	{
		flagHandler = new FlagHandler<>(this);
	}

	private final int id;

	/*
	*	The species falls into this CropRotationGroup.
	 */
	Integer cropRotationGroupId;

	/*
	*	The name by which plants of this type are usually known.
	*/
	private final String commonName;

	/*
	*	The formal horticultural name for this species.
The common name is language specific, the latin name is internationally standardised.
	*/
	private final String latinName;
	private final String description;

	/*
	*	The plant's function in the garden, typically vegetable, ornamental, weed.
	*/
	private final String utility;

	/*
	*	The plant's hardiness or tenderness, typically hardy, half hardy, tender.
	*/
	private final String hardiness;

	/*
	*	Typically annual, biennial or perennial.  Variations such as 'perennial grown as annual' (e.g. runner bean).
	*/
	private final String lifeType;

	/*
	*	Such as climber, shrub, tree.
	*/
	private final String plantType;
	private final LocalDateTime lastUpdated;
	private final LocalDateTime created;
	private final List<Comment> commentsList;

	/**
	*	Build an immutable PlantSpecies entry one field at a time
	*/
	PlantSpecies(
		final int id,
		final Integer cropRotationGroupId,
		final String commonName,
		final String latinName,
		final String description,
		final String utility,
		final String hardiness,
		final String lifeType,
		final String plantType,
		final LocalDateTime lastUpdated,
		final LocalDateTime created,
		final Comment... comments)
	{
		this.id = id;
		this.cropRotationGroupId = cropRotationGroupId;
		this.commonName = commonName;
		this.latinName = latinName;
		this.description = description;
		this.utility = utility;
		this.hardiness = hardiness;
		this.lifeType = lifeType;
		this.plantType = plantType;
		this.lastUpdated = lastUpdated;
		this.created = created;
		if (comments != null && comments.length>0)
		{
			this.commentsList = new ArrayList<>(Arrays.asList(comments));
		}
		else
		{
			this.commentsList = Collections.emptyList();
		}
	}

	/**
	*	Build an immutable PlantSpecies entry cloning the given PlantSpecies entry but adding the comments list
	*/
	PlantSpecies(
		final PlantSpecies toCopy,
		final Comment... comments)
	{
		this(toCopy, Arrays.asList(comments));
	}

	/**
	*	Build an immutable PlantSpecies entry cloning the given PlantSpecies entry but adding the comments list
	*/
	PlantSpecies(
		final PlantSpecies toCopy,
		final List<Comment> comments)
	{
		this.id = toCopy.id;
		this.cropRotationGroupId = toCopy.cropRotationGroupId;
		this.commonName = toCopy.commonName;
		this.latinName = toCopy.latinName;
		this.description = toCopy.description;
		this.utility = toCopy.utility;
		this.hardiness = toCopy.hardiness;
		this.lifeType = toCopy.lifeType;
		this.plantType = toCopy.plantType;
		this.lastUpdated = toCopy.lastUpdated;
		this.created = toCopy.created;
		if (comments != null && comments.size()>0)
		{
			if (toCopy.commentsList.size()>0)
			{
				// append new comments to previous list
				this.commentsList = new ArrayList<>(toCopy.commentsList);
				this.commentsList.addAll(comments);
			}
			else
			{	// no comments on original item
				this.commentsList = new ArrayList<>(comments);
			}
		}
		else
		{	// no new comments to add
			this.commentsList = toCopy.commentsList;
		}
	}

	/**
	*	Build an immutable PlantSpecies entry from a JSON dump.
	*	The dumped object must be complete (all non-nullable fields must have values) except
	*	the id field can be null or absent to indicate that this is a new item to be inserted.
	*
	*	@param	json	a JsonObject holding all the fields for a full initialisation.
	*/
	PlantSpecies(JsonObject json)
	{
		this.id = json.getInt("id", -1);
		this.commonName = json.getString("commonName");

		if (json.containsKey("cropRotationGroupId") && !json.isNull("cropRotationGroupId"))
		{
			this.cropRotationGroupId = json.getInt("cropRotationGroupId");
		}
		else
		{
			this.cropRotationGroupId = null;
		}

		if (json.containsKey("latinName") && !json.isNull("latinName"))
		{
			this.latinName = json.getString("latinName");
		}
		else
		{
			this.latinName = null;
		}

		if (json.containsKey("description") && !json.isNull("description"))
		{
			this.description = json.getString("description");
		}
		else
		{
			this.description = null;
		}

		if (json.containsKey("utility") && !json.isNull("utility"))
		{
			this.utility = json.getString("utility");
		}
		else
		{
			this.utility = null;
		}

		if (json.containsKey("hardiness") && !json.isNull("hardiness"))
		{
			this.hardiness = json.getString("hardiness");
		}
		else
		{
			this.hardiness = null;
		}

		if (json.containsKey("lifeType") && !json.isNull("lifeType"))
		{
			this.lifeType = json.getString("lifeType");
		}
		else
		{
			this.lifeType = null;
		}

		if (json.containsKey("plantType") && !json.isNull("plantType"))
		{
			this.plantType = json.getString("plantType");
		}
		else
		{
			this.plantType = null;
		}

		this.lastUpdated = LocalDateTime.parse(json.getString("lastUpdated"));
		this.created = LocalDateTime.parse(json.getString("created"));
		JsonArray jsonComments = json.getJsonArray("comments");
		if (jsonComments != null && !jsonComments.isEmpty())
		{// there is probably only one comment
			this.commentsList = new ArrayList<>(jsonComments.size());
			for (JsonObject ct : jsonComments.getValuesAs(JsonObject.class))
			{
				this.commentsList.add(new Comment(ct));
			}
		}
		else
		{
			this.commentsList = Collections.emptyList();
		}
	}	//constructor from JSON

	int getId()
	{
		return id;
	}
	@Override
	public Integer getKey()
	{
		return id;
	}

	@Override
	public NotebookEntryType getType()
	{
		return NotebookEntryType.PLANTSPECIES;
	}

	Integer getCropRotationGroupId()
	{
		return cropRotationGroupId;
	}
	@Override
	public Optional<ICropRotationGroup> getCropRotationGroup()
	{
		return Optional.ofNullable(MySQLCache.cacheCropRotationGroup.get(cropRotationGroupId));
	}

	@Override
	public String getCommonName()
	{
		return commonName;
	}

	@Override
	public Optional<String> getLatinName()
	{
		return Optional.ofNullable(latinName);
	}

	@Override
	public Optional<String> getDescription()
	{
		return Optional.ofNullable(description);
	}

	@Override
	public Optional<String> getUtility()
	{
		return Optional.ofNullable(utility);
	}

	@Override
	public Optional<String> getHardiness()
	{
		return Optional.ofNullable(hardiness);
	}

	@Override
	public Optional<String> getLifeType()
	{
		return Optional.ofNullable(lifeType);
	}

	@Override
	public Optional<String> getPlantType()
	{
		return Optional.ofNullable(plantType);
	}

	@Override
	public LocalDateTime getLastUpdated()
	{
		return lastUpdated;
	}

	@Override
	public LocalDateTime getCreated()
	{
		return created;
	}

	@Override
	public boolean sameAs(INotebookEntry other)
	{
		if (other == null || other.getType() != this.getType())
		{
			return false;
		}
		return other.getKey().equals(this.getKey());
	}

	@Override
	public IAfflictionEventLister getAfflictionEvent()
	{
		return new AfflictionEventLister().plantSpecies(this);
	}

	@Override
	public IGroundworkLister getGroundwork()
	{
		return new GroundworkLister().plantSpecies(this);
	}

	@Override
	public IHusbandryLister getHusbandry()
	{
		return new HusbandryLister().plantSpecies(this);
	}

	@Override
	public IPlantNoteLister getPlantNote()
	{
		return new PlantNoteLister().plantSpecies(this);
	}

	@Override
	public IPlantVarietyLister getPlantVariety()
	{
		return new PlantVarietyLister().plantSpecies(this);
	}

	@Override
	public IProductLister getProduct()
	{
		return new ProductLister().plantSpecies(this);
	}

	@Override
	public IReminderLister getReminder()
	{
		return new ReminderLister().plantSpecies(this);
	}

	@Override
	public ISaleItemLister getSaleItem()
	{
		return new SaleItemLister().plantSpecies(this);
	}

	@Override
	public IToDoListLister getToDoList()
	{
		return new ToDoListLister().plantSpecies(this);
	}

	@Override
	public ICroppingActualLister getCroppingActual()
	{
		return new CroppingActualLister().plantSpecies(this);
	}

	//	TODO - getReview()
//	@Override
//	public IReviewLister getReview()
//	{
//		return new ReviewLister().plantSpecies(this);
//	}

	@Override
	public List<IComment> getComments() {
		return new ArrayList<>(this.commentsList);
	}

	JsonObjectBuilder toJson(JsonBuilderFactory jsonFactory)
	{
		JsonObjectBuilder jsonBuilder = jsonFactory.createObjectBuilder();
		jsonBuilder.add("id", id);

		if (cropRotationGroupId != null)
		{
			jsonBuilder.add("cropRotationGroupId", cropRotationGroupId);
		}
		else
		{
			jsonBuilder.addNull("cropRotationGroupId");
		}

		jsonBuilder.add("commonName", commonName);
		if (latinName != null)
		{
			jsonBuilder.add("latinName", latinName);
		}
		else
		{
			jsonBuilder.addNull("latinName");
		}
		if (description != null)
		{
			jsonBuilder.add("description", description);
		}
		else
		{
			jsonBuilder.addNull("description");
		}
		if (utility != null)
		{
			jsonBuilder.add("utility", utility);
		}
		else
		{
			jsonBuilder.addNull("utility");
		}
		if (hardiness != null)
		{
			jsonBuilder.add("hardiness", hardiness);
		}
		else
		{
			jsonBuilder.addNull("hardiness");
		}
		if (lifeType != null)
		{
			jsonBuilder.add("lifeType", lifeType);
		}
		else
		{
			jsonBuilder.addNull("lifeType");
		}
		if (plantType != null)
		{
			jsonBuilder.add("plantType", plantType);
		}
		else
		{
			jsonBuilder.addNull("plantType");
		}
		jsonBuilder.add("lastUpdated", lastUpdated.toString());
		jsonBuilder.add("created", created.toString());
		if (commentsList != null && !commentsList.isEmpty())
		{// no point writing an empty comments array (the loaders handle this)
			JsonArrayBuilder jsonComments = jsonFactory.createArrayBuilder();
			for (Comment ct : commentsList)
			{
				jsonComments.add(ct.toJson(jsonFactory));
			}
			jsonBuilder.add("comments", jsonComments);
		}
		jsonBuilder.add("JsonMode", "DUMP");
        jsonBuilder.add("JsonNBClass", "PlantSpecies");
		return jsonBuilder;
	}	//	toJson

	@Override
	public void addPropertyChangeListener(final String propertyName, final PropertyChangeListener listener)
	{
		flagHandler.addPropertyChangeListener(propertyName, listener);
	}

	@Override
	public void removePropertyChangeListener(final String propertyName, final PropertyChangeListener listener)
	{
		flagHandler.removePropertyChangeListener(propertyName, listener);
	}

	@Override
	public void flagDeleted()
	{
		flagHandler.flagDeleted();
	}

	@Override
	public void flagReplaced(final IPlantSpecies newValue)
	{
		flagHandler.flagReplaced(newValue, newValue::addPropertyChangeListener);
	}

	@Override
	public void flagChildDeleted(final IAfflictionEvent child) {
		flagHandler.flagChildDeleted("AfflictionEvent", child);	}

	@Override
	public void flagChildAdded(final IAfflictionEvent child) {
		flagHandler.flagChildAdded("AfflictionEvent", child);
	}

	@Override
	public void flagChildDeleted(final IGroundwork child)
	{
		flagHandler.flagChildDeleted("Groundwork", child);	}

	@Override
	public void flagChildAdded(final IGroundwork child)
	{
		flagHandler.flagChildAdded("Groundwork", child);
	}

	@Override
	public void flagChildDeleted(final IHusbandry child)
	{
		flagHandler.flagChildDeleted("Husbandry", child);	}

	@Override
	public void flagChildAdded(final IHusbandry child)
	{
		flagHandler.flagChildAdded("Husbandry", child);
	}

	@Override
	public void flagChildDeleted(final IPlantNote child)
	{
		flagHandler.flagChildDeleted("PlantNote", child);	}

	@Override
	public void flagChildAdded(final IPlantNote child)
	{
		flagHandler.flagChildAdded("PlantNote", child);
	}

	@Override
	public void flagChildDeleted(final IPlantVariety child)
	{
		flagHandler.flagChildDeleted("PlantVariety", child);	}

	@Override
	public void flagChildAdded(final IPlantVariety child)
	{
		flagHandler.flagChildAdded("PlantVariety", child);
	}

	@Override
	public void flagChildDeleted(final IProduct child)
	{
		flagHandler.flagChildDeleted("Product", child);	}

	@Override
	public void flagChildAdded(final IProduct child)
	{
		flagHandler.flagChildAdded("Product", child);
	}

	@Override
	public void flagChildDeleted(final IReminder child)
	{
		flagHandler.flagChildDeleted("Reminder", child);	}

	@Override
	public void flagChildAdded(final IReminder child)
	{
		flagHandler.flagChildAdded("Reminder", child);
	}

	@Override
	public void flagChildDeleted(final ISaleItem child)
	{
		flagHandler.flagChildDeleted("SaleItem", child);	}

	@Override
	public void flagChildAdded(final ISaleItem child)
	{
		flagHandler.flagChildAdded("SaleItem", child);
	}

	@Override
	public void flagChildDeleted(final IToDoList child)
	{
		flagHandler.flagChildDeleted("ToDoList", child);	}

	@Override
	public void flagChildAdded(final IToDoList child)
	{
		flagHandler.flagChildAdded("ToDoList", child);
	}

	//	TODO - Review
//	@Override
//	public void flagChildDeleted(final IReview child)
//	{
//		flagHandler.flagChildDeleted("Review", child);	}
//
//	@Override
//	public void flagChildAdded(final IReview child)
//	{
//		flagHandler.flagChildAdded("Review", child);
//	}

	@Override
	public void flagChildDeleted(final ICroppingActual child)
	{
		flagHandler.flagChildDeleted("CroppingActual", child);	}

	@Override
	public void flagChildAdded(final ICroppingActual child)
	{
		flagHandler.flagChildAdded("CroppingActual", child);
	}

	@Override
	public String toString() {
		return "PlantSpecies: " + "id: " + id + ", " +
				"cropRotationGroupId: " + cropRotationGroupId + ", " +
				"commonName: " + commonName + ", " +
				"latinName: " + latinName + ", " +
				"description: " + description + ", " +
				"utility: " + utility + ", " +
				"hardiness: " + hardiness + ", " +
				"lifeType: " + lifeType + ", " +
				"plantType: " + plantType + ", " +
				"lastUpdated: " + lastUpdated + ", " +
				"created: " + created;
	}

}
