/**
 * *********************************************************************
 * <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.ZUGFeRD;

import static org.mustangproject.ZUGFeRD.ZUGFeRDDateFormat.DATE;
import static org.mustangproject.ZUGFeRD.model.TaxCategoryCodeTypeConstants.CATEGORY_CODES_WITH_EXEMPTION_REASON;

import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

import org.mustangproject.XMLTools;

public class ZUGFeRD1PullProvider extends ZUGFeRD2PullProvider {

	public ZUGFeRD1PullProvider() {
		profile = Profiles.getByName("COMFORT", 1);
	}

	@Override
	protected String priceFormat(BigDecimal value) {
		return XMLTools.nDigitFormat(value, 4);
	}

	@Override
	protected String quantityFormat(BigDecimal value) {
		return XMLTools.nDigitFormat(value, 4);
	}

	@Override
	public void generateXML(IExportableTransaction trans) {
		this.trans = trans;
		this.calc = new TransactionCalculator(trans);

		boolean hasDueDate = false;
		final SimpleDateFormat germanDateFormat = new SimpleDateFormat("dd.MM.yyyy");
		String exemptionReason = "";

		if (trans.getPaymentTermDescription() != null) {
			paymentTermsDescription = trans.getPaymentTermDescription();
		}

		if (paymentTermsDescription == null) {
			paymentTermsDescription = "Zahlbar ohne Abzug bis " + germanDateFormat.format(trans.getDueDate());

		}

		String senderReg = "";
		if (trans.getOwnOrganisationFullPlaintextInfo() != null) {
			senderReg = "<ram:IncludedNote><ram:Content>"
					+ XMLTools.encodeXML(trans.getOwnOrganisationFullPlaintextInfo()) + "</ram:Content>"
					+ "<ram:SubjectCode>REG</ram:SubjectCode></ram:IncludedNote>";

		}

		String rebateAgreement = "";
		if (trans.rebateAgreementExists()) {
			rebateAgreement = "<ram:IncludedNote><ram:Content>"
					+ "Es bestehen Rabatt- und Bonusvereinbarungen.</ram:Content>"
					+ "<ram:SubjectCode>AAK</ram:SubjectCode></ram:IncludedNote>";
		}

		String subjectNote = "";
		if (trans.getSubjectNote() != null) {
			subjectNote = "<ram:IncludedNote><ram:Content>"
					+ XMLTools.encodeXML(trans.getSubjectNote()) + "</ram:Content>"
					+ "</ram:IncludedNote>";
		}
		String typecode = "380";
		if (trans.getDocumentCode() != null) {
			typecode = trans.getDocumentCode();
		}
		StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"

				+ "<rsm:CrossIndustryDocument xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:rsm=\"urn:ferd:CrossIndustryDocument:invoice:1p0\""
				+ " xmlns:ram=\"urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12\""
				+ " xmlns:udt=\"urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15\">"
				+ (ZUGFeRD1PullProvider.class.getPackage().getImplementationVersion() != null ?
					"<!-- generated by: mustangproject.org v" + ZUGFeRD1PullProvider.class.getPackage().getImplementationVersion() + "-->"
					: "<!-- generated by: mustangproject.org -->"
				)
				+ "<rsm:SpecifiedExchangedDocumentContext>"
				+ "<ram:GuidelineSpecifiedDocumentContextParameter>"
				+ "<ram:ID>" + getProfile().getID() + "</ram:ID>"
				+ "</ram:GuidelineSpecifiedDocumentContextParameter>"
				+ "</rsm:SpecifiedExchangedDocumentContext>"
				+ "<rsm:HeaderExchangedDocument>"
				+ "<ram:ID>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:ID>"
				+ "<ram:Name>RECHNUNG</ram:Name>"
				+ "<ram:TypeCode>" + typecode + "</ram:TypeCode>"
				+ "<ram:IssueDateTime>"
				+ DATE.udtFormat(trans.getIssueDate()) + "</ram:IssueDateTime>" // date
				// format
				// was
				// 20130605
				+ subjectNote
				+ rebateAgreement
				+ senderReg

				+ "</rsm:HeaderExchangedDocument>"
				+ "<rsm:SpecifiedSupplyChainTradeTransaction>");
		xml.append("<ram:ApplicableSupplyChainTradeAgreement>");
		if (trans.getReferenceNumber() != null) {
			xml.append("<ram:BuyerReference>" + XMLTools.encodeXML(trans.getReferenceNumber()) + "</ram:BuyerReference>");

		}
		xml.append("<ram:SellerTradeParty>");
		xml.append(getTradePartyAsXML(trans.getSender(), true, false));
		xml.append("</ram:SellerTradeParty>"
				+ "<ram:BuyerTradeParty>");
		// + " <ID>GE2020211</ID>"
		// + " <GlobalID schemeID=\"0088\">4000001987658</GlobalID>"

		xml.append(getTradePartyAsXML(trans.getRecipient(), false, false));
		if (trans.getOwnVATID() != null && trans.getOwnOrganisationName() != null) {
			xml.append("<ram:SpecifiedTaxRegistration><ram:ID schemeID=\"VA\">"
					+ XMLTools.encodeXML(trans.getOwnVATID()) + "</ram:ID>"
					+ "</ram:SpecifiedTaxRegistration>");
		}

		xml.append("</ram:BuyerTradeParty>");

		if (trans.getSellerOrderReferencedDocument() != null) {
			xml.append("<ram:SellerOrderReferencedDocument>"
					+ "<ram:IssuerAssignedID>"
					+ XMLTools.encodeXML(trans.getSellerOrderReferencedDocument().getIssuerAssignedID()) + "</ram:IssuerAssignedID>"
					+ "</ram:SellerOrderReferencedDocument>");
		}
		if (trans.getBuyerOrderReferencedDocument() != null) {
			xml.append("<ram:BuyerOrderReferencedDocument>"
					+ "<ram:IssuerAssignedID>"
					+ XMLTools.encodeXML(trans.getBuyerOrderReferencedDocument().getIssuerAssignedID()) + "</ram:IssuerAssignedID>"
					+ "</ram:BuyerOrderReferencedDocument>");
		}
		xml.append("</ram:ApplicableSupplyChainTradeAgreement>"
				+ "<ram:ApplicableSupplyChainTradeDelivery>");
		if (this.trans.getDeliveryAddress() != null) {
			xml.append("<ram:ShipToTradeParty>" +
					getTradePartyAsXML(this.trans.getDeliveryAddress(), false, true) +
					"</ram:ShipToTradeParty>");
		}

		xml.append("<ram:ActualDeliverySupplyChainEvent>"
				+ "<ram:OccurrenceDateTime>");

		if (trans.getDeliveryDate() != null) {
			xml.append(DATE.udtFormat(trans.getDeliveryDate()));
		} else {
			throw new IllegalStateException("No delivery date provided");
		}
		xml.append("</ram:OccurrenceDateTime>");
		xml.append("</ram:ActualDeliverySupplyChainEvent>"
				/*
				 * + "<DeliveryNoteReferencedDocument>" +
				 * "<IssueDateTime format=\"102\">20130603</IssueDateTime>" +
				 * "<ID>2013-51112</ID>" +
				 * "</DeliveryNoteReferencedDocument>"
				 */
				+ "</ram:ApplicableSupplyChainTradeDelivery><ram:ApplicableSupplyChainTradeSettlement>"
				+ "<ram:PaymentReference>" + XMLTools.encodeXML(trans.getNumber()) + "</ram:PaymentReference>"
				+ "<ram:InvoiceCurrencyCode>" + trans.getCurrency() + "</ram:InvoiceCurrencyCode>");

		if (trans.getTradeSettlement() != null) {
			for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
				if (payment != null) {
					if (payment instanceof IZUGFeRDTradeSettlementPayment) {
						hasDueDate = true;
					}
					xml.append(payment.getSettlementXML(getProfile()));
				}
			}
		}

		final HashMap<BigDecimal, VATAmount> VATPercentAmountMap = calc.getVATPercentAmountMap();
		for (final BigDecimal currentTaxPercent : VATPercentAmountMap.keySet()) {
			final VATAmount amount = VATPercentAmountMap.get(currentTaxPercent);
			if (amount != null) {
				final String amountCategoryCode = amount.getCategoryCode();
				final String amountDueDateTypeCode = amount.getDueDateTypeCode();
				final boolean displayExemptionReason = CATEGORY_CODES_WITH_EXEMPTION_REASON.contains(amountCategoryCode);
				xml.append("<ram:ApplicableTradeTax>"
						+ "<ram:CalculatedAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(amount.getCalculated())
						+ "</ram:CalculatedAmount>" //currencyID=\"EUR\"
						+ "<ram:TypeCode>VAT</ram:TypeCode>"
						+ (displayExemptionReason ? exemptionReason : "")
						+ "<ram:BasisAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(amount.getBasis()) + "</ram:BasisAmount>" // currencyID=\"EUR\"
						+ "<ram:CategoryCode>" + amount.getCategoryCode() + "</ram:CategoryCode>"
						+ (amountDueDateTypeCode != null ? "<ram:DueDateTypeCode>" + amountDueDateTypeCode + "</ram:DueDateTypeCode>" : "")
						+ "<ram:ApplicablePercent>" + vatFormat(currentTaxPercent)
						+ "</ram:ApplicablePercent></ram:ApplicableTradeTax>");
			}
		}

		if (trans.getPaymentTerms() == null) {
			xml.append("<ram:SpecifiedTradePaymentTerms>"
					+ "<ram:Description>" + paymentTermsDescription + "</ram:Description>");

			if (trans.getTradeSettlement() != null) {
				for (final IZUGFeRDTradeSettlement payment : trans.getTradeSettlement()) {
					if (payment instanceof IZUGFeRDTradeSettlementDebit) {
						xml.append(payment.getPaymentXML());
					}
				}
			}

			if (hasDueDate && trans.getDueDate() != null) {
				xml.append("<ram:DueDateDateTime>" // $NON-NLS-2$
						+ DATE.udtFormat(trans.getDueDate())
						+ "</ram:DueDateDateTime>"); // 20130704

			}
			xml.append("</ram:SpecifiedTradePaymentTerms>");
		} else {
			xml.append(buildPaymentTermsXml());
		}

		xml.append("<ram:SpecifiedTradeSettlementMonetarySummation>"
				+ "<ram:LineTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(calc.getTotal()) + "</ram:LineTotalAmount>"
				// currencyID=\"EUR\"
				+ "<ram:ChargeTotalAmount currencyID=\"" + trans.getCurrency() + "\">"
				+ currencyFormat(calc.getChargeTotal()) + "</ram:ChargeTotalAmount>" // currencyID=\"EUR\"
				+ "<ram:AllowanceTotalAmount currencyID=\"" + trans.getCurrency() + "\">"
				+ currencyFormat(calc.getAllowanceTotal()) + "</ram:AllowanceTotalAmount>" //
				// currencyID=\"EUR\"
				// + " <ChargeTotalAmount currencyID=\"EUR\">5.80</ChargeTotalAmount>"
				// + " <AllowanceTotalAmount currencyID=\"EUR\">14.73</AllowanceTotalAmount>"
				+ "<ram:TaxBasisTotalAmount currencyID=\"" + trans.getCurrency() + "\">"
				+ currencyFormat(calc.getTaxBasis()) + "</ram:TaxBasisTotalAmount>"
				// //
				// currencyID=\"EUR\"
				+ "<ram:TaxTotalAmount currencyID=\"" + trans.getCurrency() + "\">"
				+ currencyFormat(calc.getGrandTotal().subtract(calc.getTaxBasis())) + "</ram:TaxTotalAmount>"
				+ "<ram:GrandTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(calc.getGrandTotal()) + "</ram:GrandTotalAmount>"
				// //
				// currencyID=\"EUR\"
				+ "<ram:TotalPrepaidAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(calc.getTotalPrepaid()) + "</ram:TotalPrepaidAmount>"
				+ "<ram:DuePayableAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(calc.getGrandTotal().subtract(calc.getTotalPrepaid())) + "</ram:DuePayableAmount>"
				// //
				// currencyID=\"EUR\"
				+ "</ram:SpecifiedTradeSettlementMonetarySummation>"
				+ "</ram:ApplicableSupplyChainTradeSettlement>");


		int lineID = 0;
		for (final IZUGFeRDExportableItem currentItem : trans.getZFItems()) {
			lineID++;
			if (currentItem.getProduct().getTaxExemptionReason() != null) {
				exemptionReason = "<ram:ExemptionReason>" + XMLTools.encodeXML(currentItem.getProduct().getTaxExemptionReason()) + "</ram:ExemptionReason>";
			}


			final LineCalculator lc = currentItem.getCalculation();
			xml.append("<ram:IncludedSupplyChainTradeLineItem>" +
					"<ram:AssociatedDocumentLineDocument>"
					+ "<ram:LineID>" + lineID + "</ram:LineID>"
					+ "</ram:AssociatedDocumentLineDocument>"
					+ "<ram:SpecifiedSupplyChainTradeAgreement>"
					+ "<ram:GrossPriceProductTradePrice>"
					+ "<ram:ChargeAmount currencyID=\"" + trans.getCurrency() + "\">" + priceFormat(lc.getPriceGross())
					+ "</ram:ChargeAmount>"
					+ "<ram:BasisQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit())
					+ "\">" + quantityFormat(currentItem.getBasisQuantity()) + "</ram:BasisQuantity>"
					// + " <AppliedTradeAllowanceCharge>"
					// + " <ChargeIndicator>false</ChargeIndicator>"
					// + " <ActualAmount currencyID=\"EUR\">0.6667</ActualAmount>"
					// + " <Reason>Rabatt</Reason>"
					// + " </AppliedTradeAllowanceCharge>"
					+ "</ram:GrossPriceProductTradePrice>"
					+ "<ram:NetPriceProductTradePrice>"
					+ "<ram:ChargeAmount currencyID=\"" + trans.getCurrency() + "\">" + priceFormat(currentItem.getPrice())
					+ "</ram:ChargeAmount>"
					+ "<ram:BasisQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit())
					+ "\">" + quantityFormat(currentItem.getBasisQuantity()) + "</ram:BasisQuantity>"
					+ "</ram:NetPriceProductTradePrice>"
					+ "</ram:SpecifiedSupplyChainTradeAgreement>"


					+ "<ram:SpecifiedSupplyChainTradeDelivery>"
					+ "<ram:BilledQuantity unitCode=\"" + XMLTools.encodeXML(currentItem.getProduct().getUnit()) + "\">"
					+ quantityFormat(currentItem.getQuantity()) + "</ram:BilledQuantity>"
					+ "</ram:SpecifiedSupplyChainTradeDelivery>"
					+ "<ram:SpecifiedSupplyChainTradeSettlement>"
					+ "<ram:ApplicableTradeTax>"
					+ "<ram:TypeCode>VAT</ram:TypeCode>"
					+ exemptionReason
					+ "<ram:CategoryCode>" + currentItem.getProduct().getTaxCategoryCode() + "</ram:CategoryCode>"

					+ "<ram:ApplicablePercent>"
					+ vatFormat(currentItem.getProduct().getVATPercent()) + "</ram:ApplicablePercent>"
					+ "</ram:ApplicableTradeTax>"
					+ "<ram:SpecifiedTradeSettlementMonetarySummation>"
					+ "<ram:LineTotalAmount currencyID=\"" + trans.getCurrency() + "\">" + currencyFormat(lc.getItemTotalNetAmount())
					+ "</ram:LineTotalAmount>"
					+ "</ram:SpecifiedTradeSettlementMonetarySummation>");
			if (currentItem.getAdditionalReferences() != null) {
				xml.append("<ram:AdditionalReferencedDocument><ram:ID>" + currentItem.getAdditionalReferences()[0].getIssuerAssignedID() + "</ram:ID><ram:TypeCode>130</ram:TypeCode></ram:AdditionalReferencedDocument>");
			}
			xml.append("</ram:SpecifiedSupplyChainTradeSettlement>"
					+ "<ram:SpecifiedTradeProduct>");
			// + " <GlobalID schemeID=\"0160\">4012345001235</GlobalID>"
			if (currentItem.getProduct().getSellerAssignedID() != null) {
				xml.append("<ram:SellerAssignedID>"
						+ XMLTools.encodeXML(currentItem.getProduct().getSellerAssignedID()) + "</ram:SellerAssignedID>");
			}
			if (currentItem.getProduct().getBuyerAssignedID() != null) {
				xml.append("<ram:BuyerAssignedID>"
						+ XMLTools.encodeXML(currentItem.getProduct().getBuyerAssignedID()) + "</ram:BuyerAssignedID>");
			}
			xml.append("<ram:Name>" + XMLTools.encodeXML(currentItem.getProduct().getName()) + "</ram:Name>"
					+ "<ram:Description>" + XMLTools.encodeXML(currentItem.getProduct().getDescription())
					+ "</ram:Description>"
					+ "</ram:SpecifiedTradeProduct>"
					+ "</ram:IncludedSupplyChainTradeLineItem>");
		}

		// + " <IncludedSupplyChainTradeLineItem>\n"
		// + " <AssociatedDocumentLineDocument>\n"
		// + " <IncludedNote>\n"
		// + " <Content>Wir erlauben uns Ihnen folgende Positionen aus der Lieferung Nr.
		// 2013-51112 in Rechnung zu stellen:</Content>\n"
		// + " </IncludedNote>\n"
		// + " </AssociatedDocumentLineDocument>\n"
		// + " </IncludedSupplyChainTradeLineItem>\n";

		xml.append("</rsm:SpecifiedSupplyChainTradeTransaction>"
				+ "</rsm:CrossIndustryDocument>");

		final byte[] zugferdRaw;
		zugferdRaw = xml.toString().getBytes(StandardCharsets.UTF_8);
		zugferdData = XMLTools.removeBOM(zugferdRaw);
	}

	private String buildPaymentTermsXml() {
		final IZUGFeRDPaymentTerms[] paymentTerms = trans.getExtendedPaymentTerms();

		String paymentTermsXml = "";
		if (paymentTerms == null || paymentTerms.length == 0) {
			return paymentTermsXml;
		}

		for (IZUGFeRDPaymentTerms pt : paymentTerms) {
			paymentTermsXml += "<ram:SpecifiedTradePaymentTerms>";

			final IZUGFeRDPaymentDiscountTerms discountTerms = pt.getDiscountTerms();
			final Date dueDate = pt.getDueDate();
			if (dueDate != null && discountTerms != null && discountTerms.getBaseDate() != null) {
				throw new IllegalStateException(
					"if paymentTerms.dueDate is specified, paymentTerms.discountTerms.baseDate has not to be specified");
			}
			paymentTermsXml += "<ram:Description>" + pt.getDescription() + "</ram:Description>";
			if (dueDate != null) {
				paymentTermsXml += "<ram:DueDateDateTime>";
				paymentTermsXml += DATE.udtFormat(dueDate);
				paymentTermsXml += "</ram:DueDateDateTime>";
			}

			if (discountTerms != null) {
				paymentTermsXml += "<ram:ApplicableTradePaymentDiscountTerms>";
				final String currency = trans.getCurrency();
				final String basisAmount = currencyFormat(calc.getGrandTotal());
				paymentTermsXml += "<ram:BasisAmount currencyID=\"" + currency + "\">" + basisAmount + "</ram:BasisAmount>";
				paymentTermsXml += "<ram:CalculationPercent>" + discountTerms.getCalculationPercentage().toString()
					+ "</ram:CalculationPercent>";

				if (discountTerms.getBaseDate() != null) {
					final Date baseDate = discountTerms.getBaseDate();
					paymentTermsXml += "<ram:BasisDateTime>";
					paymentTermsXml += DATE.udtFormat(baseDate);
					paymentTermsXml += "</ram:BasisDateTime>";

					paymentTermsXml += "<ram:BasisPeriodMeasure unitCode=\"" + discountTerms.getBasePeriodUnitCode() + "\">"
						+ discountTerms.getBasePeriodMeasure() + "</ram:BasisPeriodMeasure>";
				}

				paymentTermsXml += "</ram:ApplicableTradePaymentDiscountTerms>";
			}

			paymentTermsXml += "</ram:SpecifiedTradePaymentTerms>";
		}
		return paymentTermsXml;
	}

}
