/*
 * 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	Location and Quantity added
			Use FlagHandler
	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 Husbandry implements IHusbandry
{
	private final FlagHandler<IHusbandry> flagHandler;
	{
		flagHandler = new FlagHandler<>(this);
	}

	private final int id;
	private final int husbandryClassId;

	/*
	*	Always required as Husbandry always refers to a plant.
The activity is for plants of this species.
@apiNote
plantVarietyId may or may not be given; if absent the activity is for all (current) e.g. tomato varieties, e.g for spraying.
This 'denormalises' the model but enables easy searches for e.g. 'all tomatoes'
	*/
	private final int plantSpeciesId;

	/*
	*	The activity is for plants of this variety.
@apiNote
If present, plantSpeciesId must be given.  This 'denormalises' the model but enables easy searches for e.g. 'all tomatoes'
	*/
	private final Integer plantVarietyId;

	/*
	*	If this Diary entry is for the demise of a plant, this can be used to record the guilty pest or disease.
	*/
	private final Integer terminalAfflictionId;

	/*
	*	Where the activity took place, e.g. where the plants were planted out.
	 */
	private final Integer locationId;
	private final LocalDate date;

	/*
	*	Quantifies the activity, e.g. how many seeds were sown.
	 */
	private final String quantity;
	private final LocalDateTime lastUpdated;
	private final LocalDateTime created;
	private final List<Comment> commentsList;

	/**
	*	Build an immutable Husbandry entry one field at a time
	*/
	Husbandry(
		final int id,
		final int husbandryClassId,
		final int plantSpeciesId,
		final Integer plantVarietyId,
		final Integer terminalAfflictionId,
		final Integer locationId,
		final LocalDate date,
		final String quantity,
		final LocalDateTime lastUpdated,
		final LocalDateTime created,
		final Comment... comments)
	{
		this.id = id;
		this.husbandryClassId = husbandryClassId;
		this.plantSpeciesId = plantSpeciesId;
		this.plantVarietyId = plantVarietyId;
		this.terminalAfflictionId = terminalAfflictionId;
		this.locationId = locationId;
		this.date = date;
		this.quantity = quantity;
		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 Husbandry entry cloning the given Husbandry entry but adding the comments list
	*/
	Husbandry(
		final Husbandry toCopy,
		final Comment... comments)
	{
		this(toCopy, Arrays.asList(comments));
	}

	/**
	*	Build an immutable Husbandry entry cloning the given Husbandry entry but adding the comments list
	*/
	Husbandry(
		final Husbandry toCopy,
		final List<Comment> comments)
	{
		this.id = toCopy.id;
		this.husbandryClassId = toCopy.husbandryClassId;
		this.plantSpeciesId = toCopy.plantSpeciesId;
		this.plantVarietyId = toCopy.plantVarietyId;
		this.terminalAfflictionId = toCopy.terminalAfflictionId;
		this.locationId = toCopy.locationId;
		this.date = toCopy.date;
		this.quantity = toCopy.quantity;
		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 Husbandry 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.
	*/
	Husbandry(JsonObject json)
	{
		this.id = json.getInt("id", -1);
		this.husbandryClassId = json.getInt("husbandryClassId");
		this.plantSpeciesId = json.getInt("plantSpeciesId");
		if (json.containsKey("plantVarietyId") && !json.isNull("plantVarietyId"))
		{
			this.plantVarietyId = json.getInt("plantVarietyId");
		}
		else
		{
			this.plantVarietyId = null;
		}

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

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

		this.date = LocalDate.parse(json.getString("date"));

		if (json.containsKey("quantity") && !json.isNull("quantity"))
		{
			this.quantity = json.getString("quantity");
		}
		else
		{
			this.quantity = 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.HUSBANDRY;
	}

	int getHusbandryClassId()
	{
		return husbandryClassId;
	}
	@Override
	public IHusbandryClass getHusbandryClass()
	{
		return MySQLCache.cacheHusbandryClass.get(husbandryClassId);
	}

	int getPlantSpeciesId()
	{
		return plantSpeciesId;
	}
	@Override
	public IPlantSpecies getPlantSpecies()
	{
		return MySQLCache.cachePlantSpecies.get(plantSpeciesId);
	}

	Integer getPlantVarietyId()
	{
		return plantVarietyId;
	}
	@Override
	public Optional<IPlantVariety> getPlantVariety()
	{
		return Optional.ofNullable(MySQLCache.cachePlantVariety.get(plantVarietyId));
	}

	Integer getTerminalAfflictionId()
	{
		return terminalAfflictionId;
	}
	@Override
	public Optional<IAffliction> getAffliction()
	{
		return Optional.ofNullable(MySQLCache.cacheAffliction.get(terminalAfflictionId));
	}

	Integer getLocationId()
	{
		return locationId;
	}
	@Override
	public Optional<ILocation> getLocation()
	{
		return Optional.ofNullable(MySQLCache.cacheLocation.get(locationId));
	}

	@Override
	public LocalDate getDate()
	{
		return date;
	}

	@Override
	public Optional<String> getQuantity()
	{
		return Optional.ofNullable(quantity);
	}

	@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 IToDoListLister getToDoList()
	{
		return new ToDoListLister().husbandry(this);
	}

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

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

	JsonObjectBuilder toJson(JsonBuilderFactory jsonFactory)
	{
		JsonObjectBuilder jsonBuilder = jsonFactory.createObjectBuilder();
		jsonBuilder.add("id", id);
		jsonBuilder.add("husbandryClassId", husbandryClassId);
		jsonBuilder.add("plantSpeciesId", plantSpeciesId);
		if (plantVarietyId != null)
		{
			jsonBuilder.add("plantVarietyId", plantVarietyId);
		}
		else
		{
			jsonBuilder.addNull("plantVarietyId");
		}
		if (terminalAfflictionId != null)
		{
			jsonBuilder.add("terminalAfflictionId", terminalAfflictionId);
		}
		else
		{
			jsonBuilder.addNull("terminalAfflictionId");
		}

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

		jsonBuilder.add("date", date.toString());

		if (quantity != null)
		{
			jsonBuilder.add("quantity", quantity);
		}
		else
		{
			jsonBuilder.addNull("quantity");
		}
		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", "Husbandry");
		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 IHusbandry newValue)
	{
		flagHandler.flagReplaced(newValue, newValue::addPropertyChangeListener);
	}

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

	@Override
	public void flagChildAdded(final IToDoList child)
	{
		flagHandler.flagChildAdded("ToDoList", 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 String toString() {
		return "Husbandry: " + "id: " + id + ", " +
				"husbandryClassId: " + husbandryClassId + ", " +
				"plantSpeciesId: " + plantSpeciesId + ", " +
				"plantVarietyId: " + plantVarietyId + ", " +
				"terminalAfflictionId: " + terminalAfflictionId + ", " +
				"locationId: " + locationId + ", " +
				"date: " + date + ", " +
				"quantity: " + quantity + ", " +
				"lastUpdated: " + lastUpdated + ", " +
				"created: " + created;
	}

}
