<?php

namespace xmvc;

/**
 * 结果输出类
 */
class Dump
{
    /**输出次数统计 */
    protected static $outputCount = 0; //输出次数统计
    public function __construct()
    {
        ;
    }

    public static function dumpResult(bool $isChangeRN, ...$args)
    {
        $backtrace = debug_backtrace(DEBUG_BACKTRACE);
        $caller = getTraceCaller($backtrace, [['xmvc\Dump', 'dumpResult']]);
        $fileArr = file($caller['file']);
        $code = trim($fileArr[$caller['line'] - 1]);
        unset($fileArr);
        preg_match('/\((?:[^()]|(?R))*\)/', $code, $m);
        if ($m) {
            $t = trim($m[0], '()');
            $t = explode(',', $t);
        }
        $result = '';
        foreach ($args as $index => $v) {
            if (isset($t[$index])) {
                $code = "<span class='dd-call-active'>" . $t[$index] . "</span> ";
            }
            $result .= self::dumpHtml($isChangeRN, $v, $index, $caller, $code);
        }
        if (IS_CLI) {
            $head = '<link rel="icon" type="image/png" sizes="32x32" href=' . HOST . '"/static/x/media/dd-favicon-32x32.png">';
            $title  = '<h1>DUMP <span style="font-size:13px;color:#008">' . date('Y-m-d H:i:s') . '</span></h1>
                        <div style="color:#069;font-size:14px;word-break: break-all;">控制器: ' . MVC::$method . ')</div>';
            $result = $title . $result;

            $contents =  MVC::echo($result, 'html', $head, '<body>', true);
            file_put_contents_lock(DIR_RUNTIME . '/cli_dump.html', $contents);
            file_put_contents_lock(DIR_RUNTIME . '/cli_dump.json', json_encode($args, JSON_UNESCAPED_UNICODE));
            $result = '';
            $result = "\r\n=================================DUMP BEGIN";
            //控制台
            foreach ($args as $index => $v) {
                $result .= "\r\n";
                if (isset($t[$index])) {
                    $result .= $t[$index] . "   --   ";
                }
                $result .=  relDir($caller['file']) . "(" . $caller['line'] . ")";
                $result .= "\r\n-------------------------------------------\r\n";
                $result .= "\r\n" . print_r($v, true);
                $result .= "\r\n===========================================\r\n";
            }
            $result .= "\r\n\r\nDUMP END.\r\n\r\n";
        }
        self::$outputCount++;
        return $result;
    }

    public static function dumpHtml(bool $isChangeRN, $variable, $index, $caller, $code)
    {
        $json = json_encode($variable);
        $result  = "<script>console.log($json);</script><div class='dd'><pre style='max-height:auto;overflow:auto'>";
        $result .= '<span class="dd-call">' . $code . '</span>';
        $result .= '<span class="dd-file">' . relDir($caller['file']) . '(' . $caller['line'] . ')</span>' . PHP_EOL;
        $result .= self::printR($variable, 1, $isChangeRN);
        $result .= PHP_EOL . '</pre></div>' . PHP_EOL;
        return $result;
    }
    /**
     * 自定义对象格式化输出 print_r
     * @param mixed $variable 待打印的变量
     * @return string
     * @ignore
     */
    public static function printR($variable, $tab, bool $isChangeRN)
    {
        if (is_object($variable) && !($variable instanceof \Closure)) {
            $arr = json_decode(json_encode($variable), true);
            if (!empty($arr)) {
                $variable = $arr;
            }
        }
        if (!is_array($variable)) {
            return self::echoValueByType($variable, $isChangeRN);
        }
        $result = '';
        $result .= '<span class="dd-area-' . $tab . '">[</span>' . PHP_EOL;
        $reg = 0;
        foreach ($variable as $k => $v) {
            for ($i = 0; $i < $tab; $i++) {
                $result .= "    ";
            }
            if (is_array($v)) {
                $result .= '"<span class="dd-array">' . $k . '</span>" => ';
                $result .= self::printR($v, $tab + 1, $isChangeRN);
            } elseif (is_object($v)) {
                $result .= '"<span class="dd-object">' . $k . '</span>" => ';
                $result .= self::printR($v, $tab + 1, $isChangeRN);
            } else {
                $result .= '"' . $k . '" <span class="dd-to">=></span> ';
                $result .= self::echoValueByType($v, $isChangeRN);
            }
            if ($reg != count($variable) - 1) {
                $result .= ',';
            }
            $result .= PHP_EOL;
            $reg++;
        }
        for ($i = 0; $i < $tab - 1; $i++) {
            $result .= "    ";
        }
        $result .= '<span class="dd-area-' . $tab . '">]</span>';
        return $result;
    }
    /**
     * 按值的类型输出对应的颜色
     * @param mixed $value
     * @return string
     * @ignore
     */
    public static function echoValueByType($value, bool $isChangeRN)
    {
        $type = gettype($value);
        if (is_null($value)) {
            $cssClass = "dd-blue";
            $value = 'null';
        } elseif (is_bool($value)) {
            $cssClass = "dd-blue";
            $value = $value ? 'true' : 'false';
        } elseif (is_float($value) || is_integer($value)) {
            $cssClass = "dd-num";
        } elseif (is_string($value) && substr($value, 0, 10) == '{stdClass:') {
            $cssClass = "dd-stdclass";
        } elseif (is_string($value)) {
            $lineCount = count(explode("\n", $value));
            $cssClass = $lineCount == 1 ? "dd-string" : "dd-string-multiple";
            if ($isChangeRN) {
                $value = str_replace("\r\n", '\r\n', $value);
            }
            $value = '"' . htmlspecialchars($value) . '"';
        } elseif ($value instanceof \Closure) {
            $cssClass = "dd-code";
            $value = self::phpCodeColor(closureToString($value));
        } elseif (is_object($value)) {
            //$cssClass = "dd-stdClass";
            //$class = new ReflectionClass($value);
            //$value = "new {$class->name}()";
            $cssClass = "dd-object-val";
            $value =  print_r($value, true);
        } else {
            $cssClass = '';
        }
        return '<span class="' . $cssClass . '">' . $value . '</span>';
    }


    public static function printResult(bool $isChangeRN, ...$args): string
    {
        $caller = getTraceCaller(debug_backtrace(DEBUG_BACKTRACE), [
            [__CLASS__, __FUNCTION__],
            [__CLASS__, 'ddPrint'],
        ]);
        $fileArr = file($caller['file']);
        $code = trim($fileArr[$caller['line'] - 1]);
        unset($fileArr);
        preg_match('/\((?:[^()]|(?R))*\)/', $code, $m);
        if ($m) {
            $codeArr = trim($m[0], '()');
            $codeArr = explode(',', $codeArr);
        }

        $EOF = "\r\n";
        $argsCount = count($args);
        $html = '';
        $hrefArr = [];
        $styles = [
            //中括号
            '@<mgh>@' => '<span style="color:blue">', '@</mgh>@' => '</span>',
            //中括号中间是数字的
            '@<mghNum4>@' => '<hr style="border:1px solid red"><span style="color:red;font-size:1.3em;font-weight:bold">', '@</mghNum4>@' => '</span>',
            '@<mghNum>@' => '<span style="color:#F55;font-weight:bold">', '@</mghNum>@' => '</span>',
            //Array
            '@<array>@' => '<span style="color:#0AA">', '@</array>@' => '</span>',
            //string
            '@<string>@' => '<span style="color:#000">', '@</string>@' => '</span>',
            //多行文本
            '@<stringMultiple>@' => '<span style="color:#A6A">', '@</stringMultiple>@' => '</span>',
        ];
        foreach ($args as $argIdx => $arg) {
            $str = var_export($arg, true);
            $str = htmlspecialchars($str);
            // 匹配 => 和 [ 或 ) 之间有换行符的文本
            $pattern = '/\=(\&gt;|\>)(.*?)(\s*)\s{4}(\[|\))/s';
            if (preg_match_all($pattern, $str, $m)) {
                foreach ((array)$m[2] as $i => $m2v) {
                    $firstLine = ltrim($m2v);
                    $firstLine = trim(substr($firstLine, 0, strpos($firstLine, "\n")));
                    if ($firstLine != 'Array' && substr($firstLine, -7) != ' Object') {
                        $lineCount = count(explode("\n", $m2v));
                        if ($lineCount > 1) {
                            if ($isChangeRN) {
                                $m[2][$i] = str_replace("\r\n", '\r\n', $m[2][$i]);
                            }
                            $str = str_replace($m[0][$i], '=' . $m[1][$i] . '<stringMultiple>' . $m[2][$i] . '</stringMultiple>' . $m[3][$i] . '    ' . $m[4][$i], $str);
                        } else {
                            $str = str_replace($m[0][$i], '=' . $m[1][$i] . '<string>' . $m[2][$i] . '</string>' . $m[3][$i] . '    ' . $m[4][$i], $str);
                        }
                    }
                }
            }
            //$str = preg_replace("/(?<!\\\\)((\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"))/s", '<ying>$1</ying>', $str); //双引号
            //$str = preg_replace("/(?<!\\\\)((\'[^'\\\\]*(?:\\\\.[^'\\\\]*)*\'))/s", '<ying>$1</ying>', $str); //单引号
            $str = preg_replace_callback("/([ ]+)\[([\d]+)\]\s*\=(\&gt;|>)/", function ($m) {
                $len = strlen($m[1]);
                if ($len == 4) {
                    return $m[1] . '<mghNum' . $len . '>[ ' . $m[2] . ' ]</mghNum' . $len . '> => ';
                } else {
                    return $m[1] . '[<mghNum>' . $m[2] . '</mghNum>] => ';
                }
            }, $str); //[ 数字 ]
            $str = preg_replace_callback("/(?<!\\\\)((\[[^\]\\\\]*(?:\\\\.[^\]\\\\]*)*\]))\s*\=(\&gt;|>)/s", function ($m) {
                return '[ <mgh>' . substr($m[1], 1, -1) . '</mgh> ] => ';
            }, $str); //[ ]
            $str = preg_replace("/(stdClass Object)/", '<span style="color:#aa0">$1</span>', $str); //=> stdClass Object
            $str = preg_replace("/=(\&gt;|>)\s*(Array)/", ' => <array>$2</array>', $str); //=> Array
            $str = preg_replace('/^Array\s/', '<span style="color:#0AA;font-size:1.2em">Array</span>', $str); //Array
            $str = preg_replace('/\=(\&gt;|>)/', '<span style="color:#CCC">=></span>', $str); //=>
            $str = preg_replace(array_keys($styles), array_values($styles), $str);
            $idx = $argIdx + 1;
            $hrefArr[] = '<a href="#_print_rResult_' . $idx . '">Goto ' . $idx . '</a>';
            if ($argsCount > 1) {
                $html .= '<h1 id="_print_rResult_' . $idx . '">' . $idx . ' <a href="#" style="font-size:10px;font-weight:200">Top</a></h1>';
            }
            $html .= '<pre style="border:1px solid #ccc;margin-bottom:1em;padding:.5em;font-size:14px;line-height:20px">';
            if ($codeArr[$argIdx]) {
                $html .= '<div style="border-bottom:1px solid #ccc"><span style="color:red;font-weight:bold">' . $codeArr[$argIdx] . '</span>  '
                        . '<span style="color:#aaa">' . relDir($caller['file']) . '(' . $caller['line'] . ')</span></div>';
            }
            $html .= $str;
            $html .= '</pre>' . $EOF;
        }
        if ($argsCount > 1) {
            echo "<ul><li>" . implode("</li><li>", $hrefArr) . "</li></ul>";
        }
        return $html . $EOF;
    }

    /**
     * php代码变色
     * @param string $code_string 代码行
     * @return string 变色后的字符串
     */
    public static function phpCodeColor($code_string)
    {
        $styles = [
            //锚点行
            '@<goto>@' => '<a id="goto">', '@[\r\n]?</goto>@' => "</a>\r\n",
            //高亮行
            '@<active>@' => '<div style="background:#FCC">', '/<\/active>/' => "</div>",
            //变量
            '@<var>@' => '<span style="color:#001080">', '@</var>@' => '</span>',
            //调用的函数名
            '@<func>@' => '<span style="color:#795E26">', '@</func>@' => '</span>',
            //单引号
            '@<ying>@' => '<span style="color:#A31515">', '@</ying>@' => '</span>',
            //括号 () {}
            '@<brackets>@' => '<span style="color:#145D90;padding:0 2px">', '@</brackets>@' => '</span>',
            //命名空间
            '@<nspace>@' => '<span style="color:#267FB2">', '@</nspace>@' => '</span>',
            //定义的函数名
            '@<newfunc>@' => '<span style="color:#795E26">', '@</newfunc>@' => '</span>',
            '/<\/div>\r\n/' => "</div>",
        ];
        $code_string = preg_replace_callback("/([^a-zA-Z0-9_]*)([a-zA-Z0-9_]+)(?=\()/", function ($m) {
            return $m[1] . '<func>' . $m[2] . '</func>';
        }, $code_string); //调用的函数名
        $code_string = preg_replace("/(?<!\\\\)((\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\"))/s", '<ying>$1</ying>', $code_string); //双引号
        $code_string = preg_replace("/(?<!\\\\)((\'[^'\\\\]*(?:\\\\.[^'\\\\]*)*\'))/s", '<ying>$1</ying>', $code_string); //单引号
        $code_string = preg_replace('/(\$[a-zA-Z0-9_]+)/', '<var>$1</var>', $code_string); //变量变色
        $code_string = preg_replace('/([\w+]?\\\\\w+)/', '<nspace>$1</nspace>', $code_string); //命名空间引用
        $code_string = preg_replace('/([a-zA-Z0-9_]+)\:\:/', '<nspace>$1</nspace>::', $code_string); //静态引用
        $code_string = preg_replace('/function\s+([^\(]+)[\s+]?\(/i', 'function <newfunc>$1</newfunc>(', $code_string); //定义的函数名

        //关键字
        $keywords = [
            'new', 'function', 'private', 'public', 'protected', 'static', 'as', 'namespace', 'use', 'class', 'extends',
            'echo',
            'require', 'require_once', 'include', 'include_once', 'isset', 'empty', 'is_file', 'method_exists'
        ];
        foreach ($keywords as $key) {
            $partern = '/\s\b' . $key . '\b\s/i';
            $code_string = preg_replace($partern, '<span style="color:blue"> ' . $key . ' </span>', $code_string);
        }
        //条件语句
        $keywords = [
            'foreach', 'for', 'while', 'if', 'else', 'else if', 'switch', 'case', 'break', 'continue', 'return',
        ];
        foreach ($keywords as $key) {
            $partern = '/\s\b' . $key . '\b\s/i';
            $code_string = preg_replace($partern, '<span style="color:#AF00DB"> ' . $key . ' </span>', $code_string);
        }
        //函数
        $functions = [
            'ReflectionClass', 'print_r', 'var_dump', 'dump', 'dd', 'R'
        ];
        $funcText = file_get_contents(DIR_MVC . '/Function.php'); //加入 MVC Function.php 函数
        preg_match_all('/function\s+([^\(]+)[\s+]?\(/i', $funcText, $m);
        foreach ($m[1] as $v) {
            $functions[] = $v;
        }
        foreach ($functions as $func) {
            $partern = '/<func>(\s*)' . $func . '<\/func>/i';
            $code_string = preg_replace($partern, '<span style="color:#00C">$1' . $func . '</span>', $code_string);
        }
        $code_string = preg_replace('/(\$this)/i', '<span style="color:blue">$1</span>', $code_string); //$this
        $code_string = preg_replace('/(self)\:\:/i', '<span style="color:blue">$1</span>::', $code_string); //self::

        $code_string = preg_replace('/([{|}|(|)])/', '<brackets>$1</brackets>', $code_string); //{}()
        //注释变色
        /*
        preg_match_all('/(\/\*[\s\S]*\*\/)/U', $code_string, $m);
        if(isset($m[1])){
            foreach($m[1] as $v){
                $code_string = str_replace($v, '<span style="color:#080">'. strip_tags($v) .'</span>', $code_string);
            }
        }
        */
        $code_string = preg_replace(array_keys($styles), array_values($styles), $code_string);
        $code_string = preg_replace_callback('/(\/\*[\s\S]*\*\/)/U', function ($m) {
            return '<span style="color:#080">' . strip_tags($m[1]) . '</span>';
        }, $code_string);
        $code_string = preg_replace_callback('/(?s)\/\/(.*?)(\r|\n|<\/div>)/', function ($m) {
            return '<span style="color:#080">//' . strip_tags($m[1]) . '</span>' . $m[2];
        }, $code_string);
        return $code_string;
    }
}
