<?php declare(strict_types=1);

require_once __DIR__ . '/../common/QrbException.php';

// Holds the QR bill recipients.
final class QrbRecipient
{
    // key constants (public for unit tests)
    public const HDR_GREETING     = "Greeting";
    public const HDR_COMPANY      = "Company";
    public const HDR_SALUTATION   = "Salutation";
    public const HDR_FIRST_NAME   = "FirstName";
    public const HDR_LAST_NAME    = "LastName";
    public const HDR_STREET       = "Street";
    public const HDR_BUILDING_NO  = "BuildingNo";
    public const HDR_POSTAL_CODE  = "PostalCode";
    public const HDR_CITY         = "City";
    public const HDR_COUNTRY      = "Country";
    public const HDR_LANGUAGE     = "Language";
    public const HDR_MAIL_ADDRESS = "MailAddress";
    public const HDR_REFERENCE    = "Reference";
    public const HDR_AMOUNT       = "Amount";
    
    private int     $index;
    // for PDF
    private string  $greeting     = '';    // greeting like "Lieber Herr Muster", optional        
    private string  $company      = '';    // optional        
    private string  $salutation   = '';    // salutation like "Herr", optional
    // for PDF or paymement slip if addDebtor
    private string  $firstName    = '';
    private string  $lastName     = '';
    private string  $street       = '';
    private string  $buildingNo   = '';
    private string  $postalCode   = '';
    private string  $city         = '';
    private string  $country      = '';
    // MANDATORY
    private string  $language     = '';
    // for mail
    private string  $mailAddress  = '';
    // for payment slip
    private ?string $reference    = NULL;    // NULL if no QR-IBAN
    private ?float  $amount       = NULL;    // optional if global amount (or no amount at all)

    // Parses the header of the recipients for the known header names.
    // Returns an associative array with pairs of a known header name and its index.
    public static function parseHeader(array $header, 
                                       bool $amountMandatory,    bool $createPdf, 
                                       bool $referenceMandatory, bool $sendMail
                                       ) : array
    {
        $headerNames = [];
        $index = 0;
        
        // collect all well known header names
        foreach ($header as $name)
        {
            if     ( self::mbEquals($name, self::HDR_GREETING) )     $headerNames[self::HDR_GREETING] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_COMPANY) )      $headerNames[self::HDR_COMPANY] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_SALUTATION) )   $headerNames[self::HDR_SALUTATION] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_FIRST_NAME) )   $headerNames[self::HDR_FIRST_NAME] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_LAST_NAME) )    $headerNames[self::HDR_LAST_NAME] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_STREET) )       $headerNames[self::HDR_STREET] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_BUILDING_NO) )	 $headerNames[self::HDR_BUILDING_NO] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_POSTAL_CODE) )  $headerNames[self::HDR_POSTAL_CODE] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_CITY) )         $headerNames[self::HDR_CITY] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_COUNTRY) )      $headerNames[self::HDR_COUNTRY] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_LANGUAGE) )     $headerNames[self::HDR_LANGUAGE] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_MAIL_ADDRESS) ) $headerNames[self::HDR_MAIL_ADDRESS] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_REFERENCE) )    $headerNames[self::HDR_REFERENCE] = $index++;
            else
            if     ( self::mbEquals($name, self::HDR_AMOUNT) )       $headerNames[self::HDR_AMOUNT] = $index++;
            else                                                     $index++;    // increase index!
        }
        
        // check mandatory columns
        // greeting is optional
        if ($createPdf)
        {
            // company and salutation are optional
            $key = self::HDR_FIRST_NAME;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
            $key = self::HDR_LAST_NAME;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
            $key = self::HDR_STREET;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
            $key = self::HDR_BUILDING_NO;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
            $key = self::HDR_POSTAL_CODE;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
            $key = self::HDR_CITY;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
            $key = self::HDR_COUNTRY;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
        }
        
        $key = self::HDR_LANGUAGE;
        if (!array_key_exists($key, $headerNames))
            throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                   "exception.recipient.headerDoesNotExist", $key);
        
        if ($sendMail)    // don't check e-mail address if no mails are sent
        {
            $key = self::HDR_MAIL_ADDRESS;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
        }
        
        if ($createPdf && $referenceMandatory)
        {
            $key = self::HDR_REFERENCE;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
        }
        
        if ($createPdf && $amountMandatory)
        {
            $key = self::HDR_AMOUNT;
            if (! array_key_exists($key, $headerNames))
                throw new QrbException(static::class . " - Missing header column ' . $key . '",
                                       "exception.recipient.headerDoesNotExist", $key);
        }
        
        return $headerNames;
    }
    
    // Compares two multi-byte strings.
    // Returns TRUE if they are equal. 
    private static function mbEquals($string1, $string2) : bool
    {
        if ($string1 == $string2)    return true;    // case sensitive equal
    
        $string1Normalized = Normalizer::normalize($string1, Normalizer::FORM_KC);
        $string2Normalized = Normalizer::normalize($string2, Normalizer::FORM_KC);
        return mb_strtolower($string1Normalized) === mb_strtolower($string2Normalized)
            || mb_strtoupper($string1Normalized) === mb_strtoupper($string2Normalized);
    }
    
    // Validates a recipient from the given array.
    public static function validate(int $recipientIndex, array $recipient, array $headerNames, 
                                    bool $amountMandatory, bool $createPdf, bool $referenceMandatory,
                                    bool $sendMail, array $supportedLanguages
                                    ) : void
    {
        $countR = count($recipient);
        $countH = count($headerNames);
        if ($countR < $countH)
            throw new QrbException(
                static::class . " - Not enough elements ({$countR} instead of {$countH}) in recipient #{$recipientIndex}",
                "exception.recipient.notEnoughElements",
                "{$recipientIndex}, {$countR}/{$countH}");

        // greeting is are optional
        
        if ($createPdf)
        {
            // company and salutation are optional
            if (! array_key_exists($headerNames[self::HDR_FIRST_NAME], $recipient))
                throw new QrbException(
                    static::class . " - Missing column 'FirstName' in recipient #{$recipientIndex}",
                    "exception.recipient.firstNameDoesNotExist",
                    "{$recipientIndex}, FirstName");
            if (! array_key_exists($headerNames[self::HDR_LAST_NAME], $recipient))
                throw new QrbException(
                    static::class . " - Missing column 'LastName' in recipient #{$recipientIndex}",
                    "exception.recipient.lastNameDoesNotExist",
                    "{$recipientIndex}, LastName");
            if (! array_key_exists($headerNames[self::HDR_STREET], $recipient))
                throw new QrbException(
                    static::class . " - Missing column 'Street' in recipient #{$recipientIndex}",
                    "exception.recipient.streetDoesNotExist",
                    "{$recipientIndex}, Street");
            if (! array_key_exists($headerNames[self::HDR_BUILDING_NO], $recipient))
                throw new QrbException(
                    static::class . " - Missing column 'BuildingNo' in recipient #{$recipientIndex}",
                    "exception.recipient.buildingNoDoesNotExist",
                    "{$recipientIndex}, BuildingNo");
            if (! array_key_exists($headerNames[self::HDR_POSTAL_CODE], $recipient))
                throw new QrbException(
                    static::class . " - Missing column 'PostalCode' in recipient #{$recipientIndex}",
                    "exception.recipient.postalCodeDoesNotExist",
                    "{$recipientIndex}, PostalCode");
            if (! array_key_exists($headerNames[self::HDR_CITY], $recipient))
                throw new QrbException(
                    static::class . " - Missing column 'City' in recipient #{$recipientIndex}",
                    "exception.recipient.cityDoesNotExist",
                    "{$recipientIndex}, City");
            if (! array_key_exists($headerNames[self::HDR_COUNTRY], $recipient))
                throw new QrbException(
                    static::class . " - Missing column 'Country' in recipient #{$recipientIndex}",
                    "exception.recipient.countryDoesNotExist",
                    "{$recipientIndex}, Country");
        }
    
        if (! array_key_exists($headerNames[self::HDR_LANGUAGE], $recipient))    // MANDATORY!
            throw new QrbException(
                static::class . " - Missing column 'Language' in recipient #{$recipientIndex}",
                "exception.recipient.languageDoesNotExist",
                "{$recipientIndex}, Language");

        if ($sendMail)    // don't check e-mail address if no mails are sent
        {
            if (! array_key_exists($headerNames[self::HDR_MAIL_ADDRESS], $recipient))
                throw new QrbException(
                    static::class . " - Missing column 'MailAddress' in recipient #{$recipientIndex}",
                    "exception.recipient.mailAddressDoesNotExist",
                    "{$recipientIndex}, MailAddress");
            if (filter_var($recipient[$headerNames[self::HDR_MAIL_ADDRESS]], FILTER_VALIDATE_EMAIL) === false)
                throw new QrbException(
                    static::class . " - Invalid mailAddress '{$recipient[$headerNames[self::HDR_MAIL_ADDRESS]]}' in recipient #{$recipientIndex}",
                    "exception.recipient.mailAddressInvalid",
                    "{$recipientIndex}, MailAddress, {$recipient[$headerNames[self::HDR_MAIL_ADDRESS]]}");
        }

        if ($createPdf)
        {
            if ($referenceMandatory)
                if (! array_key_exists($headerNames[self::HDR_REFERENCE], $recipient))
                    throw new QrbException(
                        static::class . " - Mandatory QR bill reference is missing in recipient #{$recipientIndex}",
                        "exception.recipient.referenceDoesNotExist",
                        "{$recipientIndex}, Reference");
            
            if (  array_key_exists(self::HDR_REFERENCE, $headerNames)
               && ($recipient[$headerNames[self::HDR_REFERENCE]] != '')
               && (! is_numeric($recipient[$headerNames[self::HDR_REFERENCE]])) )
                throw new QrbException(
                    static::class . " - Invalid reference (not numeric) in recipient #{$recipientIndex}, reference = {$recipient[$headerNames[self::HDR_REFERENCE]]}",
                    "exception.recipient.referenceNotNumeric",
                    "{$recipientIndex}, Reference, {$recipient[$headerNames[self::HDR_REFERENCE]]}");
        }
        
        if ($createPdf && $amountMandatory)
        {
            if (! array_key_exists($headerNames[self::HDR_AMOUNT], $recipient))
                throw new QrbException(
                    static::class . " - Mandatory QR bill amount is missing in recipient #{$recipientIndex}",
                    "exception.recipient.amountDoesNotExist",
                    "{$recipientIndex}, Amount");
        }

        // amount may be present although it is not mandatory
        if  (  array_key_exists(self::HDR_AMOUNT, $headerNames) 
            && array_key_exists($headerNames[self::HDR_AMOUNT], $recipient) )
        {
            if (! is_numeric($recipient[$headerNames[self::HDR_AMOUNT]]))
                throw new QrbException(
                    static::class . " - Invalid QR bill amount (not numeric) in recipient #{$recipientIndex}, amount = '{$recipient[$headerNames[self::HDR_AMOUNT]]}'",
                    "exception.recipient.amountNotNumeric",
                    "{$recipientIndex}, Amount, {$recipient[$headerNames[self::HDR_AMOUNT]]}");
            if (floatval($recipient[$headerNames[self::HDR_AMOUNT]]) < 0)
                throw new QrbException(
                    static::class . " - Invalid QR bill amount (< 0) in recipient #{$recipientIndex}, amount = '{$recipient[$headerNames[self::HDR_AMOUNT]]}'",
                    "exception.recipient.amountLessThanZero",
                    "{$recipientIndex}, Amount, {$recipient[$headerNames[self::HDR_AMOUNT]]}");
        }
    }

    // Creates a recipient instance from the given array.
    public function __construct(int $recipientIndex, array $recipient, array $headerNames, 
                                bool $amountMandatory, bool $createPdf, bool $printPaymentSlip,
                                bool $sendMail, array $supportedLanguages)
    {
        self::validate($recipientIndex, $recipient, $headerNames, 
                       $amountMandatory, $createPdf, $printPaymentSlip,
                       $sendMail, $supportedLanguages);
        
        $this->index       = $recipientIndex;
        
        // greeting is optional
        if (  (array_key_exists(self::HDR_GREETING, $headerNames))
           && (array_key_exists($headerNames[self::HDR_GREETING], $recipient)))
            $this->greeting   = $recipient[$headerNames[self::HDR_GREETING]];
            
        if ($createPdf)
        {
            // company and salutation are optional
            if (  (array_key_exists(self::HDR_COMPANY, $headerNames))
               && (array_key_exists($headerNames[self::HDR_COMPANY], $recipient)))
                $this->company    = $recipient[$headerNames[self::HDR_COMPANY]];
            if (  (array_key_exists(self::HDR_SALUTATION, $headerNames))
               && (array_key_exists($headerNames[self::HDR_SALUTATION], $recipient)))
                $this->salutation = $recipient[$headerNames[self::HDR_SALUTATION]];
            $this->firstName   = $recipient[$headerNames[self::HDR_FIRST_NAME]];
            $this->lastName    = $recipient[$headerNames[self::HDR_LAST_NAME]];
            $this->street      = $recipient[$headerNames[self::HDR_STREET]];
            $this->buildingNo  = $recipient[$headerNames[self::HDR_BUILDING_NO]];
            $this->postalCode  = $recipient[$headerNames[self::HDR_POSTAL_CODE]];
            $this->city        = $recipient[$headerNames[self::HDR_CITY]];
            $this->country     = $recipient[$headerNames[self::HDR_COUNTRY]];
        }
        
        $this->language = $recipient[$headerNames[self::HDR_LANGUAGE]];    // MANDATORY
        
        if ($sendMail)
        {
            $this->mailAddress = $recipient[$headerNames[self::HDR_MAIL_ADDRESS]];
        }
        
        if ($printPaymentSlip)
        {
            if (  array_key_exists($headerNames[self::HDR_REFERENCE], $recipient)
               && ($recipient[$headerNames[self::HDR_REFERENCE]] != '') )
            {
                $this->reference = $recipient[$headerNames[self::HDR_REFERENCE]];
            }
        }

        // amount may be present although it is not mandatory
        if (  (array_key_exists(self::HDR_AMOUNT, $headerNames))
           && (array_key_exists($headerNames[self::HDR_AMOUNT], $recipient)))
            $this->amount = floatval($recipient[$headerNames[self::HDR_AMOUNT]]);
    }
    
    public function getIndex()       :    int       {    return $this->index;       }
    public function getGreeting()    :    string    {    return $this->greeting;    }
    public function getCompany()     :    string    {    return $this->company;     }
    public function getSalutation()  :    string    {    return $this->salutation;  }
    public function getFirstName()   :    string    {    return $this->firstName;   }
    public function getLastName()    :    string    {    return $this->lastName;    }
    public function getStreet()      :    string    {    return $this->street;      }
    public function getBuildingNo()  :    string    {    return $this->buildingNo;  }
    public function getPostalCode()  :    string    {    return $this->postalCode;  }
    public function getCity()        :    string    {    return $this->city;        }
    public function getCountry()     :    string    {    return $this->country;     }
    public function getLanguage()    :    string    {    return $this->language;    }
    public function getMailAddress() :    string    {    return $this->mailAddress;	}
    
    // May return NULL if no QR-IBAN.
    public function getReference()   :    ?string   {    return $this->reference;   }
    
    // Returns NULL if no amount.
    public function getAmount()      :    ?float    {    return $this->amount;      }

    // Sets the given mailAddress. Used for test run.
    public function setMailAddress(string $mailAddress)
    {
        $this->mailAddress = $mailAddress;
    }

    public function __toString()
    {
        return  "index = '{$this->index}'"
            . ", greeting = '{$this->greeting}'"
            . ", company = '{$this->company}'"
            . ", salutation = '{$this->salutation}'"
            . ", firstName = '{$this->firstName}'"
            . ", lastName = '{$this->lastName}'"
            . ", street = '{$this->street}'"
            . ", buildingNo = '{$this->buildingNo}'"
            . ", postalCode = '{$this->postalCode}'"
            . ", city = '{$this->city}'"
            . ", country = '{$this->country}'"
            . ", language = '{$this->language}'"
            . ", mailAddress = '{$this->mailAddress}'"
            . ", reference = '{$this->reference}'"
            . ", amount = '{$this->amount}'"
            ;
    }
}

?>
