<?php


namespace Mnv\Core\Utilities;

use Mnv\Core\Config;
use Mnv\Core\Singleton\SingletonTrait;

/**
 * Class Mask
 * @package Mnv\Core\Utilities
 */
class Mask {

    use SingletonTrait;

    const CEP           = "CEP";
    const CPF           = "CPF";
    const CNPJ          = "CNPJ";
    const DATE          = "DATE";
    const RG            = "RG";
    const PHONE         = "PHONE";
    const CELL_PHONE    = "CELL_PHONE";
    const CUSTOM        = "CUSTOM";
    const CREDIT_CARD   = "CREDIT_CARD";

    /**
     * Маски любого поддерживаемого типа
     *
     * @const LENGHT
     */
    private $MASK = [
        "CEP" => "#####-###",
        "CPF" => "###.###.###-##",
        "CNPJ" => "##.###.###/####-##",
        "DATE" => "##/##/####",
        "RG" => "##.###.###-#",
        "PHONE" => "(##) ####-####",
        "CELL_PHONE" => "(##) #####-####",
        "CREDIT_CARD" => "####.****.****.####"
    ];

    /**
     * Определите тип автозаполнения (с нулем слева)
     *
     * @const LENGHT
     */
    const AUTO_COMPLETE = ["CEP", "CPF", "CNPJ", "RG"];

    /**
     * Точный размер каждого типа информации
     *
     * @const LENGHT
     */
    const LENGTH = ['CEP' => 8, 'CPF' => 11, 'CNPJ' => 14, 'RG' => 9, 'PHONE' => 10];

    /**
     * Определите ошибки класса маски
     *
     * @const ERROR
     */
    const ERROR = [
        'NULL' => 'The value entered is null.',
        'INVALID' => 'Not supported type entered.'
    ];

    public function __construct()
    {
        $this->MASK[Mask::CELL_PHONE] = Config::getValue('phone_mask');
    }

    /**
     * Add a phone mask
     *
     * @param string $value
     *
     * @return string
     */
    public function phone(string $value): string
    {
        return $this->transform(Mask::PHONE, $value);
    }

    /**
     * Add a cep mask
     *
     * @param string $value
     *
     * @return string
     */
    public function cep(string $value): string
    {
        return $this->transform(Mask::CEP, $value);
    }

    /**
     * Add a cpf mask
     *
     * @param string $value
     *
     * @return string
     */
    public function cpf(string $value): string
    {
        return $this->transform(Mask::CPF, $value);
    }

    /**
     * Add a cnpj mask
     *
     * @param string $value
     *
     * @return string
     */
    public function cnpj(string $value): string
    {
        return $this->transform(Mask::CNPJ, $value);
    }

    /**
     * Add a rg mask
     *
     * @param string $value
     *
     * @return string
     */
    public function rg(string $value): string
    {
        return $this->transform(Mask::RG, $value);
    }

    /**
     * Add a credit card mask hiding central characters
     *
     * @param string $value
     *
     * @return string
     */
    public function creditCard(string $value): string
    {
        return $this->transform(Mask::CREDIT_CARD, $value);
    }

    /**
     * Add a custom mask.
     * Set the characters you want to replace with #
     *
     * @param string $value
     * @param string $mask
     *
     * @return string
     */
    public function custom(string $value, string $mask): string
    {
        $format = Mask::CUSTOM;
        return $this->transform($format, $value, $mask);
    }

    /**
     * Remove special characters
     *
     * @param string $value
     *
     * @return string
     */
    public function clear(string $value): string
    {
        $value = preg_replace('/[\@\.\;\-\?\/\|\*\_\~\!\$\&\(\)\{\}\:\=\+\^\´\`\"\'\," "]+/', '', $value);

        $value = str_replace('[','', $value);
        return str_replace(']','', $value);
    }

    /**
     * Transfom value with the mask
     *
     * @param string $format
     * @param $value
     * @param $mask = null
     *
     * @return string
     */
    private function transform(string $format, $value, $mask = null): string
    {
        $this->validate($value);
        $mask = $this->getMask($format, $value, $mask);
        $value = $this->clear($value);
        $value = $this->autocomplete($format, $value);
        return $this->do($value, $mask);
    }

    /**
     * Execute replace process
     *
     * @param string $value
     * @param string $mask
     *
     * @return string
     */
    private function do(string $value, string $mask): string
    {
        $masked = '';
        $k = 0;
        for ($i = 0; $i <= strlen($mask)-1; $i++) {
            if ($mask[$i] == '#') {
                if (isset($value[$k])) {
                    $masked .= $value[$k++];
                }
            } else {
                if (isset($mask[$i])) {
                    $masked .= $mask[$i];
                }
            }
        }
        return $masked;
    }

    /**
     * Auto complete validate
     *
     * @param string $format
     * @param string $value
     *
     * @return string
     */
    private function autocomplete(string $format, string $value): string
    {
        if (in_array($format, Mask::AUTO_COMPLETE)) {
            $length = Mask::LENGTH[$format];
            $value = $this->addZeros($value, $length);
        }
        return $value;
    }

    /**
     * Add zero to the left
     *
     * @param string $value
     * @param int $length
     *
     * @return string
     */
    private function addZeros(string $value, int $length): string
    {
        return str_pad($value, $length, "0", STR_PAD_LEFT);
    }

    /**
     * Validate value
     *
     * @param $value
     *
     * @return bool
     */
    private function validate($value): bool
    {
        if (is_null($value)) {
            $this->error(Mask::ERROR['NULL']);
        }
        if (is_string($value)) {
            return true;
        }
        if (is_int($value)) {
            return true;
        }
        if (is_double($value)) {
            return true;
        }
        $this->error(Mask::ERROR['INVALID']);
    }

    /**
     * Get the mask format
     *
     * @param string $format
     * @param string $value
     * @param ?string $mask
     *
     * @return string
     */
    private function getMask(string $format, string $value, ?string $mask): string
    {
        if (!is_null($mask)) {
            return $mask;
        }


        if ($format == Mask::PHONE) {
            if (strlen($value) > Mask::LENGTH[Mask::PHONE]) {
                $format = Mask::CELL_PHONE;
            }
        }
        $mask = $this->MASK[$format];
        return $mask;
    }

    /**
     * Error method
     *
     * @param string $message
     */
    private function error(string $message)
    {
        throw new \Exception('Goldbach Algorithms: '.$message);
    }
}
