/*
 * 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
	3.1.0	Use jakarta implementation of JSON
 */

package uk.co.gardennotebook.mysql;

import java.beans.PropertyChangeListener;

import uk.co.gardennotebook.spi.*;

import uk.co.gardennotebook.util.SimpleMoney;

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

//import java.time.*;
import java.math.BigDecimal;

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

	private final int id;
	private final int retailerId;
	private final LocalDate date;
	private final BigDecimal totalCost;

	/*
	*	ISO 4217 standard currency code (GBP, USD, EUR, etc).  Null means the local currency.
	*/
	private final String currency;
	private final String orderNo;
	private final String invoiceNo;
	private final LocalDate deliveryDate;
	private final LocalDateTime lastUpdated;
	private final LocalDateTime created;
	private final List<Comment> commentsList;

	/**
	*	Build an immutable Purchase entry one field at a time
	*/
	Purchase(
		final int id,
		final int retailerId,
		final LocalDate date,
		final BigDecimal totalCost,
		final String currency,
		final String orderNo,
		final String invoiceNo,
		final LocalDate deliveryDate,
		final LocalDateTime lastUpdated,
		final LocalDateTime created,
		final Comment... comments)
	{
		this.id = id;
		this.retailerId = retailerId;
		this.date = date;
		this.totalCost = totalCost;
		this.currency = currency;
		this.orderNo = orderNo;
		this.invoiceNo = invoiceNo;
		this.deliveryDate = deliveryDate;
		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 Purchase entry cloning the given Purchase entry but adding the comments list
	*/
	Purchase(
		final Purchase toCopy,
		final Comment... comments)
	{
		this(toCopy, Arrays.asList(comments));
	}

	/**
	*	Build an immutable Purchase entry cloning the given Purchase entry but adding the comments list
	*/
	Purchase(
		final Purchase toCopy,
		final List<Comment> comments)
	{
		this.id = toCopy.id;
		this.retailerId = toCopy.retailerId;
		this.date = toCopy.date;
		this.totalCost = toCopy.totalCost;
		this.currency = toCopy.currency;
		this.orderNo = toCopy.orderNo;
		this.invoiceNo = toCopy.invoiceNo;
		this.deliveryDate = toCopy.deliveryDate;
		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 Purchase 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.
	*/
	Purchase(JsonObject json)
	{
		this.id = json.getInt("id", -1);
		this.retailerId = json.getInt("retailerId");
		this.date = LocalDate.parse(json.getString("date"));
		if (json.containsKey("totalCost") && !json.isNull("totalCost"))
		{
			this.totalCost = json.getJsonNumber("totalCost").bigDecimalValue();
		}
		else
		{
			this.totalCost = null;
		}

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

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

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

		if (json.containsKey("deliveryDate") && !json.isNull("deliveryDate"))
		{// arg to .parse must NOT be null
			this.deliveryDate = LocalDate.parse(json.getString("deliveryDate"));
		}
		else
		{
			this.deliveryDate = 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.PURCHASE;
	}

	int getRetailerId()
	{
		return retailerId;
	}
	@Override
	public IRetailer getRetailer()
	{
		return MySQLCache.cacheRetailer.get(retailerId);
	}

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

	@Override
	public Optional<BigDecimal> getTotalCost()
	{
		return Optional.ofNullable(totalCost);
	}

	@Override
	public Optional<String> getCurrency()
	{
		return Optional.ofNullable(currency);
	}

	@Override
	public Optional<String> getOrderNo()
	{
		return Optional.ofNullable(orderNo);
	}

	@Override
	public Optional<String> getInvoiceNo()
	{
		return Optional.ofNullable(invoiceNo);
	}

	@Override
	public Optional<LocalDate> getDeliveryDate()
	{
		return Optional.ofNullable(deliveryDate);
	}

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

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

	/**
	*	get the value of totalCost as a Money value
	*
	*	@return	 the value of totalCost
	*/
    @Override
	public SimpleMoney getTotalPrice()

	{
		if (currency == null)
		{
			return new SimpleMoney(totalCost);
		}
		return new SimpleMoney(currency, totalCost);
	}

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

	@Override
	public IPurchaseItemLister getPurchaseItem()
	{
		return new PurchaseItemLister().purchase(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("retailerId", retailerId);
		jsonBuilder.add("date", date.toString());
		if (totalCost != null)
		{
			jsonBuilder.add("totalCost", totalCost);
		}
		else
		{
			jsonBuilder.addNull("totalCost");
		}
		if (currency != null)
		{
			jsonBuilder.add("currency", currency);
		}
		else
		{
			jsonBuilder.addNull("currency");
		}
		if (orderNo != null)
		{
			jsonBuilder.add("orderNo", orderNo);
		}
		else
		{
			jsonBuilder.addNull("orderNo");
		}
		if (invoiceNo != null)
		{
			jsonBuilder.add("invoiceNo", invoiceNo);
		}
		else
		{
			jsonBuilder.addNull("invoiceNo");
		}
		if (deliveryDate != null)
		{
			jsonBuilder.add("deliveryDate", deliveryDate.toString());
		}
		else
		{
			jsonBuilder.addNull("deliveryDate");
		}
		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", "Purchase");
		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 IPurchase newValue)
	{
		flagHandler.flagReplaced(newValue, newValue::addPropertyChangeListener);
	}

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

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


	@Override
	public String toString() {
		return "Purchase: " + "id: " + id + ", " +
				"retailerId: " + retailerId + ", " +
				"date: " + date + ", " +
				"totalCost: " + totalCost + ", " +
				"currency: " + currency + ", " +
				"orderNo: " + orderNo + ", " +
				"invoiceNo: " + invoiceNo + ", " +
				"deliveryDate: " + deliveryDate + ", " +
				"lastUpdated: " + lastUpdated + ", " +
				"created: " + created;
	}

}
