<?php declare(strict_types=1);

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

// Holds the QR bill creditor information.
final class QrbCreditorInformation
{
    public const KEY_CRED_INFO    = "CreditorInformation";

    // key constants (public for unit test)
    public const KEY_NAME          = "name";
    public const KEY_ADDR_LINE_1   = "addressLine1";
    public const KEY_ADDR_LINE_2   = "addressLine2";
    public const KEY_COUNTRY       = "country";
    public const KEY_CHECK_QR_IBAN = "checkQrIban";
    public const KEY_IBAN          = "iban";
    public const KEY_BESR_ID       = "besrId";

    private string  $name;
    private string  $addressLine1;
    private string  $addressLine2;
    private string  $country;
    private bool    $qrIban;   
    private string  $iban;
    private ?string $besrId = NULL;
    
    // Validates creditor information from the given associative array.
    // The surrounding key "CreditorInformation" can contain the following fields:
    // creditorInformation["name"]:         taken as is
    // creditorInformation["addressLine1"]: taken as is
    // creditorInformation["addressLine2"]: taken as is
    // creditorInformation["country"]:      taken as is, must be the country code, typically "CH" for Switzerland
    // creditorInformation["checkQrIban"]:  if "true" then "iban" must be a QR-IBAN
    // creditorInformation["iban"]:         position 1..2 must be 'CH' or 'LI', positions 5..9 must be numeric; 
    //                                      if they are in the range 30000..31999 then it is a QR-IBAN
    // creditorInformation["besrId"]:       optional, missing or NULL for PostFinance; 
    //                                      if present, it must be numeric and have 6..8 digits
    public static function validate(array $creditorInformation) : void
    {
        if (! array_key_exists(self::KEY_CRED_INFO, $creditorInformation))
            throw new QrbException(
                static::class . " - surrounding key 'CreditorInformation' of creditor information does not exist",
                "exception.creditorInformation.surroundingKeyDoesNotExist");
        $creditorInformationContents = $creditorInformation[self::KEY_CRED_INFO];
        
        if (! array_key_exists(self::KEY_NAME, $creditorInformationContents))
            throw new QrbException(
                static::class . " - key 'name' for name of creditor information does not exist",
                "exception.creditorInformation.nameDoesNotExist");
        if (! array_key_exists(self::KEY_ADDR_LINE_1, $creditorInformationContents))
            throw new QrbException(
                static::class . " - key 'addressLine1' for adress line 1 of creditor information does not exist",
                "exception.creditorInformation.addressLine1DoesNotExist");
        if (! array_key_exists(self::KEY_ADDR_LINE_2, $creditorInformationContents))
            throw new QrbException(
                static::class . " - key 'addressLine2' for adress line 2 of creditor information does not exist",
                "exception.creditorInformation.addressLine2DoesNotExist");
        if (! array_key_exists(self::KEY_COUNTRY, $creditorInformationContents))
            throw new QrbException(
                static::class . " - key 'country' for country of creditor information does not exist",
                "exception.creditorInformation.countryDoesNotExist");
                
        $checkQrIban = false;
        
        if (array_key_exists(self::KEY_CHECK_QR_IBAN, $creditorInformationContents))    // optional flag check for QR-IBAN
        {
            if ($creditorInformationContents[self::KEY_CHECK_QR_IBAN] == 'true')
            {
                $checkQrIban = true;
            }
        }

        if (! array_key_exists(self::KEY_IBAN, $creditorInformationContents))
            throw new QrbException(
                static::class . " - key 'iban' for IBAN of creditor information does not exist",
                "exception.creditorInformation.ibanDoesNotExist");
    
        $ibanCountry = substr($creditorInformationContents[self::KEY_IBAN], 0, 2);
        if (($ibanCountry != 'CH') && ($ibanCountry != 'LI'))
            throw new QrbException(
                static::class . " - Invalid QR bill IBAN (position 1..2 are not 'CH' or 'LI'), iban = {$creditorInformationContents[self::KEY_IBAN]}",
                "exception.creditorInformation.ibanCountryInvalidForQrBill");
        
        if (! is_numeric(self::extractQrId($creditorInformationContents[self::KEY_IBAN])))
            throw new QrbException(
                static::class . " - Invalid QR bill IBAN (position 5..9 not numeric), iban = {$creditorInformationContents[self::KEY_IBAN]}",
                "exception.creditorInformation.ibanNotNumeric");
        
        if ($checkQrIban && ! self::checkForQrIban($creditorInformationContents[self::KEY_IBAN]))
        {
            throw new QrbException(
                static::class . " - Invalid QR bill IBAN (position 5..9 not in range 30000..31999), iban = {$creditorInformationContents[self::KEY_IBAN]}",
                "exception.creditorInformation.ibanNotQrIban");
        }

        if  (  (array_key_exists(self::KEY_BESR_ID, $creditorInformationContents))    // optional BESR-ID is set
            && ($creditorInformationContents[self::KEY_BESR_ID] != 'NULL') )
        {
            if (! is_numeric($creditorInformationContents[self::KEY_BESR_ID]))
                throw new QrbException(
                    static::class . " - Invalid QR bill BESR-ID (not numeric), besrId = {$creditorInformationContents[self::KEY_BESR_ID]}",
                    "exception.creditorInformation.besrIdNotNumeric");

            $len = strlen($creditorInformationContents[self::KEY_BESR_ID]);                
            if ($len < 6)
                throw new QrbException(
                    static::class . " - Invalid QR bill BESR-ID (less than 6 digits), besrId = '{$creditorInformationContents[self::KEY_BESR_ID]}'",
                    "exception.creditorInformation.besrIdTooShort");
            if ($len > 8)
                throw new QrbException(
                    static::class . " - Invalid QR bill BESR-ID (more than 8 digits), besrId = '{$creditorInformationContents[self::KEY_BESR_ID]}'",
                    "exception.creditorInformation.besrIdTooLong");
        }
    }
    
    // extracts the QR ID (positions 5..9)
    private static function extractQrId(string $iban) : string
    {
        return substr($iban, 4, 5);
    }
    
    // checks that the given IBAN is a QR-IBAN (positions 5..9 are in the range 30000..31999)
    private static function checkForQrIban(string $iban) : bool
    {
        $qrIid = self::extractQrId($iban);
        return ( ((int) $qrIid >= 30000) && ((int) $qrIid <= 31999) ) ? true : false;
    }
    
    // Creates a creditor information instance from the given associative array.
    // The surrounding key "CreditorInformation" can contain the following fields:
    // creditorInformation["name"]:         taken as is
    // creditorInformation["addressLine1"]: taken as is
    // creditorInformation["addressLine2"]: taken as is
    // creditorInformation["country"]:      taken as is but must be the country code, typically "CH" for Switzerland
    // creditorInformation["checkQrIban"]:  if "true" then "iban" must be a QR-IBAN
    // creditorInformation["iban"]:         position 1..2 must be 'CH' or 'LI', positions 5..9 must be numeric; 
    //                                      if they are in the range 30000..31999 then it is a QR-IBAN
    // creditorInformation["besrId"]:       optional, missing or NULL for PostFinance; 
    //                                      if present, it must be numeric and have 6..8 digits
    public function __construct(array $creditorInformation)
    {
        self::validate($creditorInformation);
        
        $creditorInformationContents = $creditorInformation[self::KEY_CRED_INFO];
        $this->name         = $creditorInformationContents[self::KEY_NAME];
        $this->addressLine1 = $creditorInformationContents[self::KEY_ADDR_LINE_1];
        $this->addressLine2 = $creditorInformationContents[self::KEY_ADDR_LINE_2];
        $this->country      = $creditorInformationContents[self::KEY_COUNTRY];
        $this->qrIban       = self::checkForQrIban($creditorInformationContents[self::KEY_IBAN]);
        $this->iban         = $creditorInformationContents[self::KEY_IBAN];

        if  (  (array_key_exists(self::KEY_BESR_ID, $creditorInformationContents))    // optional BESR-ID is set
            && ($creditorInformationContents[self::KEY_BESR_ID] != 'NULL') )
        {
            $this->besrId = $creditorInformationContents[self::KEY_BESR_ID];
        }
    }
    
    // returns the name
    public function getName() : string
    {
        return $this->name;
    }
    
    // returns the address line 1
    public function getAddressLine1() : string
    {
        return $this->addressLine1;
    }
    
    // returns the address line 2
    public function getAddressLine2() : string
    {
        return $this->addressLine2;
    }
    
    // returns the country
    public function getCountry() : string
    {
        return $this->country;
    }
    
    // returns true if IBAN is a QR-IBAN
    public function isQrIban() : bool
    {
        return $this->qrIban;
    }
    
    // returns the IBAN
    public function getIban() : string
    {
        return $this->iban;
    }
    
    // returns the BESR-ID; NULL if there is no BESR-ID (as for PostFinance)
    public function getBesrId() : ?string
    {
        return $this->besrId;
    }
    
    public function __toString()
    {
        return "\n\tname         = '{$this->name}'"
             . "\n\taddressLine1 = '{$this->addressLine1}'"
             . "\n\taddressLine2 = '{$this->addressLine2}'"
             . "\n\tcountry      = '{$this->country}'"
             . "\n\tqrIban       = '{$this->qrIban}'"
             . "\n\tiban         = '{$this->iban}'"
             . "\n\tbesrId       = '{$this->besrId}'";
    }
}

?>
