<?php

namespace xmvc;

use stdClass;

/**
 * 编码器类, 编码,加密,解密 , 压缩, 解压，63进制
 */
class Encoder
{
    protected static $code37Chars = '0123456789abcdefghijklmnopqrstuvwxyz_';
    protected static $code63Chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_';


    /**
     * 密码 BCRYPT 算法, 60 字符长度
     *
     * 验证密码使用 password_verify('123456', $hash) 可防时序攻击
     */
    public static function password(string $string): string
    {
        return password_hash($string, PASSWORD_BCRYPT);
    }

    /**替换非ascii码为 unicode 编码 */
    public static function asciiToUnicode(string $string): string
    {
        return preg_replace_callback('/[^\x00-\x7F]/u', function ($matches) {
            return '\\u' . str_pad(dechex(mb_ord($matches[0])), 4, '0', STR_PAD_LEFT);
        }, $string);
    }

    /** 将 unicode 编码转换回非 ASCII 字符 */
    public static function unicodeToAscii(string $string): string
    {
        return preg_replace_callback('/\\\\u([0-9a-fA-F]{4})/', function ($matches) {
            return mb_chr(hexdec($matches[1]));
        }, $string);
    }

    /**
     * 可用于网址安全的 base64编码 from 字符串 String
     *
     * @param string|null $string
     * @return string safe base64
     */
    public static function encode(string $string): string
    {
        return 'base64:' . rtrim(strtr(base64_encode($string), '+/', '-_'), '=');
    }

    /**
     * 可用于网址安全的 base64解码 from 字符串 String
     *
     * @param string $base64Str
     * @return string
     */
    public static function decode($base64Str): string
    {
        $base64Str = substr($base64Str, strpos($base64Str, ':') ?: 0);
        return base64_decode(str_pad(strtr($base64Str, '-_', '+/'), strlen($base64Str) % 4, '=', STR_PAD_RIGHT));
    }

    /**
     * 可用于网址安全的 base64编码 For 数组 Array
     *
     * @param array $var
     * @return string safe base64
     */
    public static function encodeForArray(array $var): string
    {
        if (is_object($var)) {
            $var = get_object_vars($var);
        }
        return 'base64array:' . rtrim(strtr(base64_encode(json_encode($var)), '+/', '-_'), '=');
    }

    /**
     * 可用于网址安全的 base64解码 For 数组 Array
     *
     * @param string $base64Str
     * @return array
     */
    public static function decodeForArray(string $base64Str): array
    {
        $base64Str = substr($base64Str, strpos($base64Str, ':') ?: 0);
        $jsonString = base64_decode(str_pad(strtr($base64Str, '-_', '+/'), strlen($base64Str) % 4, '=', STR_PAD_RIGHT));
        return $jsonString ? json_decode($jsonString, true) : [];
    }


    /**
     * 可用于网址安全的 base64编码 For 对象 Object
     *
     * @param object $var
     * @return string safe base64
     */
    public static function encodeForObject(object $var): string
    {
        $var = get_object_vars($var);
        return 'base64object:' . rtrim(strtr(base64_encode(json_encode($var)), '+/', '-_'), '=');
    }

    /**
     * 可用于网址安全的 base64解码 For 对象 Object
     *
     * @param string $base64Str
     * @return object
     */
    public static function decodeForObject(string $base64Str): object
    {
        $base64Str = substr($base64Str, strpos($base64Str, ':') ?: 0);
        $jsonString = base64_decode(str_pad(strtr($base64Str, '-_', '+/'), strlen($base64Str) % 4, '=', STR_PAD_RIGHT));
        return $jsonString ? json_decode($jsonString) : new stdClass();
    }

    /**
     * 加密字符串
     *
     * @param string|null $str
     * @return string base64
     */
    public static function encrypt($str): string
    {
        return  !is_null($str) ? self::encode(self::encryptRAW($str)) : '';
    }

    /**
     * 解密字符串
     *
     * @param string $str
     * @return string
     */
    public static function decrypt(string $str): string
    {
        return self::decryptRAW(self::decode($str));
    }

    /**
     * 加密
     *
     * @param string $str
     * @return string|false 返回 RAW 字符
     */
    public static function encryptRAW(string $data): string|false
    {
        $encryptkey = MVC::getEncryptkey();
        return openssl_encrypt($data, 'AES-128-ECB', $encryptkey, OPENSSL_RAW_DATA);
    }

    /**
     * 解密
     *
     * @param string $data 接收 RAW 字符
     * @return string|false
     */
    public static function decryptRAW(string $data): string|false
    {
        $encryptkey = MVC::getEncryptkey();
        return openssl_decrypt($data, 'AES-128-ECB', $encryptkey, OPENSSL_RAW_DATA);
    }

    /** md5的63进制值 */
    public static function md6(string $string): string
    {
        return self::decToCode63(hexdec(md5($string)));
    }

    /**
     * 10进制转自定义63进制
     * - 需要 extension=bcmath 或者 extension_loaded('bcmath')
     * - windows的 fat32文件系统不区分文件名的大小写, 所以 windows实际返回的是37进制
     */
    public static function decToCode63($decimalString): string
    {
        if (empty($decimalString)) {
            return '0';
        }
        if (!extension_loaded('bcmath')) {
            if (IS_WIN) {
                ini_set('extension', 'php_bcmath.dll');
            } else {
                ini_set('extension', 'bcmath.so');
            }
        }
        $chars = (!IS_WIN) ? self::$code63Chars : self::$code37Chars;
        $charsLen = strlen($chars);
        $result = '';
        $decimal = bcmul(self::scientificToDecimal($decimalString), '1'); // 将字符串转换为BC Math库的数值
        while (bccomp($decimal, '0') === 1) { // 当十进制数不为0时继续
            $remainder = bcmod($decimal, $charsLen); // 获取余数
            $result = $chars[$remainder] . $result; // 将余数对应的字符添加到结果字符串的开头
            $decimal = bcdiv($decimal, $charsLen, 0); // 除以基数得到新的十进制数
        }
        return $result ?: '0';
    }

    /** 自定义63进制转10进制 */
    public static function code63ToDec(string $code63)
    {
        if (empty($code63)) {
            return 0;
        }
        $chars = (!IS_WIN) ? self::$code63Chars : self::$code37Chars;
        $charsLen = strlen($chars);
        $code63Len = strlen($code63);
        $decimal = 0;
        for ($i = 0; $i < $code63Len; $i++) {
            $idx = strpos($chars, $code63[$i]);
            $decimal += $idx * pow($charsLen, $code63Len - $i - 1);
        }
        return $decimal;
    }

    /** 将科学计数法表示的数字转换为 BC Math 可以处理的数字字符串 */
    public static function scientificToDecimal($scientificNotation)
    {
        // 移除 "E" 或 "e" 以及后面的指数部分
        if (preg_match('/^([+-]?[0-9.]+)([eE]([+-]?[0-9]+))?$/', $scientificNotation, $matches)) {
            $mantissa = $matches[1]; // 尾数部分
            $exponent = isset($matches[3]) ? intval($matches[3]) : 0; // 指数部分

            // 计算10的指数次方，并乘以尾数
            $decimal = bcmul($mantissa, bcpow('10', $exponent));
            return $decimal;
        } else {
            throw new \InvalidArgumentException("Invalid scientific notation format");
        }
    }
}
