<?php

namespace Velis\Mvc\View\Helper;

use Velis\App;
use Velis\Debug;
use Velis\Lang;

/**
 * Application debug profiler
 * @author Olek Procki <olo@velis.pl>
 */
class Profiler extends BaseProfiler
{
    /**
     * Outputs profiler
     * @return string|void
     */
    public function __invoke(bool $standalone = false)
    {
        $this->_standalone = $standalone;

        return $this->getProfiler();
    }

    /**
     * @inheritDoc
     *
     * @return void|string
     */
    public function getProfiler()
    {
        if (self::$_rendered) {
            return;
        }

        if ($this->_standalone) {
            $this->buildRequiredScripts();
        }

        $this->buildProfilerHead();
        $this->buildProfiler();
        $this->buildProfilerBottom();

        self::$_rendered = true;

        return $this->_output;
    }

    /**
     * Build server variables list
     */
    protected function buildServerVars(): self
    {
        $this->_output .= '
                <div class="profilerItem">
                    <table class="list2">
                    <thead>
                    <tr><td>
                            [<a href="javascript:;"
                                onclick="dojo.toggleClass(\'profilerServerRow\', \'displayNone\')">show/hide</a>]
                            &nbsp;&nbsp;&nbsp;$_SERVER
                        </td>
                    </tr>
                    </thead>
                    <tr id="profilerServerRow" ' . ($this->_standalone ? '' : 'class="displayNone"') . '>
                        <td class="listItems"><ul>';

        foreach ($_SERVER as $k => $v) {
            $this->_output .= '<li><strong>' . $k . '</strong>: ' . (is_array($v) ? Debug::dump($v, true) : $v) . '</li>';
        }

        $this->_output .= '</ul></td></tr>';

        $this->_output .= '</table></div>';

        return $this;
    }

    /**
     * Build checked privs list
     */
    protected function buildPrivsList(): self
    {
        $privs = $this->getPrivsDataProvider();

        $this->_output .= '
                <div class="profilerItem">
                    <table class="list2">
                    <thead>
                    <tr><td>
                            [<a href="javascript:;"
                                onclick="dojo.toggleClass(\'privsRow\', \'displayNone\')">show/hide</a>]
                            &nbsp;&nbsp;&nbsp; Checked privs
                        </td>
                    </tr>
                    </thead>
                    <tr ' . ($this->_standalone ? '' : 'class="displayNone"') . ' id="privsRow">
                        <td class="filesList">
                            <ul>';

        foreach ($privs as $file => $privsPerFile) {
            $this->_output .= '<li><b>' . $file . '</b></li>';
            $this->_output .= '<ul>';
            foreach ($privsPerFile as $priv) {
                $this->_output .= '<li>' . $priv . '</li>';
            }
            $this->_output .= '</ul>';
        }
        $this->_output .= '</ul>';
        $this->_output .= '</td></tr>';
        $this->_output .= '</table></div>';

        return $this;
    }

    /**
     * Build included files list
     */
    protected function buildIncludesList(): self
    {
        $this->_output .= '
                <div class="profilerItem">
                    <table class="list2">
                    <thead>
                    <tr><td>
                            [<a href="javascript:;"
                                onclick="dojo.toggleClass(\'profilerIncludesRow\', \'displayNone\')">show/hide</a>]
                            &nbsp;&nbsp;&nbsp;' . $this->getIncludesListKey() . '
                        </td>
                    </tr>
                    </thead>
                    <tr ' . ($this->_standalone ? '' : 'class="displayNone"') . ' id="profilerIncludesRow">
                        <td class="filesList">';


        $includedFiles = array();
        $colors        = array(
            'ZendFramework'     => 'darkGreen',
            'SmartyTemplates'   => 'darkOrange',
            'Library'           => 'green',
            'Application'       => 'black'
        );

        $files = $this->getIncludesListDataProvider();
        sort($files);

        foreach ($files as $file) {
            if (strpos($file, 'lib' . DIRECTORY_SEPARATOR . 'Zend')) {
                $includedFiles['ZendFramework'][] = substr($file, strpos($file, 'Zend'));
            } elseif (strpos($file, 'data' . DIRECTORY_SEPARATOR . 'smarty-compiled')) {
                $includedFiles['SmartyTemplates'][] = substr($file, strpos($file, 'smarty-compiled'));
            } elseif (strpos($file, 'lib' . DIRECTORY_SEPARATOR) || strpos($file, 'lib-src' . DIRECTORY_SEPARATOR) || strpos($file, 'Smarty' . DIRECTORY_SEPARATOR)) {
                $includedFiles['Library'][] = substr($file, strpos($file, 'lib'));
            } else {
                $includedFiles['Application'][] = str_replace(ROOT_PATH, '', $file);
            }
        }
        foreach ($includedFiles as $groupName => $group) {
            $this->_output .= '<b>' . $groupName . ' (' . sizeof($includedFiles[$groupName]) . '):</b>';
            $this->_output .= '<ol type="1" class="' . $colors[$groupName] . '">';
            foreach ($group as $file) {
                $this->_output .= '<li>' . $file . '</li>';
            }
            $this->_output .= '</ol>';
        }

        $this->_output .= '</ol>';
        $this->_output .= '</td></tr>';
        $this->_output .= '</table></div>';

        return $this;
    }

    /**
     * Build server variables list
     */
    protected function buildMissingLangs(): self
    {
        $missingLanguages = $this->getMissingLanguagesDataProvider();

        if ($missingLanguages) {
            $this->_output .= '
                    <div class="profilerItem">
                        <table class="list2">
                        <thead>
                        <tr><td>
                                [<a href="javascript:;"
                                    onclick="dojo.toggleClass(\'profilerLangRow\', \'displayNone\')">show/hide</a>]
                                &nbsp;&nbsp;&nbsp;Missing translations (' . count(Lang::getMissingTranslations()) . ')
                            </td>
                        </tr>
                        </thead>
                        <tr id="profilerLangRow">
                            <td class="listItems"><ul>';

            if (Lang::$onEmpty == Lang::BHVR_RETURN_EDIT_LINK) {
                $translations = array();
                foreach ($missingLanguages as $translation) {
                    $translations[] = Lang::get($translation);
                }
            } else {
                $translations = Lang::getMissingTranslations();
            }
            $this->_output .= "'" . implode("', '", $translations) . "'";
            $this->_output .= '</ul></td></tr>';
            $this->_output .= '</table></div>';
        }

        return $this;
    }

    /**
     * Builds cache profiler table
     */
    protected function buildCacheProfiler(): self
    {
        $cacheProfiler = $this->getCacheDataProvider();

        $this->_output .= '
                    <h5>cache</h5>
                    <table class="table-profiler table table-striped">
                        <thead>
                            <tr>
                                <th>function&nbsp;(' . sizeof($cacheProfiler) . ')</th>
                                <th>start</th>
                                <th>exec</th>
                                <th>caller</th>
                                <th>cache value ID</th>
                                <th>lifetime</th>
                                <th>status</th>
                            </tr>
                        </thead>';

        if (count($cacheProfiler)) {
            $lp = 1;

            $execTimeTotal = 0;

            foreach ($cacheProfiler as $row) {
                $execTimeTotal += $row['execTime'];

                if ($row['status'] === true) {
                    $status = 'OK';
                } elseif ($row['status'] === false) {
                    $status = 'FAILED/EXPIRED';
                } else {
                    $status = '';
                }

                $this->_output .= '
                <tr onclick="Profiler.bookmark(this)">
                    <td class="text-left func' . (strpos($row['func'], 'load') === 0 ? ' gray' : '') . '"><span style="margin-right: 10px;">' . $lp . '</span>' . $row['func'] . '</td>
                    <td class="testRight">'  . round($row['startTime'] - SCRIPT_START, 4) . 's</td>
                    <td class="testRight'  . ($row['execTime'] > 0.1 ? ' red' : '') . '">' . round($row['execTime'], 4) . 's</td>
                    <td class="backtrace">
                        <div>'
                    . '<b>file:</b> ' . $row['caller']['file'] . '(<b>' . $row['caller']['line'] . '</b>)<br />'
                    . '<b>func:</b> ' . $row['caller']['func'] . ' [<a href="javascript:;" onclick="Profiler.backtrace(\'cache\',' . $lp . ')"><b>more</b></a>]<br />' .
                    '</div>
                        <span class="backtraceRow" id="cacheBacktraceRow' . $lp . '">';
                foreach ($row['caller']['backtrace'] as $backtraceRow) {
                    $this->_output .=  '<br /><b>file:</b> ' . $backtraceRow['file'] . '(<b>' . $backtraceRow['line'] . '</b>)<br />'
                        . (strlen($backtraceRow['func']) ? '<b>func:</b> ' . $backtraceRow['func'] . '(' . $backtraceRow['args'] . ')<br />' : '');
                }
                $this->_output .= '</span>
                    </td>';
                if (!is_array($row['id']) && strlen($row['id'])) {
                    $this->_output .= '<td class="cacheBlue">' . $row['id'] . '[<a class="unset red" href="/admin/index/unset-cache-value?keyName=' . urlencode($row['id']) . '">unset</a>]</td>';
                } elseif (is_array($row['id'])) {
                    $this->_output .= '<td class="cacheRed">';
                    foreach ($row['id'] as $id) {
                        $this->_output .= $id . '[<a class="unset" href="/admin/index/unset-cache-value?keyName=' . urlencode($id) . '">unset</a>]; ';
                    }
                    $this->_output .= '</td>';
                } else {
                    $this->_output .= '<td></td>';
                }
                $this->_output .= '
                    <td class="lifetime">' . ($row['lifetime'] ? '<b>'  . $row['lifetime'] . '</b>' : '') . '</td>
                    <td>' . $status . '</td>
                </tr>';
                $lp++;
            }
            $this->_output .= '
            <tr onclick="Profiler.bookmark(this)">
                <td class="total" colspan="1"><b>TOTAL:&nbsp;</b></td>
                <td></td>
                <td class="textRight textBold">' . round($execTimeTotal, 4) . 's</td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>';
        } else {
            $this->_output .=
                '<tr>
                <td colspan="10" class="noProfiles">
                    cache not used
                </td>
            </tr>';
        }
        $this->_output .= '</table>';

        return $this;
    }

    /**
     * Builds database profiler table
     */
    protected function buildDbProfiler(string $db = 'db'): self
    {
        $dbProfilerInfo = $this->getDbDataProvider($db);

        $this->_output .= '<h5 class="pull-left">database (' . $db . ')</h5>
        <table class="table-profiler table table-striped" >
            <thead>
                <tr>
                    <th>function&nbsp;(' . count($dbProfilerInfo) . ')</th>
                    <th></th>
                    <th>start</th>
                    <th>exec</th>
                    <th>cache</th>
                    <th>fetch</th>
                    <th>caller</th>
                    <th>query</th>
                    <th>params</th>
                    <th>info</th>
                </tr>
            </thead>';

        if (count($dbProfilerInfo)) {
            $execTimeTotal        = 0;
            $cacheAccessTimeTotal = 0;
            $fetchTimeTotal       = 0;

            while ($requestData = array_pop($dbProfilerInfo)) {
                $lp = 1;

                $this->_output .= '
                    <tr>
                        <td colspan="10"><b>' . $requestData['method'] . '</b> ' . $requestData['url'] . '</td>
                    </tr>
                ';

                foreach ($requestData['queries'] as $row) {
                    $execTimeTotal += $row['execTime'];
                    $cacheAccessTimeTotal += $row['cacheAccessTime'];
                    $fetchTimeTotal += $row['fetchTime'];
                    $sql = $this->sqlScriptParser->parse($row['query'], $row['params']);
                    $id = md5($sql . $lp);

                    $this->_output .= '
                    <tr onclick="Profiler.bookmark(this)">
                        <td class="text-left func' . (strpos($row['func'], 'cache') === 0 ? ' gray' : '') . '"><span style="margin-right: 10px;">' . $lp . '</span>' . $row['func'] . '</td>
                        <td>
                            <a href="javascript:void(0);" onclick="copyToClipboardFromNode(\'' . $id . '\')">
                                <i class="fa fa-copy"></i>
                            </a>
                            <pre class="displayNone" id="' . $id . '">' . htmlspecialchars($sql) . '</pre>
                        </td>
                        <td class="testRight">' . $row['startTime'] . 's</td>
                        <td class="testRight' . ($row['execTime'] > 0.1 ? ' red">' : '">') . round($row['execTime'], 4) . 's</td>
                        <td class="testRight">' . round($row['cacheAccessTime'], 4) . 's</td>
                        <td class="testRight">' . round($row['fetchTime'], 4) . 's</td>
                        <td class="backtrace">
                            <div>'
                        . '<b>file:</b> ' . $row['caller']['file'] . '(<b>' . $row['caller']['line'] . '</b>)<br />'
                        . '<b>func:</b> ' . $row['caller']['func'] . ' [<a href="javascript:;" onclick="Profiler.backtrace(\'' . $db . '\',' . $lp . ')"><b>more</b></a>]<br />' .
                        '</div>
                            <span class="backtraceRow" id="' . $db . 'BacktraceRow' . $lp . '">';

                    foreach ($row['caller']['backtrace'] as $backtraceRow) {
                        $this->_output .= '<br /><b>file:</b> ' . $backtraceRow['file'] . '(<b>' . $backtraceRow['line'] . '</b>)<br />'
                            . (strlen($backtraceRow['func']) ? '<b>func:</b> ' . $backtraceRow['func'] . '(' . $backtraceRow['args'] . ')<br />' : '');
                    }

                    $this->_output .= '</span>
                        </td>
                        <td class="query' . (strpos($row['func'], 'cache') === 0 ? '' : ' blue') . '">'
                        . ' [<a href="javascript:;" class="querySql" onclick="dojo.byId(\'queryRow-' . $lp . '\').style.whiteSpace = dojo.byId(\'queryRow-' . $lp . '\').style.whiteSpace == \'pre\' ? \'normal\' : \'pre\';"><b>pre</b></a>] '
                        . '<div class="' . (strpos($row['func'], 'cache') === 0 ? 'sqlCodeCached' : 'sqlCode') . '" id="queryRow-' . $lp . '">' . $row['query'] . '</div>
    
                        </td>
                        <td class="top params">
                                ' . Debug::dump($row['params'], true, false) .
                        '</td>
                        <td>' . $row['info'] . '</td>
                    </tr>';

                    $lp++;
                }
            }
            $this->_output .= '
            <tr onclick="Profiler.bookmark(this)">
                <td class="total" colspan="1"><b>TOTAL:&nbsp;</b></td>
                <td></td>
                <td class="textRight textBold">' . round($execTimeTotal, 4) . 's</td>
                <td class="textRight textBold">'  . round($cacheAccessTimeTotal, 4) . 's</td>
                <td class="textRight textBold">'  . round($fetchTimeTotal, 4) . 's</td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>';
        } else {
            $this->_output .=
                '<tr>
                <td colspan="9" class="noProfiles">
                    no queries analysed by profiler
                </td>
            </tr>';
        }
        $this->_output .= '</table>';

        return $this;
    }

    protected function buildProfilerHead()
    {
        $this->_output .= '
            <style>
                .table.table-profiler thead tr th {
                    color: #fff;
                    background-color: #348fe2;
                }

                .table.table-profiler thead tr th:first-child {
                    -webkit-border-top-left-radius: 2px;
                    -moz-border-radius-topleft: 2px;
                    border-top-left-radius: 2px;
                }

                .table.table-profiler thead tr th:last-child {
                    -webkit-border-top-right-radius: 2px;
                    -moz-border-radius-topright: 2px;
                    border-top-right-radius: 2px;
                }
            </style>

            <div id="profilerTable" ' . ($this->_standalone ? '' : 'class="displayNone"') . '>';

        return $this;
    }


    private function buildProfilerBottom()
    {
        $this->_output .= '</div>';
    }


    /**
     * Adds required profiler scripts when not rendered within layout template
     */
    public function buildRequiredScripts()
    {
        $this->_output .= '<script type="text/javascript" src="/res/js/base.js"></script>
                           <link rel="stylesheet" href="/res/css/base.css" type="text/css" />';
        return $this;
    }
}
