<?php
/**
*   Versões demonstrativas contém o CNPJ da Windel
*/
class ChaveAcesso {

    const QTD_MODULOS_ADICIONAIS = 5;
    const DIAS_VALIDADE = 45;
    const DIAS_BLOQUEIO = 60;
    const DIAS_AVISO_BLOQUEIO = 5;
    const CNPJ_DEMO = '04430502000103';


    private $cpfCnpj = '';
    private $quantidadeMaquinas = 0;
    private $ultimaGeracao;
    private $moduloPrincipal = 0;
    private $modulosAdicionais = [];
    public $unnencrypted = '';

    public function gerarChaveAcesso(string $cpfCnpj, int $quantidadeMaquinas, Datetime $ultimaGeracao, int $moduloPrincipal, array $modulosAdicionais)
    {
        $this->cpfCnpj = Util::unmask($cpfCnpj);
        $this->quantidadeMaquinas = $this->strZeroString($quantidadeMaquinas, 4);
        $this->ultimaGeracao = $ultimaGeracao;
        $this->moduloPrincipal = $this->convertproduto($moduloPrincipal);
        $this->modulosAdicionais = [];
        foreach($modulosAdicionais as $key => $adicional) {
            $this->modulosAdicionais[$key] = $this->convertModulo($adicional);
        }

        return $this->getDisplayText();
    }

    private function convertModulo($id) {
        if(empty($id)) {
            return 0;
        }

        switch ($id) {
            case 7:
                return 1;
            case 8:
                return 2;
            case 9:
                return 3;
            case 10:
                return 4;
            case 12:
                return 5;
            case 13:
                return 6;
            case 14:
                return 7;
            case 15:
                return 8;
            
            default:
                return $id;
        }
    }

    private function convertProduto($id)
    {
        if($id == 11) {
            return 7;
        }
        return $id;
    }

    public function digitoVerificador(string $value): int
    {
        $value = $this->apenasNumerosStr($value);

        $result = 0;

        for($i = 0; $i < strlen($value); $i++) {
            if ($i%2 == 0) {
                $result += intval($value[$i]);
            } else {
                $result -= intval($value[$i]);
            }
        }
        return abs($result);
    }

    public function digitoVerificadorValido(string $value): bool
    {
        $value = $this->descriptografa($this->apenasNumerosStr($value));
        $this->unnencrypted = $value;

        $sum = 0;

        for($i = 0; $i < (strlen($value) - 3); $i++) {
            if ($i%2 == 0) {
                $sum += $value[$i];
            } else {
                $sum -= $value[$i];
            }
        }

        return abs($sum) == substr($value, -3);
    }

    public function criptografa(string $value): string
    {
        $result = '';
        while(strlen($value) > 0)
        {
            $result .= substr($value, 0, 1);

            if(strlen($value) > 1) {
                $result .= substr($value, -1);
            }

            $value = substr_replace($value, '', 0, 1);
            $value = substr_replace($value, '', -1, 1);
        }
        
        $iAdicional = 1;
        for($i = 0; $i < strlen($result); $i++) {

            $iAux = $result[$i];
            $iAux += $iAdicional;
            
            if($iAux > 9) {
                $iAux -= 10;
            }

            $result[$i] = str_split($iAux)[0];

            $iAdicional++;

            if($iAdicional > 9) {
                $iAdicional = 1;
            }
        }
        
        return $result;
    }

    public function descriptografa(string $value): string
    {
        $result = '';
        $strAux = '';
        $iAux;
        $iAdicional = 1;
        $value = $this->apenasNumerosStr($value);

        for($i = 0; $i < strlen($value); $i++) {
            $iAux = $value[$i];
            $iAux -= $iAdicional;

            if($iAux < 0) {
                $iAux += 10;
            }

            $value[$i] = str_split($iAux)[0];

            $iAdicional++;

            if($iAdicional > 9) {
                $iAdicional = 1;
            }
        }

        $i = 0;
        $lenght = strlen($value);
        while($i < $lenght) {
            
            if($i%2 == 0) {
                $result .= substr($value, $i, 1);
            } else {
                $strAux .= substr($value, $i, 1);
            }

            $i++;
        }

        $result .= strrev($strAux);

        return $result;
    }

    public function getAsString(): string
    {
        $result = 
        $this->strZeroString($this->cpfCnpj,       14) .
        $this->strZero($this->quantidadeMaquinas,   2) .
        $this->strZero($this->moduloPrincipal,      2) .
        $this->strZero($this->modulosAdicionais[0], 2) .
        $this->strZero($this->modulosAdicionais[1], 2) .
        $this->strZero($this->modulosAdicionais[2], 2) .
        $this->strZero($this->modulosAdicionais[3], 2) .
        $this->strZero($this->modulosAdicionais[4], 2) .
        $this->ultimaGeracao->format('dmY');

        $result .= $this->strZero($this->digitoVerificador($result), 3);
        $this->unnencrypted = $result;
        $result = $this->criptografa($result);

        return $result;
    }

    public function setAsString(string $value): void
    {
        $chave = $this->descriptografa($this->apenasNumerosStr($value));

        if($this->chaveValida($chave))
        {
            $this->CNPJCPF              = substr($chave, 0, 13);
            $this->quantidadeMaquinas   = substr($chave, 14, 2);
            $this->moduloPrincipal      = substr($chave, 16, 2);
            $this->modulosAdicionais[0] = substr($chave, 18, 2);
            $this->modulosAdicionais[1] = substr($chave, 20, 2);
            $this->modulosAdicionais[2] = substr($chave, 22, 2);
            $this->modulosAdicionais[3] = substr($chave, 24, 2);
            $this->modulosAdicionais[4] = substr($chave, 26, 2);

            $day   = substr($chave, 28, 2);
            $month = substr($chave, 30, 2);
            $year  = substr($chave, 32, 4);
            if(checkdate($month, $day, $year)) {
                $this->ultimaGeracao = DateTime::createFromFormat("Y-m-d", date("{$year}-{$month}-{$day}"));
            } else {
                $this->ultimaGeracao = DateTime::createFromFormat("Y-m-d H:i:s", date("Y-m-d H:i:s"));
            }
        } else {
            throw new Exception('A chave de acesso informada é inválida.');
        }
    }

    public function getDisplayText(): string
    {
        return Util::mask($this->getAsString(), '######-######-######-######-######-######-###');
    }

    public function getSegmento(int $index): string
    {
        $strAux = $this->getAsString();

        switch ($index) {
            case 1: return substr($strAux,  0, 6);
            case 2: return substr($strAux,  6, 6);
            case 3: return substr($strAux, 12, 6);
            case 4: return substr($strAux, 18, 6);
            case 5: return substr($strAux, 24, 6);
            case 6: return substr($strAux, 30, 6);
            case 7: return substr($strAux, 36, 3);
        }
    }

    public function chaveValida(string $value): bool
    {
        $value = $this->apenasNumerosStr($value);
        return (strlen($value) == 39) and ($this->digitoVerificadorValido($value));
    }

    public function getModuloAdicional(int $index): int
    {
        return $this->modulosAdicionais[$index];
    }

    public function clear(): void
    {
        $this->cpfCnpj = '';
        $this->quantidadeMaquinas = 1;
        $this->ultimaGeracao  = DateTime::createFromFormat("Y-m-d H:i:s", date("Y-m-d H:i:s"));
        $this->moduloPrincipal    = 0;

        for($i = min($this->modulosAdicionais); $i <= max($this->modulosAdicionais); $i++) {
            $this->modulosAdicionais[$i] = 0;

            if(Assigned(FOnChange())) {
                FOnChange($Self, '');
            }
        }
    }

    public function getIsEmpty(): bool
    {
        return ($this->cpfCnpj == '') and
        ($this->quantidadeMaquinas == 1) and
        ($this->moduloPrincipal == 0) and
        ($this->modulosAdicionais[1] == 0) and
        ($this->modulosAdicionais[2] == 0) and
        ($this->modulosAdicionais[3] == 0) and
        ($this->modulosAdicionais[4] == 0) and
        ($this->modulosAdicionais[5] == 0);
    }

    public function getDiasAteVencimento(): int
    {
        $dias = (DateTime())->diff($this->getDataVencimento());

        return $dias > 0 ? $dias : 0;
    }

    public function getDiasAteBloqueio(): int
    {
        $dias = (DateTime())->diff($this->getDataBloqueio());

        return $dias > 0 ? $dias : 0;
    }

    public function getVencida(): bool
    {
        return $this->getDiasAteVencimento() == 0;
    }

    public function getBloqueada(): bool
    {
        return $this->getDiasAteBloqueio() == 0 and !$this->getVersaoDemonstracao();
    }

    public function getDataVencimento(): DateTime
    {
        return $this->incDay($DataUltimaGeracao, DIAS_VALIDADE);
    }

    public function getDataBloqueio(): DateTime
    {
        return $this->incDay($DataUltimaGeracao, DIAS_BLOQUEIO);
    }

    public function getStatus(): string
    {
        if($this->getIsEmpty())
            $result = StatusChaveAcesso::scaVazia;
        else if($this->getBloqueada())
            $result = StatusChaveAcesso::scaBloqueada;
        else if($this->getBloqueadaEmBreve())
            $result = StatusChaveAcesso::scaBloqueadaEmBreve;
        else if($this->getVencida())
            $result = StatusChaveAcesso::scaVencida;
        else
            $result = StatusChaveAcesso::scaNormal;
    }                                                        

    public function getMensagemStatus(): string
    {
        $result;
        if($this->getVersaoDemonstracao()) {
            $result = 'Versão demonstração';
        }
        else {
            $result = 'Licenciado para ' . Util::mask($this->strZeroString($this->CNPJCPF, 14), Mask::DOCUMENTO);
        }

        switch ($this->getStatus()) {
            case StatusChaveAcesso::scaVazia:
                return 'Sem chave de acesso.';
            case StatusChaveAcesso::scaBloqueada:
                return $result . ' [Chave Bloqueada]';
            case StatusChaveAcesso::scaBloqueadaEmBreve:
                return $result . ' [Chave será bloqueada em ' . $this->intToStr($this->getDiasAteBloqueio()) . ' dia(s)]';
            case StatusChaveAcesso::scaVencida:
                return $result . ' [Chave Vencida]';
            case StatusChaveAcesso::scaNormal:
                return $result . ' [' . $this->intToStr($this->getDiasAteVencimento()) . ' dia(s) restante(s)]';
        }
    }

    public function getBloqueadaEmBreve(): bool
    {
        return $this->getDiasAteBloqueio() <= DIAS_AVISO_BLOQUEIO and !$this->getVersaoDemonstracao();
    }

    public function getVersaoDemonstracao(): bool
    {
        return $this->cpfCnpj == $this->CNPJ_DEMO and $this->moduloPrincipal == 0;
    }

    public function getTipoPessoa(): string
    {
        return (strlen(self::$cpfCnpj) == 11) ? TipoPessoa::tpFisica : TipoPessoa::tpJuridica;
    }

    public function GetPossuiModuloCooperativa(): bool
    {
        $result = in_array((intval(ModuloAdicional::maCooperativa + 1)), [$this->ModuloAdicional1, $this->ModuloAdicional2, $this->ModuloAdicional3, $this->ModuloAdicional4, $this->ModuloAdicional5]);
    }

    public function GetPossuiModuloManufatura(): bool
    {
        $result = in_array((intval(ModuloAdicional::maManufatura + 1)), [$this->ModuloAdicional1, $this->ModuloAdicional2, $this->ModuloAdicional3, $this->ModuloAdicional4, $this->ModuloAdicional5]);
    }

    public function GetPossuiModuloQualidade(): bool
    {
        $result = in_array((intval(ModuloAdicional::maQualidade + 1)), [$this->ModuloAdicional1, $this->ModuloAdicional2, $this->ModuloAdicional3, $this->ModuloAdicional4, $this->ModuloAdicional5]);
    }

    function copy($string, $init, $size): string {
        return substr($string, $init -1, $size);
    }

    function strToInt(string $string): int {
        return intval($string);
    }

    function intToStr(int $int): string {
        return (string) $string;
    }

    function apenasNumerosStr(string $numero): string {
        return Util::unmask($numero);
    }

    function incDay(DateTime $data, int $days): Datetime {
        return $days == 1 ? $date->modify("+{$days} day") : $date->modify("+{$days} days");
    }

    function strZeroString(string $value, $size): string {
        return str_pad($value, $size, "0", STR_PAD_LEFT);
    }
    function strZero(int $value, int $size): string {
        return $this->strZeroString((string)$value, $size);
    }
}

class TipoPessoa
{
    const tpFisica = 'F';
    const tpJuridica = 'J';
}

class StatusChaveAcesso
{
    const scaNormal = "NORM";
    const scaVazia = "VAZ";
    const scaVencida = "VENC";
    const scaBloqueadaEmBreve = "BREV";
    const scaBloqueada = "BLOQ";
}
class ModuloAdicional
{
    const maManufatura = "MAN";
    const maCooperativa = "COOP";
    const maQualidade = "QUAL";
}