<?php

namespace NFePHP\Common;

/**
 * Class to create and validate the identification keys of electronic documents
 * from SPED
 * NT 2018.001 Emitente com CPF e IE
 * @category   NFePHP
 * @package    NFePHP\Common\Keys
 * @copyright  Copyright (c) 2008-2018
 * @license    http://www.gnu.org/licenses/lesser.html LGPL v3
 * @author     Roberto L. Machado <linux dot rlm at gmail dot com>
 * @link       http://github.com/nfephp-org/sped-common for the canonical source repository
 */

class Keys
{
    public const ACCESS_KEY_PATTERN = '/^[0-9]{6}[A-Z0-9]{12}[0-9]{26}$/';

    /**
     * Build 44 digits keys to NFe, NFCe, CTe and MDFe
     * @param string $cUF UF number
     * @param string $ano year
     * @param string $mes month
     * @param string $cnpj or CPF
     * @param string $mod model of document 55, 65, 57 etc
     * @param string $serie
     * @param string $numero document number
     * @param string $tpEmis emission type
     * @param string $codigo random number or document number
     * @return string
     */
    public static function build(
        $cUF,
        $ano,
        $mes,
        $cnpj,
        $mod,
        $serie,
        $numero,
        $tpEmis,
        $codigo = null
    ) {
        if (empty($codigo)) {
            $codigo = self::random();
        }
        //if a cpf with 11 digits is passed then complete with leading zeros
        //up to the 14 digits
        if (strlen($cnpj) < 14) {
            $cnpj = str_pad($cnpj, 14, '0', STR_PAD_LEFT);
        }
        $format = "%02d%02d%02d%s%02d%03d%09d%01d%08d";
        $key = sprintf(
            $format,
            $cUF,
            $ano,
            $mes,
            $cnpj,
            $mod,
            $serie,
            $numero,
            $tpEmis,
            $codigo
        );
        return $key . self::verifyingDigit($key);
    }

    /**
     * Verifies that the key provided is valid
     * @param string $key
     * @return boolean
     */
    public static function isValid($key)
    {
        if (strlen($key) != 44 || !preg_match(self::ACCESS_KEY_PATTERN, $key)) {
            return false;
        }
        $cDV = substr($key, -1);
        $calcDV = self::verifyingDigit(substr($key, 0, 43));
        if ($cDV === $calcDV) {
            return true;
        }
        return false;
    }

    /**
     * This method calculates verifying digit
     * @param string $accessKeyWithoutVerificationDigit
     * @return string
     */
    public static function verifyingDigit($accessKeyWithoutVerificationDigit)
    {
        if (strlen($accessKeyWithoutVerificationDigit) != 43) {
            return '';
        }

        $multipliers = [
            4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4,
            3, 2, 9, 8, 7, 6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2,
        ];

        $baseValue = 48;
        $accessKeyLengthWithoutVerificationDigit = 43;

        $sum = 0;

        for ($i = 0; $i < $accessKeyLengthWithoutVerificationDigit; $i++) {
            $asciiDigit = ord($accessKeyWithoutVerificationDigit[$i]) - $baseValue;

            $sum += $asciiDigit * $multipliers[$i];
        }

        $digit = ($sum % 11) < 2
            ? 0
            : 11 - ($sum % 11);

        return (string) $digit;
    }

    /**
     * Generate and return a 8 digits random number
     * for cNF tag
     * @param string|null $nnf
     * @return string
     */
    public static function random($nnf = null)
    {
        $loop = true;
        while ($loop) {
            $cnf = str_pad(strval(mt_rand(0, 99999999)), 8, '0', STR_PAD_LEFT);
            $loop = !self::cNFIsValid($cnf);
            if (!empty($nnf)) {
                if (intval($cnf) === intval($nnf)) {
                    $loop = true;
                }
            }
        }
        return $cnf;
    }

    /**
     * Verify if cNF number is valid NT2019.001
     * @param string $cnf
     */
    public static function cNFIsValid($cnf)
    {
        $defs = [
            '00000000', '11111111', '22222222', '33333333', '44444444',
            '55555555', '66666666', '77777777', '88888888', '99999999',
            '12345678', '23456789', '34567890', '45678901', '56789012',
            '67890123', '78901234', '89012345', '90123456', '01234567'
        ];
        return !in_array($cnf, $defs);
    }

    /**
     * Return access key from string
     * @param string $string
     * @return string
     */
    public static function extractAccessKey($string)
    {
        $regex = '/[0-9]{6}[A-Z0-9]{12}[0-9]{26}/';

        if (preg_match($regex, $string, $matches)) {
            $extractedValue = $matches[0];

            return $extractedValue;
        }

        return '';
    }

    /**
     * Return elements of key
     * @param string $key
     * @return array
     */
    public static function decompile($key)
    {
        if (strlen($key) != 44) {
            return [];
        }
        return [
            'cuf'       => substr($key, 0, 2),
            'ano'       => substr($key, 2, 2),
            'mes'       => substr($key, 4, 2),
            'cnpj'      => substr($key, 6, 14),
            'modelo'    => substr($key, 20, 2),
            'serie'     => substr($key, 22, 3),
            'nnf'       => substr($key, 25, 9),
            'tpemis'    => substr($key, 34, 1),
            'cnf'       => substr($key, 35, 8),
            'dv'        => substr($key, -1)
        ];
    }
}
