/*
 * 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	Support 'watch for' entries on PurchaseItems
	3.1.0	Use jakarta implementation of JSON
 */

package uk.co.gardennotebook.mysql;

import java.beans.PropertyChangeListener;

import uk.co.gardennotebook.spi.*;

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

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 ToDoList implements IToDoList
{
	private final FlagHandler<IToDoList> flagHandler;
	{
		flagHandler = new FlagHandler<>(this);
	}

	private final int id;
	private final Integer plantSpeciesId;
	private final Integer plantVarietyId;
	private final Integer husbandryClassId;
	private final Integer groundWorkActivityId;

	/*
	*	The Husbandry editor can set up a ToDoList entry intended as a 'watch for this to happen' (e.g. on sowing, add a 'watch for germination').<BR>
This field is the id of that parent/ancestor Husbandry event so when this ToDoList entry is processed the appropriate Storyline links can be set up.
	*/
	private final Integer husbandryId;

	/*
	*	The Purchase editor can set up a ToDoList entry intended as a 'watch for this to happen' (e.g. for a seed purchase, watch for sowing).<BR>
This field is the id of that parent/ancestor PurchaseItem event so when this ToDoList entry is processed the appropriate Storyline links can be set up.
	*/
	private final Integer purchaseItemId;
	private final String description;
	private final LocalDateTime lastUpdated;
	private final LocalDateTime created;
	private final List<Comment> commentsList;

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

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

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

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

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

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

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

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

	Integer getPlantSpeciesId()
	{
		return plantSpeciesId;
	}
	@Override
	public Optional<IPlantSpecies> getPlantSpecies()
	{
		return Optional.ofNullable(MySQLCache.cachePlantSpecies.get(plantSpeciesId));
	}

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

	Integer getHusbandryClassId()
	{
		return husbandryClassId;
	}
	@Override
	public Optional<IHusbandryClass> getHusbandryClass()
	{
		return Optional.ofNullable(MySQLCache.cacheHusbandryClass.get(husbandryClassId));
	}

	Integer getGroundWorkActivityId()
	{
		return groundWorkActivityId;
	}
	@Override
	public Optional<IGroundworkActivity> getGroundworkActivity()
	{
		return Optional.ofNullable(MySQLCache.cacheGroundworkActivity.get(groundWorkActivityId));
	}

	Integer getHusbandryId()
	{
		return husbandryId;
	}
	@Override
	public Optional<IHusbandry> getHusbandry()
	{
		return Optional.ofNullable(MySQLCache.cacheHusbandry.get(husbandryId));
	}

	Integer getPurchaseItemId()
	{
		return purchaseItemId;
	}
	@Override
	public Optional<IPurchaseItem> getPurchaseItem()
	{
		return Optional.ofNullable(MySQLCache.cachePurchaseItem.get(purchaseItemId));
	}

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

	@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 List<IComment> getComments() {
		return new ArrayList<>(this.commentsList);
	}

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

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

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

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


	@Override
	public String toString() {
		return "ToDoList: " + "id: " + id + ", " +
				"plantSpeciesId: " + plantSpeciesId + ", " +
				"plantVarietyId: " + plantVarietyId + ", " +
				"husbandryClassId: " + husbandryClassId + ", " +
				"groundWorkActivityId: " + groundWorkActivityId + ", " +
				"husbandryId: " + husbandryId + ", " +
				"purchaseItemId: " + purchaseItemId + ", " +
				"description: " + description + ", " +
				"lastUpdated: " + lastUpdated + ", " +
				"created: " + created;
	}

}
