<?php

namespace xmvc;

/**
 * 运行时处理类, 包括错误处理类
 */
class ErrorClass
{
    public static $msgFanyiPatternKeys = null; //错误信息翻译正则
    public static $msgFanyiPatternValues = null;
    /**
     * 自定错误信息池
     * - 例 i_last_messge() 取 ErrorClass::$errorCustomPool['i_last_messge']
     */
    public static $errorCustomPool = [];

    /** 加入自定义错误信息池
     * @param string $customKey 自定义键组
     * @param string $message 错误信息
     * @param array $exts 扩展信息，例  ['sql'=>$sql]
     * @param array $filters debug_backtrace() 中要过滤的文件或类方法, 参考 getTraceCaller()
     * @param array $customCaller 自定义调用者 ['file'=>string, 'line'=>int]
     * @param bool $isAddTrace 是否添加 debug_backtrace()
     */
    public static function setErrorCustomPool(string $customKey, string $message, array $exts = null, array $filters = [], array $customCaller = [], bool $isAddTrace = true)
    {
        $filters = array_merge([__FILE__], $filters);
        self::$errorCustomPool[$customKey][] = errorInfo($message, $exts, $filters, $customCaller, $isAddTrace);
    }

    /** 取自定义错误信息池内容 */
    public static function getErrorCustomPool(string $customKey): array|null
    {
        if (isset(self::$errorCustomPool[$customKey])) {
            return self::$errorCustomPool[$customKey];
        } else {
            return null;
        }
    }

    /**错误信息翻译正则 */
    public static function errorReport($type, $error, $function)
    {
        /*
        if (is_null(self::$msgFanyiPatternKeys)) {
            $pattern = [
                '/ in /i' => ' 在 ',
                '/ on line (\d+) /i' => ' 第 $1 行 ',
                '/syntax error/i' => '语法错误',
                '/ unexpected /i' => ' 意外的 ',
                '/Uncaught Error:/i' => '错误:',
                '/Uncaught TypeError:/i' => '类型错误:',
                '/Uncaught ArgumentCountError:/i' => '参数数量错误:',
                '/failed to open stream:/i' => '打开文件流失败:',
                '/Call to a member function ([^\(]+)\(\) on ([^\s]+)/i' => '不能对 <b style="color:#00F">$2</b>  类型调用其成员函数 <b style="color:#00F">$1</b>()',
                '/Call to undefined method/i' => '调用了一个未定义的方法',
                '/Too few arguments to function/i' => '参数过少',
                '/ and exactly (\d+) expected /i' => ', 实际必须要 <b style="color:#00F">$1</b> 个参数 ',
                '/Return value of ([^\(]+)\(\) must be of the type (\w+), (\w+) returned /i' => '$1() 必须返回一个 <b style="color:#00F">$2</b> 类型, 但它返回了一个 <b style="color:#F00">$3</b> 类型 ',
                '/Failed opening required/' => '<b style="color:#F00">调取文件失败</b>',
                '/No such file or directory/i' => '<b style="color:#F00">文件或者目录不存在</b>',
                '/stat failed for/i' => '统计失败',
                '/(expects|Expected) parameter (\d+) to be (an )?(\w+), (\w+) given/i' => '第 $2 个参数, 需要 <b style="color:#00F">$4</b> 类型的参数, 而给的是 <b style="color:#F00">$5</b> 类型',
                '/Cannot modify header information - headers already sent by/i' => '无法修改 <b style="color:#00F">header</b> 头信息 - 标头信息已提前发送',
                '/output started at/i' => 'header 输出始于',
                '/Argument (\d+) passed to ([^\(]+)\(\) must be an instance of (\w+), (\w+) given/' => '传递给 $2() 的参数 $1 必须是 $3 的实例，而给的是 $4 类型',
                '/Cannot use a scalar value as an array/i' => "目标不是一个数组,无法使用数组下标",
                '/Delimiter must not be alphanumeric or backslash/i' => '正则表达式的格式不正确',
                '/Implicit conversion from float ([^\s]+) to ([^\s]+) loses precision/i' => '浮点 $1 隐式转换成 $2 会丢失精度',
                '/Cannot use object of type stdClass as array/' => '不能对 stdClass 对象进行 array 数组式的操作',
                '/\bUndefined variable\b/i' => '未定义的变量',
                '/\bUnknown modifier\b/i' => '未知的修饰符',
                '/\bUndefined\b/i' => '未定义的',
                '/\barray key\b/i' => '数组键',
                '/\bcalled\b/i' => ' 调用 ',
                '/\band defined\b/i' => '定义',
                '/\bstatic property\b/i' => '静态属性',
                '/\bAccess to\b/i' => '访问',
                '/\bshould not be\b/i' => '不能够',
                '/\bNon-static method\b/i' => '不是静态方法',
                '/\bstatically\b/i' => '静态指向',
                '/\bAccess\b/i' => '访问',
                '/\bundeclared\b/i' => '未声明的',
                '/(\d+) passed /i' => ' 传递了 <b style="color:#F00">$1</b> 个参数 ',
                '/\s([^\(\s]+)\(\)/' => ' <b style="color:#00F">$1()</b>',
            ];
            self::$msgFanyiPatternKeys = array_keys($pattern);
            self::$msgFanyiPatternValues = array_values($pattern);
        }
        if ($error) {
            $error['message'] = preg_replace(self::$msgFanyiPatternKeys, self::$msgFanyiPatternValues, $error['message']);
        }
        */
        return require(DIR_MVC . "/core/onShutdown/" . $type . ".php");
    }

    /**
     * 生产一个错误
     * @param $error
     * @return exit
     */
    public static function errorMake($error)
    {
        if (MVC::$hasErrorMake === false) {
            ob_clean(); // 当有错误时, 清除前面的输出
            http_response_code(403);
            MVC::$hasErrorMake = true;
        }
        $error = array_merge([
            'type' => E_ERROR,
            'file' => '',
            'line' => 0,
            'message' => '',
            'backtrace' => null,
            'method' => '',
            'sql' => '',
        ], $error);
        if (IS_CLI) {
            $head = '<link rel="icon" type="image/png" sizes="32x32" href=' . HOST . '"/static/x/media/error-favicon-32x32.png">';
            $contents =  MVC::echo(self::errorReport('debug', $error, __FUNCTION__), 'html', $head, '<body>', true);
            file_put_contents_lock(DIR_RUNTIME . '/cli_error.html', $contents);
            echo require(DIR_MVC . "/core/onShutdown/cli.php");
        } elseif (DEBUG) {
            $contents = self::errorReport('debug', $error, __FUNCTION__);
            if ($contents != '') {
                if (!headers_sent()) {
                    header('Content-Type: text/html; charset=UTF-8');
                }
                MVC::echo($contents, 'html');
            }
        } else {
            self::errorReport('product', $error, __FUNCTION__);
        }
        //这里不要加中断 exit()
    }

    /**错误处理函数 */
    public static function errorHandlerDebug($errno, $message, $errfile, $errline)
    {
        if ($errno == E_NOTICE) {
            return;
        }
        if ($errno == E_WARNING && substr($errfile, -8) == '.tpl.php' && !MVC::$cfg['error']['tplUndefinedVariable']) {
            return;
        }
        $error['message'] = preg_replace_callback('/\s([^\.]+\.php)/i', function ($m) {
            return relDir($m[0]);
        }, $message);
        $error = [
            'type' => $errno,
            'file' => $errfile,
            'line' => $errline,
            'message' => $message,
            'files' => self::messageFiles($message)
        ];
        if ($errno == E_NOTICE) {
            exit();
        }
        self::errorMake($error);
    }

    /**异常中断处理函数 Debug */
    public static function onShutdownDebug($error = null)
    {
        $error = $error ?: error_get_last();
        if (!is_null($error)) {
            $backtrace = [];
            $arr = explode("\n", $error['message']);
            $error['message'] = array_shift($arr);  //第一行为错误信息
            foreach ($arr as $v) {
                if (substr($v, 0, 1) == '#') {
                    if (preg_match('/\s([^\(]+)\((\d+)\)/', $v, $mA)) {
                        $trace = ['file' => '', 'line' => '', 'class' => '', 'type' => '', 'function' => '', 'object' => '', 'args' => '', 'files' => null];
                        $trace['file'] = $mA[1];
                        $trace['line'] = $mA[2];
                        if (preg_match('/\:\s([^\(]+)/', $v, $mB)) {
                            $trace['function'] = $mB[1];
                            if (preg_match('/([^\:\-]+)([\:\-\>]+)(.+)/', $trace['function'], $mC)) {
                                $trace['class'] = $mC[1];
                                $trace['type'] = $mC[2];
                                $trace['function'] = $mC[3];
                            }
                        }
                        $trace['function'] .= '()';
                        $backtrace[] = $trace;
                    }
                }
            }
            if ($backtrace) {
                $error['backtrace'] = $backtrace;
            }
            $error['files'] = self::messageFiles($error['message']);
            if (!MVC::$cfg['error']['allBacktrace'] && $error) {
                $error['message'] = preg_replace_callback('/\sin\s([^\.]+\.php)/i', function ($m) {
                    return relDir($m[0]); //将绝对路径转相对路径
                }, $error['message']);
            }
            $contents = self::errorReport('debug', $error, __FUNCTION__);
            if ($contents != '') {
                if (!headers_sent()) {
                    header('Content-Type: text/html; charset=UTF-8');
                }
                MVC::echo($contents, 'html');
            }
        }
    }


    /**异常中断处理函数 Product */
    public static function onShutdownProduct($error = null)
    {
        $error = $error ?: error_get_last();
        if (!is_null($error)) {
            MVC::echo(self::errorReport('product', $error, __FUNCTION__), 'html');
        }
    }

    /**异常中断处理函数 Product */
    public static function onShutdownCli($error = null)
    {
        $error = $error ?: error_get_last();
        if (!is_null($error)) {
            self::errorMake($error);
        }
    }

    /**
     * 返回错误信息中的所有的文件名和行
     * @param string $message
     * @return array [ [string filename, int line], ...]
     */
    public static function messageFiles(string $message): array
    {
        $result = [];
        preg_match_all('/\sin\s+([^\s]+\.php)/i', $message, $m);
        if (isset($m[1])) {
            foreach ($m[1] as $v) {
                $result[]['file'] = fdir($v);
            }
            preg_match_all('/(\son line \d+|\.php\:\d+|\.php\(\d+\))/i', $message, $mA);
            if (isset($mA[1])) {
                foreach ($mA[1] as $i => $v) {
                    preg_match('/(\d+)/', $v, $mB);
                    $result[$i]['line'] = $mB[1];
                }
            }
        }
        return $result;
    }

    /**
     * 返回错误代码的类型名称
     */
    public static function errorTypeName(int $type): string
    {
        switch ($type) {
            case E_ERROR: // 1
                return 'E_ERROR';
            case E_WARNING: // 2
                return 'E_WARNING';
            case E_PARSE: // 4
                return 'E_PARSE';
            case E_NOTICE: // 8
                return 'E_NOTICE';
            case E_CORE_ERROR: // 16
                return 'E_CORE_ERROR';
            case E_CORE_WARNING: // 32
                return 'E_CORE_WARNING';
            case E_COMPILE_ERROR: // 64
                return 'E_COMPILE_ERROR';
            case E_COMPILE_WARNING: // 128
                return 'E_COMPILE_WARNING';
            case E_USER_ERROR: // 256
                return 'E_USER_ERROR';
            case E_USER_WARNING: // 512
                return 'E_USER_WARNING';
            case E_USER_NOTICE: // 1024
                return 'E_USER_NOTICE';
            case E_STRICT: // 2048
                return 'E_STRICT';
            case E_RECOVERABLE_ERROR: // 4096
                return 'E_RECOVERABLE_ERROR';
            case E_DEPRECATED: // 8192
                return 'E_DEPRECATED';
            case E_USER_DEPRECATED: // 16384
                return 'E_USER_DEPRECATED';
        }
        return "E_UNKNOW";
    }
}
