<?php

namespace xmvc;

/**
 * 运行时处理类, 包括错误处理类
 */
class Runtime
{
    /**添加运行时间统计, 只在 DEBUG 模式下有效*/
    public static function add($remark = '', string $color = 'blue')
    {
        if ($remark === 'PHP SYSTEM') {
            file_put_contents_lock(
                DIR_RUNTIME . '/runtime.html',
                '[' . date('Y-m-d H:i:s') . '] <span style="color:blue">'
                    . MVC::$method . "</span> 已经开始计算运行状态, 但意外中断, 代码中可能存在 <b>dd(),exit(),die()</b> 等中断指令"
            );
            if (IS_CLI) {
                $tmp = '<!DOCTYPE html><html><head><meta charSet="utf-8" />
                        <link rel="icon" type="image/png" sizes="32x32" href="/static/x/media/{:1}-favicon-32x32.png">
                        </head><body>';
                file_put_contents_lock(DIR_RUNTIME . '/cli_dump.html', qgprintf($tmp, 'dd'));
                file_put_contents_lock(DIR_RUNTIME . '/cli_error.html', qgprintf($tmp, 'error'));
            }
        }
        $backtrace = debug_backtrace(DEBUG_BACKTRACE);
        $caller = $backtrace[1];

        //内存
        $useMemory = memory_get_usage() ?: 0;  //占用的内存
        $peakMemory = memory_get_peak_usage() ?: 0; //分配的内存
        MVC::$runtimeArr[] = [
            'msec' => msec(),
            'class' => $caller['class'],
            'type' => $caller['type'],
            'function' => $caller['function'],
            'call' => $caller['class'] . $caller['type'] . $caller['function'],
            'file' => $caller['file'],
            'line' => $caller['line'],
            'useMemory' => $useMemory,
            'peakMemory' => $peakMemory,
            'remark' => $remark,
            'color' => $color,
        ];
    }

    /**计算运行时间统计*/
    public static function runtimeCalc()
    {
        $result = ['items' => []];
        $item = [
            'useTime' => 0,
            'msec' => 0,
            'class' => '',
            'type' => '',
            'function' => '',
            'call' => '',
            'file' => '',
            'line' => 0,
            'useMemory' => 0,
            'peakMemory' => 0,
            'remark' => '',
            'color' => '#333'
        ];
        $count = count(MVC::$runtimeArr);
        for ($i = 0; $i < $count; $i++) {
            $v = array_merge($item, MVC::$runtimeArr[$i]);
            $callFile = str_replace('\\', '/', $v['file']);
            $callFile = str_replace(ROOT, '/', $callFile);
            $classFile = '';
            $startLine = '';
            $endLine = '';
            if ($v['class']) {
                $reflectionClass = new \ReflectionClass($v['class']);
                $reflectionMethod = $reflectionClass->getMethod($v['function']);
                $classFile = $reflectionMethod->getFileName();
                $startLine = $reflectionMethod->getStartLine();
                $endLine = $reflectionMethod->getEndLine();
                $classFile = str_replace('\\', '/', $classFile);
                $classFile = str_replace(ROOT, '/', $classFile);
            }

            $v['callFile'] = $callFile;
            $v['callLine'] = $v['line'];
            $v['classFile'] = $classFile;
            $v['funtionStartLine'] = $startLine;
            $v['funtionEndLine'] = $endLine;
            if ($i > 0) {
                $v['useTime'] = $v['msec'] - MVC::$runtimeArr[$i - 1]['msec'];
                $v['useMemory'] = $v['useMemory'] - MVC::$runtimeArr[$i - 1]['useMemory'];
                $v['peakMemory'] = $v['peakMemory'];
            }
            $result['items'][] = $v;
        }
        $result['useAllTime'] = round(MVC::$runtimeArr[$count - 1]['msec'] - MVC::$runtimeArr[0]['msec']);
        return $result;
    }

    /**
     * 显示运行时间统计
     *
     * @param boolean $returnHtml 默认 false 直接输出html 如果为 true 则返回html文本,不直接输出
     * @return void|string
     */
    public static function createRuntimeReport(bool $returnHtml = false)
    {
        $runtimes = self::runtimeCalc();
        $link = '<link rel="icon" type="image/png" sizes="32x32" href="/static/x/media/runtime-favicon-32x32.png">';
        $HTTP_URL_SELF = HTTP_URL_SELF;
        $method = MVC::$method;
        $datetime = date('Y-m-d H:i:s');
        $html = <<<EOD
            <div style="font-size:12px"><u>{$HTTP_URL_SELF}</u></div>
            <h2>
            {$method}
            <span style="font-size:14px;color:#999">[ {$datetime} ]</span>
            </h2>
            <table class="mvc-purple-table" cellspacing="0" cellpadding="0">
                <thead>
                    <th>调用者</th>
                    <th>行</th>
                    <th>调用方法</th>
                    <th>描述</th>
                    <th>执行时间(秒)</th>
                    <th>毫秒</th>
                    <th>分配内存</th>
                    <th>内存峰值</th>
                    <th>断点文件</th>
                    <th>断点函数起始行</th>
                <thead>
            <tbody>
        EOD;
        $count = count($runtimes['items']);
        for ($i = 0; $i < $count; $i++) {
            $v = $runtimes['items'][$i];
            $html .= '<tr>';
            $html .= '<td>' . relDir($v['callFile']) . '</td>';
            $html .= '<td>' . $v['callLine'] . '</td>';
            $html .= '<td>' . $v['call'] . '</td>';
            if ($v['color']) {
                $html .= '<td style="color:' . $v['color'] . '">' . $v['remark'] . '</td>';
            } else {
                $html .= '<td>' . $v['remark'] . '</td>';
            }
            $html .= '<td>' . round($v['useTime'] / 1000) . '</td>';
            $html .= '<td>' . round($v['useTime'] % 1000) . '</td>';
            $html .= '<td>' . formatBytes($v['useMemory']) . '</td>';
            $html .= '<td>' . formatBytes($v['peakMemory']) . '</td>';
            $html .= '<td>' . relDir($v['classFile']) . '</td>';
            $html .= '<td>' . $v['funtionStartLine'] . '</td>';
            $html .= '</tr>';
        }
        $colspan = 10;
        $useMemory = memory_get_usage();  //分配的内存
        $peakMemory = memory_get_peak_usage(); //内存使用峰值
        $html .= str_replace("\r\n", ' ', '<tr class="sum">
				  	<td colspan="' . ($colspan) . '">
                        总执行时间: <strong>' . formatMsecTime($runtimes['useAllTime']) . '</strong>
                        总分配内存: <strong>' . formatBytes($useMemory) . '</strong>
                        内存峰值: <strong>' . formatBytes($peakMemory) . '</strong>
                        去PHP本身总分配内存: <strong>'
                        . formatBytes($useMemory - $runtimes['items'][0]['useMemory']) . '</strong>
                        去PHP本身内存峰值: <strong>'
                        . formatBytes($peakMemory - $runtimes['items'][0]['peakMemory']) . '</strong>
                    </td>
				  </tr>');
        $html .= "</tbody>";
        $html .= "</table>";

        //SQL执行时间
        $sqlList = [];
        foreach (MVC::$runtimeSQLs as $i => $v) {
            $sqlList[$i]['执行顺序'] = $i + 1;
            $sqlList[$i]['执行时间(秒)'] = $v['time'];
            $sqlList[$i]['执行 SQL'] = $v['sql'];
        }
        $html .= "<br /><br />" . dumpTableResult($sqlList, [
            '执行顺序' => 'width:50px;text-align:right',
            '执行时间(秒)' => 'width:80px;text-align:right']);

        //所有加载的文件列表
        $tmp = get_included_files();
        $allIncludeFiles = [];
        $total = 0;
        foreach ($tmp as $i => $v) {
            $size = is_file($v) ? filesize($v) : 0;
            $allIncludeFiles[$i]['加载顺序'] = $i + 1;
            $allIncludeFiles[$i]['大小'] = formatBytes($size);
            $allIncludeFiles[$i]['所有加载的文件列表'] = relDir($v);
            $total += $size;
        }
        $html .= "<br /><br />" . dumpTableResult($allIncludeFiles, [
            '加载顺序' => 'width:50px;text-align:right',
            '大小' => 'width:90px;text-align:right']);
        $html .= "<h14>文件总数量: <span style='color:blue'>" . count($tmp) . '</span> 个</h14><br />';
        $html .= "<h14>文件总大小: <span style='color:blue'>" . formatBytes($total) . '</span></h14>';
        if ($returnHtml) {
            return $html;
        } else {
            $html = MVC::echo($html, 'html', $link, $body = '<body style="padding:1em">', true);
            file_put_contents_lock(DIR_RUNTIME . '/runtime.html', $html);
        }
    }
}
