<?php
// Copyright (c) Pickware GmbH. All rights reserved.
// This file is part of software that is released under a proprietary license.
// You must not copy, modify, distribute, make publicly available, or execute
// its contents or parts thereof without express permission by the copyright
// holder, unless otherwise permitted by law.

namespace Shopware\Plugins\ViisonDHL\Classes;

use Shopware\Plugins\ViisonDHL\Classes\DhlServiceOptions\PreferredDeliveryServiceOption;
use Shopware\Plugins\ViisonDHL\Util;

/**
 * Class CreateShipmentOrderRequest
 *
 * This class represents an DHL SOAP API call to the CreateShipmentOrder method.
 * Therefor it overrides the constructor expecting the shipment information and provides a setter for
 * the receiver as well as the shipment weight.
 *
 * @package Shopware\Plugins\ViisonDHL\Classes
 */
class CreateShipmentOrderRequest implements SoapRequest
{
    /**
     * Maximum field lengths according to DHL documentation.
     */
    const MAX_FIRST_NAME_LENGTH = 30; // The maximum according to the DHL API is 50, but the real maximum length is 30, since no more than 30 characters are printed onto the label
    const MAX_LAST_NAME_LENGTH = 30; // The maximum according to the DHL API is 50, but the real maximum length is 30, since no more than 30 characters are printed onto the label
    const MAX_ADDRESS_NAME_LENGTH = 50;
    const MAX_STREET_NAME_LENGTH = 50; // No real maximum exists for this and the following values since GKP API v2.x, we use an arbitrary value that makes sure that the characters do not get too small (the font size is chosen by DHL so that the full content fits)
    const MAX_STREET_NUMBER_LENGTH = 50;
    const MAX_ADDITIONAL_ADDRESS_LINE_LENGTH = 50;
    const MAX_ZIP_LENGTH = 50;
    const MAX_CITY_LENGTH = 50;
    const MAX_PHONE_NUMBER_LENGTH = 20;
    const MAX_EMAIL_LENGTH = 100;
    const MAX_WEIGHT_LENGTH = 22;
    const MAX_LENGTH_LENGTH = 22;
    const MAX_WIDTH_LENGTH = 22;
    const MAX_HEIGHT_LENGTH = 22;

    const FRANKATUR_CODES = [
        'DDP',
        'DXV',
        'DAP',
        'DDX',
        'CPT',
    ];

    const INCOTERM_CODES = [
        'DDP',
        'DXV',
        'DAP',
        'DDX',
    ];

    const INCOTERM_EXPRESS_CODES = [
        'EXW',
        'FCA',
        'CPT',
        'CIP',
        'DAT',
        'DAP',
        'DDP',
    ];

    /**
     * Const field values for DHL Packing Station
     */
    const MOB_PACKING_STATION = 'packstation';
    const MOB_POST_BRANCH = 'filiale';

    /**
     * The shipment details contained in the request body.
     */
    private $shipmentDetails;

    /**
     * The shipper information contained in the request body.
     */
    private $shipper;

    /**
     * The receiver information contained in the request body.
     */
    private $receiver;

    /**
     * The optional export document which is only added for international shipments.
     */
    private $exportDocument;

    /**
     * This option determines if a label should only be generated if the address is codeable ("leitcodierbar").
     */
    private $printOnlyIfCodeable;

    /**
     * The shop id, for which the label should be created.
     */
    private $shopId;

    /**
     * @var SoapRequestBodyMutator[]
     */
    private $soapBodyRequestMutators = [];

    /**
     * The main constructor creating all necessary parts for the request body.
     *
     * @param array $order An array containing the main order information.
     * @param array $sender An array containing information about the shipper.
     * @param string $EKP The DHL EKP of this shipment.
     * @param array $productType The product type of this shipment.
     * @param string $partnerId The DHL partner id of the shipper.
     * @param float $weight The weight of this shipment.
     * @param array $packageDimensions The length, width and height of the package.
     * @param int $shopId
     */
    public function __construct($order, $sender, $EKP, $product, $partnerId, $weight, $packageDimensions, $printOnlyIfCodeable, $shopId)
    {
        $this->shopId = $shopId;
        $this->shipmentDetails = $this->createShipmentDetails($order, $EKP, $product, $partnerId, $weight, $packageDimensions);
        $this->shipper = $this->createShipper($sender, $order);
        $this->printOnlyIfCodeable = $printOnlyIfCodeable;
    }

    /**
     * Creates the main request body containing all information like shipper and receiver.
     *
     * @param array $version The DHL API version which should be used.
     * @return array The request body containing all shipment information.
     */
    public function getBody($version)
    {
        $shipment = [
            'ShipmentDetails' => $this->shipmentDetails,
            'Shipper' => $this->shipper,
            'Receiver' => $this->receiver,
        ];

        if ($this->receiver['Address']['Origin']['countryISOCode'] == 'DE') {
            $shipperPhoneNumber = $shipment['Shipper']['Communication']['phone'];
            // Shipper phone number not required for national shipments, remove it if it is a pseudo phone number consisting of zeros
            $strippedPhoneNumber = preg_replace('/0/', '', $shipperPhoneNumber);
            if (empty($strippedPhoneNumber)) {
                unset($shipment['Shipper']['Communication']['phone']);
            }
        }

        if (!empty($this->exportDocument)) {
            // Add export document
            $shipment['ExportDocument'] = $this->exportDocument;
        }

        $body = [
            'Version' => $version,
            'ShipmentOrder' => [
                'sequenceNumber' => 1,
                'Shipment' => $shipment,
                'PrintOnlyIfCodeable' => [
                    'active' => $this->printOnlyIfCodeable ? 1 : 0,
                ],
            ],
            'labelResponseType' => 'B64', // Get the label as base64 encoded pdf embedded in the response
        ];

        $body = Shopware()->Events()->filter(
            'Shopware_ViisonDHL_CreateShipmentDDRequest_GetBody_FilterBody',
            $body,
            [
                'subject' => $this,
            ]
        );
        // Fire legacy event
        // TODO: Remove this as soon as no other plugins depend on this old event and
        //       the adoption of the new releases of other plugins is close to 100%.
        $body = Shopware()->Events()->filter(
            'Shopware_ViisonIntraship_CreateShipmentDDRequest_GetBody_FilterBody',
            $body,
            [
                'subject' => $this,
            ]
        );

        foreach ($this->soapBodyRequestMutators as $soapBodyRequestMutator) {
            $body = $soapBodyRequestMutator->mutate($body);
        }

        return $body;
    }

    /**
     * Sets a new shipment receiver which is created using the given data.
     *
     * @param array $details The shipment details used to create the new receiver.
     * @param null|int $orderId
     */
    public function setReceiver($details, $orderId = null)
    {
        $this->receiver = $this->createReceiver($details, $orderId);
    }

    /**
     * Adds the necessary information for a 'cash on delivery' (COD) shipment.
     *
     * @param float $amount The amount of the COD, which should never exceed 22 digits.
     * @param string $currency The currency of the payment as its ISO-4217 code (3 characters).
     * @param array $bankData The information about the bank including the reason for payment.
     */
    public function setCashOnDelivery($amount, $currency, $bankData)
    {
        // Add the service to the shipment details
        $this->addService('CashOnDelivery', [
            'addFee' => 1, // We already include the fee (2 Euro Übermittlungsentgelt) in the amount
            'codAmount' => $amount,
        ]);

        // Set the bank information
        $this->shipmentDetails['BankData'] = $bankData;
    }

    /**
     * Marks this shipment to make use of the 'Personal handover' service (Serviceoption 'Persönliche Übergabe').
     */
    public function setPersonalHandover()
    {
        $this->addService('NamedPersonOnly');
    }

    /**
     * Marks this shipment to make use of the 'Visual Age Check' service (Serviceoption 'Alterssichtprüfung').
     */
    public function setMinimumAge($minimumAge)
    {
        $this->addService('VisualCheckOfAge', [
            'type' => sprintf('A%d', $minimumAge),
        ]);
    }

    /**
     * Marks this shipment to make use of the 'Ident Check' service (Serviceoption 'Ident Check').
     *
     * @param int $minimumAge The set minimum age for ident check.
     * @param array $shippingDetails The first and last name of the reciever.
     */
    public function setIdentCheck($minimumAge, $shippingDetails)
    {
        $this->addService('IdentCheck', [
            'Ident' => [
                'surname' => $shippingDetails['surname'],
                'givenName' => $shippingDetails['givenname'],
                'dateOfBirth' => '', // Only date of birth or minimum age are required but the current iteration only supports minimum Age
                'minimumAge' => sprintf('A%d', $minimumAge),
            ],
        ]);
    }

    /**
     * Enables the 'Parcel Outlet Routing' service (Serviceoption 'Filial Routing').
     *
     * @param string $email The email address DHL should send the notification to.
     */
    public function enableParcelOutletRouting($email)
    {
        $this->addService('ParcelOutletRouting', [
            'details' => $email,
        ]);
    }

    /**
     * Enables the 'Endorsement' service.
     *
     * @param string $type
     */
    public function enableEndorsement($type)
    {
        $this->addService('Endorsement', [
            'type' => $type,
        ]);
    }

    /**
     * Enables the 'Postal Delivery Duty Paid' service (Serviceoption 'Postal Delivery Duty Paid').
     *
     */
    public function enablePostalDeliveryDutyPaid()
    {
        $this->addService('PDDP');
    }

    /**
     * Enables the 'Closest Droppoint Delivery' service (Serviceoption 'Closest Droppoint Delivery').
     *
     */
    public function enableClosestDroppointDelivery()
    {
        $this->addService('CDP');
    }

    /**
     * Enables the 'Signed for by Recipient' service (Serviceoption 'Signed for by Recipient').
     *
     */
    public function enableSignedForByRecipient()
    {
        $this->addService('SignedForByRecipient');
    }

    /**
     * Enables the dispatch notification.
     *
     * The delivery notification is sent to the shipment receiver once the merchant has shipped the shipment.
     *
     * Description by DHL support:
     * Versandbestätigung: Die Versandbestätigung wird nach erfolgtem Tagesschluss an dort hinterlegte
     * Mailadresse ($mail) versandt.
     *
     * @param string $email The email address DHL should send the notification to.
     */
    public function setDispatchEmailNotification($email)
    {
        $this->shipmentDetails['Notification'] = [
            'recipientEmailAddress' => $email,
        ];
    }

    /**
     * Enables the delivery announcement.
     *
     * The delivery announcement is sent to the shipment receiver once the shipment has been processed by DHL to tell
     * the receiver when he can expect the shipment.
     *
     * @param string $email The email address DHL should send the announcement to.
     */
    public function setDeliveryEmailNotification($email)
    {
        $this->receiver['Communication']['email'] = $email;
    }

    /**
     * Adds the necessary information for an export document, which is required at least for some international
     * shipments.
     *
     * @param array $order The order containing the date, total amount and currency.
     * @param array $exportInformation An array containing information like the Incoterm or the items contained in the order.
     * @param float $totalWeight The (optional) total weight of the order used to calculate the weight of each item. Pass a value only if the item weights shall be calculated proportionately from that weight.
     */
    public function setExportDocument($order, $exportInformation, $totalWeight = null)
    {
        // Create export document
        $this->exportDocument = [
            'exportType' => $exportInformation['typeOfShipment'],
            'exportTypeDescription' => mb_substr($exportInformation['typeDescription'], 0, 30, 'utf-8'),
            'termsOfTrade' => $exportInformation['termsOfTrade'],
            'placeOfCommital' => $exportInformation['placeOfCommittal'], // Sic! Committal is a typo by DHL!
            'additionalFee' => $order['shippingCosts'],
            'sendersCustomsReference' => $exportInformation['sendersCustomsReference'],
            'addresseesCustomsReference' => $exportInformation['addresseesCustomsReference'],
        ];
        if ($exportInformation['invoice'] !== null) {
            // Use the date and number from the invoice document
            $this->exportDocument['invoiceDate'] = $exportInformation['invoice']['date'];
            $this->exportDocument['invoiceNumber'] = $exportInformation['invoice']['number'];
        }

        // Add the shipped order items as export document positions
        $shipperCountryCode = $this->shipper['Address']['Origin']['countryISOCode'];
        $exportDocPositions = Shopware()->Container()->get('viison_dhl.export_document')->getExportDocPositions(
            $exportInformation['items'],
            $shipperCountryCode,
            $totalWeight
        );
        $this->exportDocument['ExportDocPosition'] = $exportDocPositions;
    }

    /**
     * Manually encode the export document positions as SoapVars, to work around a bug in several
     * PHP versions (reproduced e.g. in v7.0.5 or v5.6.24), which lead to wrong SOAP request data
     * when adding more than one position. In that case, one 'ExportDocPosition' node was added for
     * each position, but all these nodes were just empty. By manually encoding them and explicitly
     * naming all position SoapVars 'ExportDocPosition', we can work around this issue and make
     * it work in all PHP versions.
     */
    public function encodeExportDocumentPositionsAsSoapVars()
    {
        $this->exportDocument['ExportDocPosition'] = array_map(function ($position) {
            return new \SoapVar($position, SOAP_ENC_OBJECT, null, null, 'ExportDocPosition');
        }, $this->exportDocument['ExportDocPosition']);
    }

    /**
     * Converts all "money" values of the current export document into EUR.
     *
     * @param float|int $currencyConversionFactor The conversion factor to convert to Euro
     */
    public function multiplyMoneyValuesWithFactor($currencyConversionFactor)
    {
        // Convert field "additional fee" that are usually the shipping costs:
        $this->exportDocument['additionalFee'] = round($this->exportDocument['additionalFee'] * $currencyConversionFactor, 2);

        // Convert the value of every item in the export document to EUR
        $this->exportDocument['ExportDocPosition'] = array_map(function (array $position) use ($currencyConversionFactor) {
            $position['customsValue'] = round($position['customsValue'] * $currencyConversionFactor, 2);
            $position['currency'] = 'EUR';

            return $position;
        }, $this->exportDocument['ExportDocPosition']);
    }

    /**
     * Adds the given service option to the shipment details. For each service, a new service
     * tag is added to the request with group name and service name describing the exact type
     * of service.
     *
     * @param string $serviceGroupName The name of the group, to which the option shall be added.
     * @param string $serviceName The name of the service option, which shall be added.
     * @param array $value The value of the newly added service option.
     */
    public function addService($serviceName, $value = [])
    {
        // Set active to 1 so that the service gets activated
        $value['active'] = 1;
        $this->shipmentDetails['Service'][$serviceName] = $value;
    }

    /**
     * Creates a structured array containing the basic shipment details. This structure is
     * conform to the one of the DHL API.
     *
     * @param array $order An array containing the main order information.
     * @param string $EKP The DHL EKP of this shipment.
     * @param array $product The product of this shipment.
     * @param string $partnerId The DHL partner id of the shipper.
     * @param float $weight The weight of this shipment.
     * @param array $packageDimensions The length, widht and height of this package.
     * @return array A structured array containing the shipment details.
     */
    private function createShipmentDetails($order, $EKP, $product, $partnerId, $weight, $packageDimensions)
    {
        $shipmentDetails = [
            'shipmentDate' => date('Y-m-d'),
            'product' => $product['product'],
            'accountNumber' => $EKP . $product['process'] . $partnerId,
            'ShipmentItem' => [
                'weightInKG' => floatval($weight),
            ],
            'Service' => [],
            'customerReference' => $order['orderNumber'],
        ];

        if ($packageDimensions !== null) {
            // Add the package dimensions
            $shipmentDetails['ShipmentItem']['lengthInCM'] = $packageDimensions['length'];
            $shipmentDetails['ShipmentItem']['widthInCM'] = $packageDimensions['width'];
            $shipmentDetails['ShipmentItem']['heightInCM'] = $packageDimensions['height'];
        }

        return $shipmentDetails;
    }

    /**
     * Creates a structured array containing the shippers data. This structure is
     * conform to the one of the DHL API.
     *
     * @param array $sender The sender information as an array.
     * @param array $order The order information as an array.
     * @return array A structured array containing the shipper information.
     */
    private function createShipper($sender, $order)
    {
        $shipper = [
            'Name' => [
                'name1' => mb_substr($sender['companyName'], 0, self::MAX_ADDRESS_NAME_LENGTH, 'utf-8'),
                'name2' => mb_substr($sender['companyName2'], 0, self::MAX_ADDRESS_NAME_LENGTH, 'utf-8'),
            ],
            'Address' => [
                'streetName' => mb_substr($sender['streetName'], 0, self::MAX_STREET_NAME_LENGTH, 'utf-8'),
                'streetNumber' => mb_substr($sender['streetNumber'], 0, self::MAX_STREET_NUMBER_LENGTH, 'utf-8'),
                'zip' => $sender['zip'],
                'city' => mb_substr($sender['city'], 0, self::MAX_CITY_LENGTH, 'utf-8'),
                'Origin' => [
                    'countryISOCode' => $sender['countryISO'],
                ],
            ],
            'Communication' => [
                'contactPerson' => mb_substr($sender['contactPerson'], 0, 30, 'utf-8'),
                'phone' => mb_substr($sender['phoneNumber'], 0, self::MAX_PHONE_NUMBER_LENGTH),
            ],
        ];

        if (mb_strlen($sender['email']) > 0) {
            $shipper['Communication']['email'] = $sender['email'];
        }

        return $shipper;
    }

    /**
     * Creates a structured array containing the shipment receivers data. This structure is
     * conform to the one of the DHL API.
     *
     * @param array $order The order information as an array.
     * @param null|int $orderId The given orderId.
     * @return array A structured array containing the receiver information.
     * @throws \Exception
     */
    private function createReceiver($order, $orderId)
    {
        $order = array_map('trim', $order);

        // Company incl. Communication
        $salutation = (!empty($order['salutation']) && !empty($order['countryiso'])) ? Util::instance()->localizeSalutation($order['salutation'], $order['countryiso'], $this->shopId) : '';
        $person = $this->shortenPersonNames([
            'salutation' => (!empty($salutation)) ? ucfirst($salutation) : '',
            'firstname' => (!empty($order['firstname'])) ? $order['firstname'] : '',
            'lastname' => (!empty($order['lastname'])) ? $order['lastname'] : '',
        ]);
        $names = [];
        $communication = [];

        if ($this->printOnlyIfCodeable && !$order['streetnumber']) {
            throw NonCodableAddressDHLException::missingStreetNumberForCodableAddress();
        }

        $data = $this->getPackingStationPluginData($order, $orderId);
        $order = $data['order'];
        $packingStationData = $data['packStationData'];

        $personName = implode(' ', [$person['name']['salutation'], $person['name']['firstname'], $person['name']['lastname']]);
        if (!empty($order['company'])) {
            // Company
            $names[] = mb_substr($order['company'], 0, self::MAX_ADDRESS_NAME_LENGTH, 'utf-8');
            $names[] = mb_substr($order['department'], 0, self::MAX_ADDRESS_NAME_LENGTH, 'utf-8');
            $names[] = $personName;
            $communication['contactPerson'] = $personName;
        } else {
            // Person
            //$company['Person'] = $person['name'];
            $names[] = $personName;
            // TODO
            // Remove duplicate name on label
            if ($order['countryiso'] === 'DE') { // Unfortunately the contact person is mandatory for all international shipments
                $communication['contactPerson'] = ''; // Otherwise computed from the company person by DHL
            }
        }

        // Additional communication
        $phoneNumber = $order['phone'];
        // Clean phone number
        $phoneNumber = preg_replace('/[^+\\d]/', '', $phoneNumber);
        // Check if phone number ist valid
        if (preg_match('/^\\+?\\d{2,}$/', $phoneNumber, $matches) === 0) {
            // If phone number is invalid, use default value to prevent errors
            $phoneNumber = '-';
        }
        $communication['phone'] = mb_substr($phoneNumber, 0, self::MAX_PHONE_NUMBER_LENGTH);

        $streetNumber = trim($order['streetnumber']);

        $additionalAddressLine = implode(', ', array_filter(
            [
                $order['additionaladdressline'],
                mb_substr($person['coForFirstname'], 0, 30, 'utf-8'),
            ]
        ));

        $streetName = trim($order['street']);
        if (empty($streetNumber)) {
            $streetNumber = '.';
        }

        // If the house number is too long, append its first characters to the street name so that no information gets lost
        if (mb_strlen($streetNumber) > self::MAX_STREET_NUMBER_LENGTH) {
            $numExcessCharacters = mb_strlen($streetNumber) - self::MAX_STREET_NUMBER_LENGTH;

            // Shorten the street name if necessary
            $streetName = mb_substr($streetName, 0, max(0, self::MAX_STREET_NAME_LENGTH - $numExcessCharacters), 'utf-8');
            $streetName .= mb_substr($streetNumber, 0, $numExcessCharacters);
            $streetNumber = mb_substr($streetNumber, $numExcessCharacters);
        }

        $zipCode = $order['zipcode'];
        // The new DHL Geschäftskundenversand requires the zip code to be in the format "ddd dd" for shipments to Sweden.
        // Add the expected space if it was left out by the customer.
        if ($order['countryiso'] == 'SE' && preg_match('/^\\d{5}$/', $zipCode)) {
            $zipCode = mb_substr($zipCode, 0, 3) . ' ' . mb_substr($zipCode, 3, 2);
        }

        // The new DHL Geschäftskundenversand requires the zip code to have a space for shipments to Great Britain. Add
        // the expected space if it was left out by the customer. The space is positioned so that there are always three
        // characters after it (e.g. "BH1 1NU", "B7 4BL",...).
        if ($order['countryiso'] == 'GB') {
            $zipCode = preg_replace('/\\s/', '', $zipCode); // Ignore any spaces that may be given by the customer, the expected space is added below
            if (mb_strlen($zipCode, 'utf8') >= 5) {
                $zipCode = mb_substr($zipCode, 0, mb_strlen($zipCode, 'utf8') - 3) . ' ' . mb_substr($zipCode, -3);
            }
        }

        $names[] = $additionalAddressLine;

        $names = array_values(array_filter($names));
        if (count($names) > 3) {
            // Merge company name and department into one field if there is more information than can be displayed on the label
            $names[0] = mb_substr(implode(', ', [$names[0], $names[1]]), 0, self::MAX_ADDRESS_NAME_LENGTH, 'utf-8');
            unset($names[1]);
            $names = array_values($names);
        }
        if (count($names) > 0) {
            $name1 = $names[0];
        }
        if (count($names) > 1) {
            $name2 = $names[1];
        }
        if (count($names) > 2) {
            $name3 = $names[2];
        }

        // Address
        $address = [
            'name2' => $name2,
            'name3' => $name3,
            'streetName' => mb_substr($streetName, 0, self::MAX_STREET_NAME_LENGTH, 'utf-8'),
            'streetNumber' => $streetNumber,
            'zip' => $zipCode,
            'city' => mb_substr($order['city'], 0, self::MAX_CITY_LENGTH, 'utf-8'),
            'Origin' => [
                'countryISOCode' => $order['countryiso'],
                'state' => $order['stateshortcode'],
            ],
        ];

        // Combine parts
        $receiver = [
            'name1' => $name1,
            'Communication' => $communication,
        ];

        if (!empty($packingStationData['packstationData'])) {
            $receiver['Packstation'] = $packingStationData['packstationData'];
        } elseif (!empty($packingStationData['postfilialeData'])) {
            $receiver['Postfiliale'] = $packingStationData['postfilialeData'];
        } else {
            $receiver['Address'] = $address;
        }

        return $receiver;
    }

    /**
     * @param SoapRequestBodyMutator $soapBodyRequestMutator
     */
    public function addServiceOption(SoapRequestBodyMutator $soapBodyRequestMutator)
    {
        $this->soapBodyRequestMutators[] = $soapBodyRequestMutator;
    }

    /**
     * Checks the address for DHL Packing station Plugin data and return packstation or postbranch delivery data.
     *
     * @param $order
     * @param $orderId
     * @return array An array that contains the current order and packstation or postbranch data or none of the two.
     * @throws \Exception
     */
    private function getPackingStationPluginData($order, $orderId)
    {
        /** @var Shopware\Models\Attribute\Order $orderAttribute */
        $orderAttribute = Util::instance()->getOrderAttributeIfPackingStationPluginExist($orderId);

        $data = [];

        if (!$orderAttribute && $order['street'] !== 'Packstation' && $order['street'] !== 'Postfiliale') {
            return [
                'order' => $order,
            ];
        }

        if ($orderAttribute && $orderAttribute->getMoptwunschpaketaddresstype()) {
            $addressType = $orderAttribute->getMoptwunschpaketaddresstype();

            // Check if the packing station plugin from MOB is installed and is not address
            $supportedMOBTypes = [
                self::MOB_POST_BRANCH,
                self::MOB_PACKING_STATION,
            ];

            if (in_array($addressType, $supportedMOBTypes)) {
                // Check additional address line and company fields for the DHL customer number
                $customerNumberRegExp = '/^\\d{6,10}$/';
                if (preg_match($customerNumberRegExp, $order['additionaladdressline'])) {
                    $customerNumber = $order['additionaladdressline'];
                    // Clear the additional address line so that it is not passed in another field as well
                    $order['additionaladdressline'] = '';
                } else {
                    throw new \Exception('Weder das Adresszusatzfeld noch die Firma enthält eine gültige DHL Kundennummer. Eine solche wird bei Packstations- sowie Postfilialadressen benötigt.');
                }

                switch ($addressType) {
                    case self::MOB_PACKING_STATION:
                        $data['packstationData'] = [
                            'postNumber' => $customerNumber,
                            'packstationNumber' => $order['streetnumber'],
                            'zip' => $order['zipcode'],
                            'city' => $order['city'],
                        ];
                        break;
                    case self::MOB_POST_BRANCH:
                        $data['postfilialeData'] = [
                            'postNumber' => $customerNumber,
                            'postfilialNumber' => $order['streetnumber'],
                            'zip' => $order['zipcode'],
                            'city' => $order['city'],
                        ];
                        break;
                }
            }
        } elseif ($order['street'] === 'Packstation' || $order['street'] === 'Postfiliale') {
            /** @deprecated Old Pakckstation plugin support will be removed in some feature releases */
            // Check for special addresses in street field (Packstation/Postfiliale)
            // Check additional address line and company fields for the DHL customer number
            $customerNumberRegExp = '/^\\d{6,10}$/';
            if (preg_match($customerNumberRegExp, $order['additionaladdressline'])) {
                $customerNumber = $order['additionaladdressline'];
                // Clear the additional address line so that it is not passed in another field as well
                $order['additionaladdressline'] = '';
            } elseif (preg_match($customerNumberRegExp, $order['company'])) {
                $customerNumber = $order['company'];
                // Clear the company so that the customer number it is not passed in another field as well
                $order['company'] = '';
            } else {
                throw new \Exception('Weder das Adresszusatzfeld noch die Firma enthält eine gültige DHL Kundennummer. Eine solche wird bei Packstations- sowie Postfilialadressen benötigt.');
            }

            switch ($order['street']) {
                case 'Packstation':
                    $data['packstationData'] = [
                        'postNumber' => $customerNumber,
                        'packstationNumber' => $order['streetnumber'],
                        'zip' => $order['zipcode'],
                        'city' => $order['city'],
                    ];
                    break;
                case 'Postfiliale':
                    $data['postfilialeData'] = [
                        'postNumber' => $customerNumber,
                        'postfilialNumber' => $order['streetnumber'],
                        'zip' => $order['zipcode'],
                        'city' => $order['city'],
                    ];
                    break;
            }
        }

        return [
            'order' => $order,
            'packStationData' => $data,
        ];
    }

    /**
     * Shortens the complete name of a person by respecting an overall limit
     * of 30 characters. That is, if all name parts joined exceed that limit,
     * these parts will be deleted step-by-step.
     *
     * @param array $name An array containing all name parts to shorten to at least 30 characters.
     * @return array An array whose name parts will not exceed a length of 30.
     */
    private function shortenPersonNames($name)
    {
        $result = [
            'name' => $name,
            'coForFirstname' => null,
        ];
        if (mb_strlen(implode(' ', $name), 'UTF-8') <= self::MAX_FIRST_NAME_LENGTH) {
            return $result;
        } elseif (mb_strlen($name['firstname'] . ' ' . $name['lastname'], 'UTF-8') <= self::MAX_FIRST_NAME_LENGTH) {
            $result['name']['salutation'] = '';

            return $result;
        } else {
            // Use c/o field for first name
            $result['name']['salutation'] = '';
            $result['name']['firstname'] = '';
            $result['name']['lastname'] = mb_substr($name['lastname'], 0, self::MAX_LAST_NAME_LENGTH, 'utf-8');
            $result['coForFirstname'] = mb_substr($name['firstname'], 0, self::MAX_FIRST_NAME_LENGTH, 'utf-8');

            return $result;
        }
    }

    /**
     * This Method is called inside 'OrderController::getViewParams()'
     *
     * @return array Contains all dimension constraints defined in the class
     */
    public static function getDimensionConstraints()
    {
        return [
            'firstName' => self::MAX_FIRST_NAME_LENGTH,
            'lastName' => self::MAX_LAST_NAME_LENGTH,
            'companyName' => self::MAX_ADDRESS_NAME_LENGTH,
            'companyDepartment' => self::MAX_ADDRESS_NAME_LENGTH,
            'streetName' => self::MAX_STREET_NAME_LENGTH,
            'streetNumber' => self::MAX_STREET_NUMBER_LENGTH,
            'additionalAddressLine' => self::MAX_ADDITIONAL_ADDRESS_LINE_LENGTH,
            'zip' => self::MAX_ZIP_LENGTH,
            'city' => self::MAX_CITY_LENGTH,
            'phoneNumber' => self::MAX_PHONE_NUMBER_LENGTH,
            'email' => self::MAX_EMAIL_LENGTH,
            'weight' => self::MAX_WEIGHT_LENGTH,
            'length' => self::MAX_LENGTH_LENGTH,
            'width' => self::MAX_WIDTH_LENGTH,
            'height' => self::MAX_HEIGHT_LENGTH,
            'preferredNeighbour' => PreferredDeliveryServiceOption::MAX_PREFERRED_NEIGHBOUR_LENGTH,
            'preferredLocation' => PreferredDeliveryServiceOption::MAX_PREFERRED_LOCATION_LENGTH,
        ];
    }
}
