/**
 * *********************************************************************
 * <p>
 * Copyright 2018 Jochen Staerk
 * <p>
 * Use is subject to license terms.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0.
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * <p>
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * <p>
 * **********************************************************************
 */
package org.mustangproject;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;

import org.mustangproject.ZUGFeRD.IExportableTransaction;
import org.mustangproject.ZUGFeRD.IReferencedDocument;
import org.mustangproject.ZUGFeRD.IZUGFeRDAllowanceCharge;
import org.mustangproject.ZUGFeRD.IZUGFeRDExportableItem;
import org.mustangproject.ZUGFeRD.IZUGFeRDLogisticsServiceCharge;
import org.mustangproject.ZUGFeRD.IZUGFeRDPaymentTerms;
import org.mustangproject.ZUGFeRD.IZUGFeRDTradeSettlement;
import org.mustangproject.ZUGFeRD.model.DocumentCodeTypeConstants;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

/***
 * An invoice, with fluent setters
 * @see IExportableTransaction if you want to implement an interface instead
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Invoice implements IExportableTransaction {

	protected boolean testIndicator;
	protected String documentName, documentCode, number, ownOrganisationFullPlaintextInfo, referenceNumber, shipToOrganisationID, shipToOrganisationName, shipToStreet, shipToZIP, shipToLocation, shipToCountry, ownForeignOrganisationID, ownOrganisationName, currency, paymentTermDescription;
	protected String deliveryTypeCode;
	protected Date issueDate, dueDate, deliveryDate;
	protected TradeParty sender, recipient, deliveryAddress, endCustomerDeliveryAddress, payee, invoicer, invoicee;
	protected ArrayList<CashDiscount> cashDiscounts;
	@JsonDeserialize(contentAs = Item.class)
	protected List<IZUGFeRDExportableItem> zfItems;
	protected ArrayList<String> notes;
	protected ReferencedDocument sellerOrderReferencedDocument;
	protected ReferencedDocument buyerOrderReferencedDocument;
	protected ReferencedDocument contractReferencedDocument;
	protected ArrayList<FileAttachment> xmlEmbeddedFiles;

	protected BigDecimal totalPrepaidAmount;
	protected Date detailedDeliveryDateStart;
	protected Date detailedDeliveryPeriodEnd;
	protected IReferencedDocument tenderReference; // TypeCode 50
	protected IReferencedDocument objectIdentifierReferencedDocument; // TypeCode 130
	protected IReferencedDocument relatedReferenceDocument; // TypeCode 916

	protected ArrayList<IZUGFeRDAllowanceCharge> allowances = new ArrayList<>(), charges = new ArrayList<>();
	protected ArrayList<IZUGFeRDLogisticsServiceCharge> logisticsServiceCharges = new ArrayList<>();
	protected ArrayList<IZUGFeRDPaymentTerms> paymentTerms = new ArrayList<>();

	// New field for storing Invoiced Object Identifier (BG-3)
	protected List<ReferencedDocument> invoiceReferencedDocuments;

	protected String specifiedProcuringProjectID;
	protected String specifiedProcuringProjectName;
	protected ReferencedDocument despatchAdviceReferencedDocument;
	protected ReferencedDocument deliveryNoteReferencedDocument;
	protected String vatDueDateTypeCode;
	protected String creditorReferenceID; // required when direct debit is used.

	private List<IncludedNote> includedNotes;
	private BigDecimal roundingAmount;
	private String paymentReference; // Remittance information / Verwendungszweck, BT-83
	private String businessProcessId;

	public Invoice() {
		zfItems = new ArrayList<>();
		cashDiscounts = new ArrayList<>();
		setCurrency("EUR");
	}

	@Override
	public boolean getTestIndicator() {
		return testIndicator;
	}

	public Invoice setTestIndicator() {
		this.testIndicator = true;
		return this;
	}

	@Override
	public String getDocumentName() {
		return documentName;
	}

	@Override
	public ReferencedDocument getContractReferencedDocument() {
		return contractReferencedDocument;
	}

	public Invoice setDocumentName(String documentName) {
		this.documentName = documentName;
		return this;
	}

	@Override
	public String getDocumentCode() {
		return documentCode;
	}

	public Invoice setDocumentCode(String documentCode) {
		this.documentCode = documentCode;
		return this;
	}

	public Invoice embedFileInXML(FileAttachment fa) {
		if (xmlEmbeddedFiles == null) {
			xmlEmbeddedFiles = new ArrayList<>();
		}
		xmlEmbeddedFiles.add(fa);
		return this;
	}

	@Override
	public FileAttachment[] getAdditionalReferencedDocuments() {
		if (xmlEmbeddedFiles == null) {
			return null;
		}
		return xmlEmbeddedFiles.toArray(new FileAttachment[0]);

	}

	/***
	 * setter in case e.g. jackson tries to map attachments (normal use embedFileInXML)
	 * @param fileArr Array of FileAttachments
	 * @return fluent setter
	 */
	public Invoice setAdditionalReferencedDocuments(FileAttachment[] fileArr) {
		if (fileArr != null) {
			xmlEmbeddedFiles = new ArrayList<>(Arrays.asList(fileArr));
		} else {
			xmlEmbeddedFiles = new ArrayList<>();
		}
		return this;
	}

	@Override
	public CashDiscount[] getCashDiscounts() {
		return cashDiscounts.toArray(new CashDiscount[0]);
	}

	@Override
	public String getNumber() {
		return number;
	}



	@Override
	/***
	 * BT-17
	 */
	public IReferencedDocument getTenderReferencedDocument() {
		return tenderReference;
	}


	/***
	 * BT-17
	 * @param dr the Referenced Document (may contain ID, typecode, date...)
	 * @return fluent setter
	 */
	public Invoice setTenderReferencedDocument(ReferencedDocument dr) {
		dr.setTypeCode("50"); // 50 is fixed for tender documents, Price/sales catalogue response
		tenderReference = dr;
		return this;
	}

	/***
	 * Sets a reference to a tender
	 * @param ID the key ID of the tender
	 * @return fluent setter
	 */
	@JsonIgnore
	public Invoice setTenderReferencedDocument(String ID) {
		ReferencedDocument dr = new ReferencedDocument(ID);
		setTenderReferencedDocument(dr);
		return this;
	}



	/** BT-18 */
	@Override
	/***
	 *
	 */
	public IReferencedDocument getObjectIdentifierReferencedDocument() {
		return objectIdentifierReferencedDocument;
	}


	/** BT-18 */
	public Invoice setObjectIdentifierReferencedDocument(ReferencedDocument dr) {
		dr.setTypeCode("130"); // Rechnungsdatenblatt
		objectIdentifierReferencedDocument = dr;
		return this;
	}

	@JsonIgnore
	public Invoice setObjectIdentifierReferencedDocument(String id) {
		ReferencedDocument dr = new ReferencedDocument(id);
		setObjectIdentifierReferencedDocument(dr);
		return this;
	}

	@JsonIgnore
	public Invoice setObjectIdentifierReferencedDocument(String id, String referenceTypeCode) {
		ReferencedDocument dr = new ReferencedDocument(id);
		dr.setReferenceTypeCode(referenceTypeCode);
		return setObjectIdentifierReferencedDocument(dr);
	}

	@JsonIgnore
	public Invoice setObjectIdentifierReferencedDocument(String id, String referenceTypeCode, Date issueDate) {
		ReferencedDocument dr = new ReferencedDocument(id);
		dr.setReferenceTypeCode(referenceTypeCode);
		dr.setFormattedIssueDateTime(issueDate);
		return setObjectIdentifierReferencedDocument(dr);
	}

	@Override
	public IReferencedDocument getRelatedReferencedDocument() {
		return relatedReferenceDocument;
	}


	public Invoice setRelatedReferencedDocument(ReferencedDocument dr) {
		dr.setTypeCode("916"); // Referenzpapier
		relatedReferenceDocument = dr;
		return this;
	}

	@JsonIgnore
	public Invoice setRelatedReferencedDocument(String id) {
		ReferencedDocument dr = new ReferencedDocument(id);
		setRelatedReferencedDocument(dr);
		return this;
	}

	@JsonIgnore
	public Invoice setRelatedReferencedDocument(String id, String referenceTypeCode) {
		ReferencedDocument dr = new ReferencedDocument(id);
		dr.setReferenceTypeCode(referenceTypeCode);
		return setRelatedReferencedDocument(dr);
	}

	@JsonIgnore
	public Invoice setRelatedReferencedDocument(String id, String referenceTypeCode, Date issueDate) {
		ReferencedDocument dr = new ReferencedDocument(id);
		dr.setReferenceTypeCode(referenceTypeCode);
		dr.setFormattedIssueDateTime(issueDate);
		return setRelatedReferencedDocument(dr);
	}


	public Invoice setNumber(String number) {
		this.number = number;
		return this;
	}

	/***
	 * switch type to invoice correction and refer to document number.
	 * Please note that the quantities need to be negative, if you
	 * e.g. delivered 100 and take 50 back the quantity should be -50 in the
	 * corrected invoice, which will result in negative VAT and a negative payment amount
	 * @param number the invoice number to be corrected
	 * @return this object (fluent setter)
	 */
	public Invoice setCorrection(String number) {
		setInvoiceReferencedDocumentID(number);
		documentCode = DocumentCodeTypeConstants.CORRECTEDINVOICE;
		return this;
	}

	public Invoice setCreditNote() {
		documentCode = DocumentCodeTypeConstants.CREDITNOTE; // this value should somewhen be changed to Selfbilling
		return this;
	}

	public Invoice setCreditNote(String number) {
		setInvoiceReferencedDocumentID(number);
		documentCode = DocumentCodeTypeConstants.CREDITNOTE;
		return this;
	}

	@Override
	public String getOwnOrganisationFullPlaintextInfo() {
		return ownOrganisationFullPlaintextInfo;
	}

	public Invoice setOwnOrganisationFullPlaintextInfo(String ownOrganisationFullPlaintextInfo) {
		this.ownOrganisationFullPlaintextInfo = ownOrganisationFullPlaintextInfo;
		return this;
	}

	@Override
	public String getReferenceNumber() {
		return referenceNumber;
	}

	public Invoice setReferenceNumber(String referenceNumber) {
		this.referenceNumber = referenceNumber;
		return this;
	}

	@Override
	public String getShipToOrganisationID() {
		return shipToOrganisationID;
	}

	public Invoice setShipToOrganisationID(String shipToOrganisationID) {
		this.shipToOrganisationID = shipToOrganisationID;
		return this;
	}

	@Override
	public String getShipToOrganisationName() {
		return shipToOrganisationName;
	}

	public Invoice setShipToOrganisationName(String shipToOrganisationName) {
		this.shipToOrganisationName = shipToOrganisationName;
		return this;
	}

	@Override
	public String getShipToStreet() {
		return shipToStreet;
	}


	public Invoice setShipToStreet(String shipToStreet) {
		this.shipToStreet = shipToStreet;
		return this;
	}

	@Override
	public String getShipToZIP() {
		return shipToZIP;
	}

	public Invoice setShipToZIP(String shipToZIP) {
		this.shipToZIP = shipToZIP;
		return this;
	}

	@Override
	public String getShipToLocation() {
		return shipToLocation;
	}

	public Invoice setShipToLocation(String shipToLocation) {
		this.shipToLocation = shipToLocation;
		return this;
	}

	@Override
	public String getShipToCountry() {
		return shipToCountry;
	}

	public Invoice setShipToCountry(String shipToCountry) {
		this.shipToCountry = shipToCountry;
		return this;
	}

	@Override
	public String getDeliveryTypeCode() {
		return deliveryTypeCode;
	}

	public Invoice setDeliveryTypeCode(String deliveryTypeCode) {
		this.deliveryTypeCode = deliveryTypeCode;
		return this;
	}

	@Deprecated(forRemoval = true, since = "2.24.1")
	public String getBuyerOrderReferencedDocumentID() {
		if (buyerOrderReferencedDocument == null) {
			return null;
		} else {
			return buyerOrderReferencedDocument.getIssuerAssignedID();
		}
	}

	@Deprecated(forRemoval = true, since = "2.24.1")
	public String getSellerOrderReferencedDocumentID() {
		if (sellerOrderReferencedDocument == null) {
			return null;
		} else {
			return sellerOrderReferencedDocument.getIssuerAssignedID();
		}
	}

	@Deprecated(forRemoval = true, since = "2.24.1")
	public Invoice setSellerOrderReferencedDocumentID(String sellerOrderReferencedDocumentID) {
		if (sellerOrderReferencedDocument == null) {
			sellerOrderReferencedDocument = new ReferencedDocument();
		}
		this.sellerOrderReferencedDocument.setIssuerAssignedID(sellerOrderReferencedDocumentID);
		return this;
	}

	/**
	 * @param sellerOrderReferencedDocument the sellerOrderReferencedDocument to set
	 */
	public Invoice setSellerOrderReferencedDocument(ReferencedDocument sellerOrderReferencedDocument) {
		this.sellerOrderReferencedDocument = sellerOrderReferencedDocument;
		return this;
	}

	/***
	 * usually the order number
	 * @deprecated	use setBuyerOrderReferencedDocument() / getBuyerOrderReferencedDocument().setIssuerAssignedID()
	 * @param buyerOrderReferencedDocumentID string with number
	 * @return fluent setter
	 */
	@Deprecated(forRemoval = true, since = "2.24.1")
	public Invoice setBuyerOrderReferencedDocumentID(String buyerOrderReferencedDocumentID) {
		if (buyerOrderReferencedDocument == null) {
			buyerOrderReferencedDocument = new ReferencedDocument();
		}
		this.buyerOrderReferencedDocument.setIssuerAssignedID(buyerOrderReferencedDocumentID);
		return this;
	}

	@Override
	public ReferencedDocument getBuyerOrderReferencedDocument() {
		return buyerOrderReferencedDocument;
	}

	/**
	 * @param buyerOrderReferencedDocument the buyerOrderReferencedDocument to set
	 */
	public Invoice setBuyerOrderReferencedDocument(ReferencedDocument buyerOrderReferencedDocument) {
		this.buyerOrderReferencedDocument = buyerOrderReferencedDocument;
		return this;
	}

	@Override
	public ReferencedDocument getSellerOrderReferencedDocument() {
		return sellerOrderReferencedDocument;
	}

	/***
	 * usually in case of a correction the original invoice number
	 * @deprecated	use setInvoiceReferencedDocument() / getInvoiceReferencedDocument().setIssuerAssignedID()
	 * @param invoiceReferencedDocumentID string with number
	 * @return fluent setter
	 */
	@Deprecated(forRemoval = true, since = "2.24.1")
	public Invoice setInvoiceReferencedDocumentID(String invoiceReferencedDocumentID) {
		if (invoiceReferencedDocuments == null) {
			invoiceReferencedDocuments = new ArrayList<>();
		}
		if (invoiceReferencedDocuments.isEmpty()) {
			invoiceReferencedDocuments.add(new ReferencedDocument());
		}
		this.invoiceReferencedDocuments.get(0).setIssuerAssignedID(invoiceReferencedDocumentID);
		return this;
	}

	@Deprecated
	@Override
	@SuppressWarnings("removal")
	public String getInvoiceReferencedDocumentID() {
		if (this.invoiceReferencedDocuments == null || this.invoiceReferencedDocuments.isEmpty() ) {
			return null;
		} else {
			return this.invoiceReferencedDocuments.get(0).getIssuerAssignedID();
		}
	}

	@Deprecated
	@Override
	@SuppressWarnings("removal")
	public Date getInvoiceReferencedIssueDate() {
		if (this.invoiceReferencedDocuments == null || this.invoiceReferencedDocuments.isEmpty() ) {
			return null;
		} else {
			return this.invoiceReferencedDocuments.get(0).getFormattedIssueDateTime();
		}
	}

	@Deprecated(forRemoval = true, since = "2.24.1")
	public Invoice setInvoiceReferencedIssueDate(Date issueDate) {
		if (invoiceReferencedDocuments == null) {
			invoiceReferencedDocuments = new ArrayList<>();
		}
		if (invoiceReferencedDocuments.isEmpty()) {
			invoiceReferencedDocuments.add(new ReferencedDocument());
		}
		this.invoiceReferencedDocuments.get(0).setFormattedIssueDateTime(issueDate);
		return this;
	}

	@Override
	@Deprecated(forRemoval = true, since = "2.24.1")
	@SuppressWarnings("removal")
	public Date getBuyerOrderReferencedDocumentIssueDateTime() {
		if (this.buyerOrderReferencedDocument == null ) {
			return null;
		} else {
			return this.buyerOrderReferencedDocument.getFormattedIssueDateTime();
		}
	}


	/***
	 * allow to set a amount which has already been paid
	 * @param prepaid null is possible to omit
	 * @return fluent setter
	 */
	public Invoice setTotalPrepaidAmount(BigDecimal prepaid) {
		totalPrepaidAmount = prepaid;
		return this;
	}

	@Override
	public BigDecimal getTotalPrepaidAmount() {
		return totalPrepaidAmount;
	}

	/***
	 * when the order (or whatever reference in BuyerOrderReferencedDocumentID) was issued
	 * @param buyerOrderReferencedDocumentIssueDateTime  IssueDateTime in format CCYY-MM-DDTHH:MM:SS
	 * @return fluent setter
	 */
	public Invoice setBuyerOrderReferencedDocumentIssueDateTime(Date buyerOrderReferencedDocumentIssueDateTime) {
		if (buyerOrderReferencedDocument == null) {
			buyerOrderReferencedDocument = new ReferencedDocument();
		}
		this.buyerOrderReferencedDocument.setFormattedIssueDateTime(buyerOrderReferencedDocumentIssueDateTime);
		return this;
	}

	@Override
	public String getOwnTaxID() {
		return getSender().getTaxID();
	}


	/***
	 * @deprecated use TradeParty::addTaxID instead
	 */
	@Deprecated
	public Invoice setOwnTaxID(String ownTaxID) {
		sender.addTaxID(ownTaxID);
		return this;
	}

	@Override
	public String getOwnVATID() {
		return getSender().getVATID();
	}

	/***
	 * @deprecated use TradeParty::addVATID instead
	 * @see TradeParty
	 */
	@Deprecated
	public Invoice setOwnVATID(String ownVATID) {
		sender.addVATID(ownVATID);
		return this;
	}

	@Override
	public String getOwnForeignOrganisationID() {
		return ownForeignOrganisationID;
	}


	/***
	 * @deprecated use TradeParty instead
	 * @see TradeParty
	 */
	@Deprecated
	public Invoice setOwnForeignOrganisationID(String ownForeignOrganisationID) {
		this.ownForeignOrganisationID = ownForeignOrganisationID;
		return this;
	}

	@Override
	public String getOwnOrganisationName() {
		return ownOrganisationName;
	}


	/***
	 * @deprecated use senders' TradeParty's name instead
	 * @see TradeParty
	 */
	@Deprecated
	public Invoice setOwnOrganisationName(String ownOrganisationName) {
		this.ownOrganisationName = ownOrganisationName;
		return this;
	}


	@JsonIgnore
	@Override
	public String getOwnStreet() {
		return sender.getStreet();
	}


	@JsonIgnore
	@Override
	public String getOwnZIP() {
		return sender.getZIP();
	}


	@JsonIgnore
	@Override
	public String getOwnLocation() {
		return sender.getLocation();
	}


	@JsonIgnore
	@Override
	public String getOwnCountry() {
		return sender.getCountry();
	}


	@Override
	public String[] getNotes() {
		if (notes == null) {
			return null;
		}
		return notes.toArray(new String[0]);
	}

	@Override
	public List<IncludedNote> getNotesWithSubjectCode() {
		return includedNotes;
	}

	public Invoice setNotesWithSubjectCode(List<IncludedNote> theList) {
		includedNotes = theList;
		return this;
	}

	@Override
	public String getCurrency() {
		return currency;
	}

	public Invoice setCurrency(String currency) {
		this.currency = currency;
		return this;
	}

	@Override
	public String getPaymentTermDescription() {
		return paymentTermDescription;
	}

	public Invoice setPaymentTermDescription(String paymentTermDescription) {
		this.paymentTermDescription = paymentTermDescription;
		return this;
	}

	@Override
	public Date getIssueDate() {
		return issueDate;
	}

	public Invoice setIssueDate(Date issueDate) {
		this.issueDate = issueDate;
		return this;
	}

	@Override
	public Date getDueDate() {
		return dueDate;
	}

	public Invoice setDueDate(Date dueDate) {
		this.dueDate = dueDate;
		return this;
	}

	@Override
	public Date getDeliveryDate() {
		return deliveryDate;
	}

	public Invoice setDeliveryDate(Date deliveryDate) {
		this.deliveryDate = deliveryDate;
		return this;
	}

	@Override
	public TradeParty getSender() {
		return sender;
	}

	/***
	 * for currency rounding differences to 5ct e.g. in Netherlands ("Rappenrundung")
	 * @return null if not set, otherwise BigDecimal of Euros
	 */
	@Override
	public BigDecimal getRoundingAmount() {
		return roundingAmount;
	}

	/***
	 * set the cent e.g. to reach the next 5ct mark for currencies in certain countries
	 * e.g. in the Netherlands ("Rappenrundung")
	 * @param amount
	 * @return fluent setter
	 */
	public Invoice setRoundingAmount(BigDecimal amount) {
		 roundingAmount = amount;
		 return this;
	}

	@Override
	public TradeParty getRecipient() {
		return recipient;
	}

	/**
	 * required.
	 * sets the invoice receiving institution = invoicee
	 *
	 * @param recipient the invoicee organisation
	 * @return fluent setter
	 */
	public Invoice setRecipient(TradeParty recipient) {
		this.recipient = recipient;
		return this;
	}

	/**
	 * required.
	 * sets the invoicing institution = invoicer
	 *
	 * @param sender the invoicer
	 * @return fluent setter
	 */
	public Invoice setSender(TradeParty sender) {
		this.sender = sender;
		if (sender.getBankDetails() != null && !sender.getBankDetails().isEmpty()) {
			// convert bankdetails
		}
		return this;
	}

	// Getter for BG-3
	@Override
	public List<ReferencedDocument> getInvoiceReferencedDocuments() {
		return invoiceReferencedDocuments;
	}

	// Setter for BG-3
	public void setInvoiceReferencedDocuments(List<ReferencedDocument> invoiceReferencedDocuments) {
		this.invoiceReferencedDocuments = invoiceReferencedDocuments;
	}

	// Method to add a single ReferencedDocument for BG-3
	public Invoice addInvoiceReferencedDocument(ReferencedDocument doc) {
		if (invoiceReferencedDocuments == null) {
			invoiceReferencedDocuments = new ArrayList<>();
		}
		invoiceReferencedDocuments.add(doc);
		return this;
	}

	@Override
	public IZUGFeRDAllowanceCharge[] getZFAllowances() {
		if (allowances.isEmpty()) {
			return null;
		} else {
			return allowances.toArray(new IZUGFeRDAllowanceCharge[0]);
		}
	}

	/***
	 * this is wrong and only used from jackson
	 * @param iza the Array of allowances/charges
	 * @return fluent setter
	 */
	public Invoice setZFAllowances(Allowance[] iza) {
		allowances = new ArrayList<>(Arrays.asList(iza));
		return this;
	}


	@Override
	public IZUGFeRDAllowanceCharge[] getZFCharges() {
		if (charges.isEmpty()) {
			return null;
		} else {
			return charges.toArray(new IZUGFeRDAllowanceCharge[0]);
		}
	}

	/***
	 * this is wrong and only used from jackson
	 * @param iza the array of charges
	 * @return fluent setter
	 */
	public Invoice setZFCharges(Charge[] iza) {
		charges = new ArrayList<>();
		charges.addAll(Arrays.asList(iza));
		return this;
	}

	@Override
	public IZUGFeRDLogisticsServiceCharge[] getZFLogisticsServiceCharges() {
		if (logisticsServiceCharges.isEmpty()) {
			return null;
		} else {
			return logisticsServiceCharges.toArray(new IZUGFeRDLogisticsServiceCharge[0]);
		}
	}

	/***
	 * this is wrong and only used from jackson
	 * @param iza the array of charges
	 * @return fluent setter
	 */
	public Invoice setZFLogisticsServiceCharges(LogisticsServiceCharge[] iza) {
		logisticsServiceCharges = new ArrayList<>();
		logisticsServiceCharges.addAll(Arrays.asList(iza));
		return this;
	}

	@Override
	public IZUGFeRDTradeSettlement[] getTradeSettlement() {

		if (getSender() == null) {
			return null;
		}

		return getSender().getAsTradeSettlement();

	}


	@JsonIgnore
	@Override
	public IZUGFeRDPaymentTerms getPaymentTerms() {
		if (!paymentTerms.isEmpty()) {
			return paymentTerms.get(0);
		}
		return null;
	}

	public Invoice setPaymentTerms(IZUGFeRDPaymentTerms paymentTerm) {
		if (paymentTerms.isEmpty()) {
			paymentTerms.add(paymentTerm);
		} else {
			paymentTerms.set(0, paymentTerm);
		}
		return this;
	}

	@JsonIgnore
	@Override
	public IZUGFeRDPaymentTerms[] getExtendedPaymentTerms() {
		return paymentTerms.toArray(new IZUGFeRDPaymentTerms[0]);
	}

	public Invoice setExtendedPaymentTerms(IZUGFeRDPaymentTerms[] paymentTerms) {
		this.paymentTerms.clear();
		this.paymentTerms.addAll(Arrays.asList(paymentTerms));
		return this;
	}

	/**
	 * Set multiple payment terms when using the EXTENDED profile.
	 * @return
	 */
	public Invoice addPaymentTerms(IZUGFeRDPaymentTerms paymentTerm) {
		paymentTerms.add(paymentTerm);
		return this;
	}


	@Override
	public String getPaymentReference() {
		return paymentReference;
	}

	public Invoice setPaymentReference(String paymentReference) {
		this.paymentReference = paymentReference;
		return this;
	}

	@Override
	public TradeParty getDeliveryAddress() {
		return deliveryAddress;
	}

	/***
	 * if the delivery address is not the recipient address, it can be specified here
	 * @param deliveryAddress the goods receiving organisation
	 * @return fluent setter
	 */
	public Invoice setDeliveryAddress(TradeParty deliveryAddress) {
		this.deliveryAddress = deliveryAddress;
		return this;
	}

	@Override
	public TradeParty getEndCustomerDeliveryAddress() {
		return endCustomerDeliveryAddress;
	}

	public Invoice setEndCustomerDeliveryAddress(TradeParty endCustomerDeliveryAddress) {
		this.endCustomerDeliveryAddress = endCustomerDeliveryAddress;
		return this;
	}

	@Override
	public TradeParty getPayee() {
		return this.payee;
	}

	/***
	 * if the payee is not the seller, it can be specified here
	 * @param payee the payment receiving organisation
	 * @return fluent setter
	 */
	public Invoice setPayee(TradeParty payee) {
		this.payee = payee;
		return this;
	}

	@Override
	public TradeParty getInvoicer() {
		return this.invoicer;
	}

	/***
	 * if the invoicer is not the seller, it can be specified here
	 * @param invoicer the invoice issuing organisation
	 * @return fluent setter
	 */
	public Invoice setInvoicer(TradeParty invoicer) {
		this.invoicer = invoicer;
		return this;
	}

	@Override
	public TradeParty getInvoicee() {
		return this.invoicee;
	}

	/***
	 * if the invoicee is not the buyer, it can be specified here
	 * @param invoicee the invoice receiving organisation
	 * @return fluent setter
	 */
	public Invoice setInvoicee(TradeParty invoicee) {
		this.invoicee = invoicee;
		return this;
	}

	/***
	 * Adds a cash discount (skonto)
	 * @param c the CashDiscount percent/period combination
	 * @return fluent setter
	 */
	public Invoice addCashDiscount(CashDiscount c) {
		this.cashDiscounts.add(c);
		return this;
	}


	@Override
	public IZUGFeRDExportableItem[] getZFItems() {
		return zfItems.toArray(new IZUGFeRDExportableItem[0]);
	}

	public void setZFItems(List<IZUGFeRDExportableItem> ims) {
		zfItems = ims;
	}

	/**
	 * required
	 * adds invoice "lines" :-)
	 *
	 * @param item the invoice line
	 * @return fluent setter
	 * @see Item
	 */
	public Invoice addItem(IZUGFeRDExportableItem item) {
		zfItems.add(item);
		return this;
	}


	/***
	 * checks if all required items are set in order to be able to export it
	 * @return true if all required items are set
	 */
	@JsonIgnore
	public boolean isValid() {
		return dueDate != null && sender != null && sender.getTaxID() != null && sender.getVATID() != null && recipient != null;
		//contact
		//		this.phone = phone;
		//		this.email = email;
		//		this.street = street;
		//		this.zip = zip;
		//		this.location = location;
		//		this.country = country;
	}

	/***
	 * adds a document level addition to the price
	 * @see Charge
	 * @param izac the charge to be applied
	 * @return fluent setter
	 */
	public Invoice addCharge(IZUGFeRDAllowanceCharge izac) {
		charges.add(izac);
		return this;
	}

	/***
	 *  adds a document level addition to the price
	 *  @see Charge
	 *
	 * @param charge
	 * @return  fluent setter
	 */
	public Invoice addLogisticServiceCharge(IZUGFeRDLogisticsServiceCharge charge) {
		logisticsServiceCharges.add(charge);
		return this;
	}

	/***
	 * adds a document level rebate
	 * @see Allowance
	 * @param izac the allowance to be applied
	 * @return fluent setter
	 */
	public Invoice addAllowance(IZUGFeRDAllowanceCharge izac) {
		allowances.add(izac);
		return this;
	}

	/***
	 * adds a referenced in the invoice
	 * @param rd the referenced Document
	 * @return
	 */
	public Invoice setContractReferencedDocument(ReferencedDocument rd) {
		contractReferencedDocument = rd;
		return this;
	}


	/***
	 * sets a document level delivery period,
	 * which is optional additional to the mandatory deliverydate
	 * and which will become a BillingSpecifiedPeriod-Element
	 * @param start the date of first delivery
	 * @param end the date of last delivery
	 * @return fluent setter
	 */
	public Invoice setDetailedDeliveryPeriod(Date start, Date end) {
		detailedDeliveryDateStart = start;
		detailedDeliveryPeriodEnd = end;

		return this;
	}


	@Override
	public Date getDetailedDeliveryPeriodFrom() {
		return detailedDeliveryDateStart;
	}

	public Invoice setDetailedDeliveryPeriodFrom(Date dt) {
		detailedDeliveryDateStart = dt;
		return this;
	}

	@Override
	public Date getDetailedDeliveryPeriodTo() {
		return detailedDeliveryPeriodEnd;
	}

	public Invoice setDetailedDeliveryPeriodTo(Date dt) {
		detailedDeliveryPeriodEnd = dt;
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element
	 *
	 * @param text freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addNote(String text) {
		if (notes == null) {
			notes = new ArrayList<>();
		}
		notes.add(text);
		return this;
	}

	public Invoice addNotes(Collection<IncludedNote> notes) {
		if (notes == null) {
			return this;
		}
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.addAll(notes);
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element with explicit
	 * subjectCode {@link SubjectCode#AAI}
	 *
	 * @param content freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addGeneralNote(String content) {
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.add(IncludedNote.generalNote(content));
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element with explicit
	 * subjectCode {@link SubjectCode#REG}
	 *
	 * @param content freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addRegulatoryNote(String content) {
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.add(IncludedNote.regulatoryNote(content));
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element with explicit
	 * subjectCode {@link SubjectCode#ABL}
	 *
	 * @param content freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addLegalNote(String content) {
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.add(IncludedNote.legalNote(content));
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element with explicit
	 * subjectCode {@link SubjectCode#CUS}
	 *
	 * @param content freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addCustomsNote(String content) {
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.add(IncludedNote.customsNote(content));
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element with explicit
	 * subjectCode {@link SubjectCode#SUR}
	 *
	 * @param content freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addSellerNote(String content) {
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.add(IncludedNote.sellerNote(content));
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element with explicit
	 * subjectCode {@link SubjectCode#TXD}
	 *
	 * @param content freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addTaxNote(String content) {
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.add(IncludedNote.taxNote(content));
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element with explicit
	 * subjectCode {@link SubjectCode#ACY}
	 *
	 * @param content freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addIntroductionNote(String content) {
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.add(IncludedNote.introductionNote(content));
		return this;
	}

	/**
	 * adds a free text paragraph, which will become an includedNote element with explicit
	 * subjectCode {@link SubjectCode#AAK}
	 *
	 * @param content freeform UTF8 plain text
	 * @return fluent setter
	 */
	public Invoice addDiscountBonusNote(String content) {
		if (includedNotes == null) {
			includedNotes = new ArrayList<>();
		}
		includedNotes.add(IncludedNote.discountBonusNote(content));
		return this;
	}

	@Override
	public String getSpecifiedProcuringProjectID() {
		return specifiedProcuringProjectID;
	}

	public Invoice setSpecifiedProcuringProjectID(String specifiedProcuringProjectID) {
		this.specifiedProcuringProjectID = specifiedProcuringProjectID;
		return this;
	}

	/**
	 * @deprecated use getDespatchAdviceReferenced.getIssuerAssignedID
	 */
	@SuppressWarnings("removal")
	@Override
	@Deprecated(forRemoval = true, since = "2.24.1")
	public String getDespatchAdviceReferencedDocumentID() {
		if (this.despatchAdviceReferencedDocument == null) {
			return null;
		} else {
			return despatchAdviceReferencedDocument.getIssuerAssignedID();
		}
	}

	/**
	 * @deprecated use setDespatchAdviceReferenced / getDespatchAdviceReferenced.setIssuerAssignedID
	 * @param despatchAdviceReferencedDocumentID
	 * @return
	 */
	@Deprecated(forRemoval = true, since = "2.24.1")
	public Invoice setDespatchAdviceReferencedDocumentID(String despatchAdviceReferencedDocumentID) {
		if (this.despatchAdviceReferencedDocument == null) {
			this.despatchAdviceReferencedDocument = new ReferencedDocument();
		}
		this.despatchAdviceReferencedDocument.setIssuerAssignedID(despatchAdviceReferencedDocumentID);
		return this;
	}

	/**
	 * @deprecated use getDeliveryNoteReferencedDocument.getIssuerAssignedID
	 */
	@SuppressWarnings("removal")
	@Override
	@Deprecated(forRemoval = true, since = "2.24.1")
	public String getDeliveryNoteReferencedDocumentID() {
		if (this.deliveryNoteReferencedDocument == null) {
			return null;
		} else {
			return deliveryNoteReferencedDocument.getIssuerAssignedID();
		}
	}

	/**
	 * @deprecated use setDeliveryNoteReferenced / getDeliveryNoteReferenced.setIssuerAssignedID
	 * @param deliveryNoteReferencedDocumentID
	 * @return
	 */
	@Deprecated(forRemoval = true, since = "2.24.1")
	public Invoice setDeliveryNoteReferencedDocumentID(String deliveryNoteReferencedDocumentID) {
		if (this.deliveryNoteReferencedDocument == null) {
			this.deliveryNoteReferencedDocument = new ReferencedDocument();
		}
		this.deliveryNoteReferencedDocument.setIssuerAssignedID(deliveryNoteReferencedDocumentID);
		return this;
	}

	/**
	 * @deprecated use getDeliveryNoteReferenced.getFormattedIssueDateTime
	 */
	@SuppressWarnings("removal")
	@Override
	@Deprecated(forRemoval = true, since = "2.24.1")
	public Date getDeliveryNoteReferencedDocumentDate() {
		if (this.deliveryNoteReferencedDocument == null) {
			return null;
		} else {
			return deliveryNoteReferencedDocument.getFormattedIssueDateTime();
		}
	}

	/**
	 * @deprecated use setDeliveryNoteReferenced / getDeliveryNoteReferenced.setFormattedIssueDateTime
	 * @param deliveryNoteReferencedDocumentDate
	 * @return
	 */
	@Deprecated(forRemoval = true, since = "2.24.1")
	public Invoice setDeliveryNoteReferencedDocumentDate(Date deliveryNoteReferencedDocumentDate) {
		if (this.deliveryNoteReferencedDocument == null) {
			this.deliveryNoteReferencedDocument = new ReferencedDocument();
		}
		this.deliveryNoteReferencedDocument.setFormattedIssueDateTime(deliveryNoteReferencedDocumentDate);
		return this;
	}

	/**
	 * @return the despatchAdviceReferencedDocument
	 */
	@Override
	public ReferencedDocument getDespatchAdviceReferencedDocument() {
		return despatchAdviceReferencedDocument;
	}

	/**
	 * @param despatchAdviceReferencedDocument the despatchAdviceReferencedDocument to set
	 */
	public Invoice setDespatchAdviceReferencedDocument(ReferencedDocument despatchAdviceReferencedDocument) {
		this.despatchAdviceReferencedDocument = despatchAdviceReferencedDocument;
		return this;
	}

	/**
	 * @return the deliveryNoteReferencedDocument
	 */
	@Override
	public ReferencedDocument getDeliveryNoteReferencedDocument() {
		return deliveryNoteReferencedDocument;
	}

	/**
	 * @param deliveryNoteReferencedDocument the deliveryNoteReferencedDocument to set
	 * @return fluent setter
	 */
	public Invoice setDeliveryNoteReferencedDocument(ReferencedDocument deliveryNoteReferencedDocument) {
		this.deliveryNoteReferencedDocument = deliveryNoteReferencedDocument;
		return this;
	}

	@Override
	public String getSpecifiedProcuringProjectName() {
		return specifiedProcuringProjectName;
	}

	public Invoice setSpecifiedProcuringProjectName(String specifiedProcuringProjectName) {
		this.specifiedProcuringProjectName = specifiedProcuringProjectName;
		return this;
	}

	@Override
	public String getVATDueDateTypeCode() {
		return vatDueDateTypeCode;
	}

	/**
	 * Decide when the VAT should be collected.
	 *
	 * @param vatDueDateTypeCode use EventTimeCodeTypeConstants
	 * @return fluent setter
	 */
	public Invoice setVATDueDateTypeCode(String vatDueDateTypeCode) {
		this.vatDueDateTypeCode = vatDueDateTypeCode;
		return this;
	}

	@Override
	public String getCreditorReferenceID() {
		return creditorReferenceID;
	}

	public Invoice setCreditorReferenceID(String creditorReferenceID) {
		this.creditorReferenceID = creditorReferenceID;
		return this;
	}

	public Invoice setBusinessProcessId(String id) {
  		this.businessProcessId = id;
  		return this;
	}

	@Override
	public String getBusinessProcessId() {
  		return businessProcessId;
	}

}
