<?php

if ( ! defined('BASEPATH')) {
    exit('No direct script access allowed');
}

/*
 * InvoicePlane
 *
 * @author      InvoicePlane Developers & Contributors
 * @copyright   Copyright (c) 2012 - 2018 InvoicePlane.com
 * @license     https://invoiceplane.com/license.txt
 * @link        https://invoiceplane.com
 *
 * ZUGFeRD v1.0 (Historic file)
 * Only for update purpose (from 1.4.7 to 1.6.2) when the include_zugferd option are active. If not, I'm deleted!
 */

/**
 * Class Zugferdv10Xml.
 */
#[\AllowDynamicProperties]
class Zugferdv10Xml
{
    public $invoice;

    public $doc;

    public $root;

    public function __construct(array $params)
    {
        $CI                 = &get_instance();
        $this->invoice      = $params['invoice'];
        $this->items        = $params['items'];
        $this->filename     = $params['filename'];
        $this->currencyCode = $CI->mdl_settings->setting('currency_code');
    }

    public function xml()
    {
        $this->doc               = new DOMDocument('1.0', 'UTF-8');
        $this->doc->formatOutput = true;

        $this->root = $this->xmlRoot();
        $this->root->appendChild($this->xmlSpecifiedExchangedDocumentContext());
        $this->root->appendChild($this->xmlHeaderExchangedDocument());
        $this->root->appendChild($this->xmlSpecifiedSupplyChainTradeTransaction());

        $this->doc->appendChild($this->root);
        $this->doc->save(UPLOADS_FOLDER . 'temp/' . $this->filename . '.xml');
        // return $this->doc->saveXML();
    }

    /**
     * @return float[]|int[]
     */
    public function itemsSubtotalGroupedByTaxPercent(): array
    {
        $result = [];
        foreach ($this->items as $item) {
            if ($item->item_tax_rate_percent == 0) {
                continue;
            }

            if ( ! isset($result[$item->item_tax_rate_percent])) {
                $result[$item->item_tax_rate_percent] = 0;
            }

            $result[$item->item_tax_rate_percent] += $item->item_subtotal;
        }

        return $result;
    }

    // ===========================================================================
    // elements helpers
    // ===========================================================================

    /**
     * @return string|null
     */
    public function formattedDate($date)
    {
        if ($date) {
            $date = DateTime::createFromFormat('Y-m-d', $date);

            return $date->format('Ymd');
        }

        return '';
    }

    public function formattedFloat($amount, $nb_decimals = 2): string
    {
        return number_format((float) $amount, $nb_decimals, '.', '');
    }

    protected function xmlRoot()
    {
        $node = $this->doc->createElement('rsm:CrossIndustryDocument');
        $node->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
        $node->setAttribute('xmlns:rsm', 'urn:ferd:CrossIndustryDocument:invoice:1p0');
        $node->setAttribute('xmlns:ram', 'urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:12');
        $node->setAttribute('xmlns:udt', 'urn:un:unece:uncefact:data:standard:UnqualifiedDataType:15');

        return $node;
    }

    protected function xmlSpecifiedExchangedDocumentContext()
    {
        $node          = $this->doc->createElement('rsm:SpecifiedExchangedDocumentContext');
        $guidelineNode = $this->doc->createElement('ram:GuidelineSpecifiedDocumentContextParameter');
        $guidelineNode->appendChild($this->doc->createElement('ram:ID', 'urn:ferd:CrossIndustryDocument:invoice:1p0:basic'));

        $node->appendChild($guidelineNode);

        return $node;
    }

    protected function xmlHeaderExchangedDocument()
    {
        $node = $this->doc->createElement('rsm:HeaderExchangedDocument');

        $node->appendChild($this->doc->createElement('ram:ID', $this->invoice->invoice_number));
        $node->appendChild($this->doc->createElement('ram:Name', trans('invoice')));
        $node->appendChild($this->doc->createElement('ram:TypeCode', 380));

        // IssueDateTime
        $dateNode = $this->doc->createElement('ram:IssueDateTime');
        $dateNode->appendChild($this->dateElement($this->invoice->invoice_date_created));

        $node->appendChild($dateNode);

        // IncludedNote
        $noteNode = $this->doc->createElement('ram:IncludedNote');
        $noteNode->appendChild($this->doc->createElement('ram:Content', htmlsc($this->invoice->invoice_terms)));

        $node->appendChild($noteNode);

        return $node;
    }

    protected function dateElement($date)
    {
        $el = $this->doc->createElement('udt:DateTimeString', $this->formattedDate($date));
        $el->setAttribute('format', 102);

        return $el;
    }

    protected function xmlSpecifiedSupplyChainTradeTransaction()
    {
        $node = $this->doc->createElement('rsm:SpecifiedSupplyChainTradeTransaction');
        $node->appendChild($this->xmlApplicableSupplyChainTradeAgreement());
        $node->appendChild($this->xmlApplicableSupplyChainTradeDelivery());
        $node->appendChild($this->xmlApplicableSupplyChainTradeSettlement());
        foreach ($this->items as $index => $item) {
            $node->appendChild($this->xmlIncludedSupplyChainTradeLineItem($index + 1, $item));
        }

        return $node;
    }

    protected function xmlApplicableSupplyChainTradeAgreement()
    {
        $node = $this->doc->createElement('ram:ApplicableSupplyChainTradeAgreement');
        $node->appendChild($this->xmlSellerTradeParty());
        $node->appendChild($this->xmlBuyerTradeParty());

        return $node;
    }

    protected function xmlSellerTradeParty($index = '', $item = '')
    {
        $node = $this->doc->createElement('ram:SellerTradeParty');

        if ( ! empty($this->invoice->user_company)) {
            $node->appendChild($this->doc->createElement('ram:Name', htmlsc($this->invoice->user_company)));
            $node->appendChild($this->doc->createElement('ram:DefinedTradeContact', htmlsc($this->invoice->user_name)));
        } else {
            $node->appendChild($this->doc->createElement('ram:Name', htmlsc($this->invoice->user_name)));
        }

        // PostalTradeAddress
        $addressNode = $this->doc->createElement('ram:PostalTradeAddress');
        $addressNode->appendChild($this->doc->createElement('ram:PostcodeCode', htmlsc($this->invoice->user_zip)));
        $addressNode->appendChild($this->doc->createElement('ram:LineOne', htmlsc($this->invoice->user_address_1)));
        $addressNode->appendChild($this->doc->createElement('ram:LineTwo', htmlsc($this->invoice->user_address_2)));
        $addressNode->appendChild($this->doc->createElement('ram:CityName', htmlsc($this->invoice->user_city)));
        $addressNode->appendChild($this->doc->createElement('ram:CountryID', htmlsc($this->invoice->user_country)));

        $node->appendChild($addressNode);

        return $node;
    }

    protected function xmlBuyerTradeParty($index = '', $item = '')
    {
        $node = $this->doc->createElement('ram:BuyerTradeParty');
        $node->appendChild($this->doc->createElement('ram:Name', htmlsc($this->invoice->client_name)));

        // PostalTradeAddress
        $addressNode = $this->doc->createElement('ram:PostalTradeAddress');
        $addressNode->appendChild($this->doc->createElement('ram:PostcodeCode', htmlsc($this->invoice->client_zip)));
        $addressNode->appendChild($this->doc->createElement('ram:LineOne', htmlsc($this->invoice->client_address_1)));
        $addressNode->appendChild($this->doc->createElement('ram:LineTwo', htmlsc($this->invoice->client_address_2)));
        $addressNode->appendChild($this->doc->createElement('ram:CityName', htmlsc($this->invoice->client_city)));
        $addressNode->appendChild($this->doc->createElement('ram:CountryID', htmlsc($this->invoice->client_country)));

        $node->appendChild($addressNode);

        // SpecifiedTaxRegistration
        $node->appendChild($this->xmlSpecifiedTaxRegistration('VA', $this->invoice->client_vat_id));
        $node->appendChild($this->xmlSpecifiedTaxRegistration('FC', htmlsc($this->invoice->client_tax_code)));

        return $node;
    }

    /**
     * @param string $schemeID
     */
    protected function xmlSpecifiedTaxRegistration($schemeID, $content)
    {
        $node = $this->doc->createElement('ram:SpecifiedTaxRegistration');
        $el   = $this->doc->createElement('ram:ID', $content);
        $el->setAttribute('schemeID', $schemeID);

        $node->appendChild($el);

        return $node;
    }

    protected function xmlApplicableSupplyChainTradeDelivery()
    {
        $node = $this->doc->createElement('ram:ApplicableSupplyChainTradeDelivery');

        // ActualDeliverySupplyChainEvent
        $eventNode = $this->doc->createElement('ram:ActualDeliverySupplyChainEvent');
        $dateNode  = $this->doc->createElement('ram:OccurrenceDateTime');
        $dateNode->appendChild($this->dateElement($this->invoice->invoice_date_created));

        $eventNode->appendChild($dateNode);

        $node->appendChild($eventNode);

        return $node;
    }

    protected function xmlApplicableSupplyChainTradeSettlement()
    {
        $node = $this->doc->createElement('ram:ApplicableSupplyChainTradeSettlement');

        $node->appendChild($this->doc->createElement('ram:PaymentReference', $this->invoice->invoice_number));
        $node->appendChild($this->doc->createElement('ram:InvoiceCurrencyCode', $this->currencyCode));

        // taxes
        foreach ($this->itemsSubtotalGroupedByTaxPercent() as $percent => $subtotal) {
            $node->appendChild($this->xmlApplicableTradeTax($percent, $subtotal));
        }

        // sums
        $node->appendChild($this->xmlSpecifiedTradeSettlementMonetarySummation());

        return $node;
    }

    protected function xmlApplicableTradeTax($percent, $subtotal)
    {
        $node = $this->doc->createElement('ram:ApplicableTradeTax');
        $node->appendChild($this->currencyElement('ram:CalculatedAmount', $subtotal * $percent / 100));
        $node->appendChild($this->doc->createElement('ram:TypeCode', 'VAT'));
        $node->appendChild($this->currencyElement('ram:BasisAmount', $subtotal));
        $node->appendChild($this->doc->createElement('ram:CategoryCode', 'S'));
        $node->appendChild($this->doc->createElement('ram:ApplicablePercent', $percent));

        return $node;
    }

    /**
     * @param string $name
     */
    protected function currencyElement($name, $amount, $nb_decimals = 2)
    {
        $el = $this->doc->createElement($name, $this->formattedFloat($amount, $nb_decimals));
        $el->setAttribute('currencyID', $this->currencyCode);

        return $el;
    }

    protected function xmlSpecifiedTradeSettlementMonetarySummation()
    {
        $node = $this->doc->createElement('ram:SpecifiedTradeSettlementMonetarySummation');
        $node->appendChild($this->currencyElement('ram:LineTotalAmount', $this->invoice->invoice_item_subtotal));
        $node->appendChild($this->currencyElement('ram:ChargeTotalAmount', 0));
        $node->appendChild($this->currencyElement('ram:AllowanceTotalAmount', 0));
        $node->appendChild($this->currencyElement('ram:TaxBasisTotalAmount', $this->invoice->invoice_item_subtotal));
        $node->appendChild($this->currencyElement('ram:TaxTotalAmount', $this->invoice->invoice_item_tax_total));
        $node->appendChild($this->currencyElement('ram:GrandTotalAmount', $this->invoice->invoice_total));
        $node->appendChild($this->currencyElement('ram:TotalPrepaidAmount', $this->invoice->invoice_paid));
        $node->appendChild($this->currencyElement('ram:DuePayableAmount', $this->invoice->invoice_balance));

        return $node;
    }

    protected function xmlIncludedSupplyChainTradeLineItem($lineNumber, $item)
    {
        $node = $this->doc->createElement('ram:IncludedSupplyChainTradeLineItem');

        // AssociatedDocumentLineDocument
        $lineNode = $this->doc->createElement('ram:AssociatedDocumentLineDocument');
        $lineNode->appendChild($this->doc->createElement('ram:LineID', $lineNumber));

        $node->appendChild($lineNode);

        // SpecifiedSupplyChainTradeAgreement
        $node->appendChild($this->xmlSpecifiedSupplyChainTradeAgreement($item));

        // SpecifiedSupplyChainTradeDelivery
        $deliveyNode = $this->doc->createElement('ram:SpecifiedSupplyChainTradeDelivery');
        $deliveyNode->appendChild($this->quantityElement('ram:BilledQuantity', $item->item_quantity));

        $node->appendChild($deliveyNode);

        // SpecifiedSupplyChainTradeSettlement
        $node->appendChild($this->xmlSpecifiedSupplyChainTradeSettlement($item));

        // SpecifiedTradeProduct
        $tradeNode = $this->doc->createElement('ram:SpecifiedTradeProduct');
        $tradeNode->appendChild($this->doc->createElement('ram:Name', htmlsc($item->item_name) . "\n" . htmlsc($item->item_description)));

        $node->appendChild($tradeNode);

        return $node;
    }

    // ===========================================================================
    // helpers
    // ===========================================================================

    protected function xmlSpecifiedSupplyChainTradeAgreement($item)
    {
        $node = $this->doc->createElement('ram:SpecifiedSupplyChainTradeAgreement');

        // GrossPriceProductTradePrice
        $grossPriceNode = $this->doc->createElement('ram:GrossPriceProductTradePrice');
        $grossPriceNode->appendChild($this->currencyElement('ram:ChargeAmount', $item->item_price, 4));

        $node->appendChild($grossPriceNode);

        // NetPriceProductTradePrice
        $netPriceNode = $this->doc->createElement('ram:NetPriceProductTradePrice');
        $netPriceNode->appendChild($this->currencyElement('ram:ChargeAmount', $item->item_price, 4));

        $node->appendChild($netPriceNode);

        return $node;
    }

    /**
     * @param string $name
     */
    protected function quantityElement($name, $quantity)
    {
        $el = $this->doc->createElement($name, $this->formattedFloat($quantity, 4));
        $el->setAttribute('unitCode', 'C62');

        return $el;
    }

    protected function xmlSpecifiedSupplyChainTradeSettlement($item)
    {
        $node = $this->doc->createElement('ram:SpecifiedSupplyChainTradeSettlement');

        // ApplicableTradeTax
        if ($item->item_tax_rate_percent > 0) {
            $taxNode = $this->doc->createElement('ram:ApplicableTradeTax');
            $taxNode->appendChild($this->doc->createElement('ram:TypeCode', 'VAT'));
            $taxNode->appendChild($this->doc->createElement('ram:ApplicablePercent', $item->item_tax_rate_percent));
            $node->appendChild($taxNode);
        }

        // SpecifiedTradeSettlementMonetarySummation
        $sumNode = $this->doc->createElement('ram:SpecifiedTradeSettlementMonetarySummation');
        $sumNode->appendChild($this->currencyElement('ram:LineTotalAmount', $item->item_subtotal));

        $node->appendChild($sumNode);

        return $node;
    }
}
