<%+cbi/valueheader%>
<%
    local fs = require "luci.openclash"
    local plugin_version = fs.oc_version()
%>
<script src="/luci-static/resources/openclash/js/oc-icons.js?v=<%=plugin_version%>"></script>
<body>
    <div id="tab" class="oc">
        <div id="tab-header">
            <ul class="oc-tabmenu">
                <li name="tab-header" class="oc-tab"><a href="#"><svg width="14" height="14"><use href="#oc-icon-package"/></svg><span><%:Plugin Logs%></span></a></li>
                <li name="tab-header" class="oc-tab-disabled"><a href="#"><svg width="14" height="14"><use href="#oc-icon-cpu"/></svg><span><%:Core Logs%></span></a></li>
                <li name="tab-header" class="oc-tab-disabled"><a href="#"><svg width="14" height="14"><use href="#oc-icon-bug"/></svg><span><%:Debug Logs%></span></a></li>
            </ul>
        </div>
        <div id="tab-content">
            <div class="dom" style="display: block;">
                <textarea id="cbid.openclash.config.clog" class="oc-input-textarea" style="display:inline" data-update="change" rows="32" cols="60" readonly="readonly" ></textarea>
            </div>
            <div class="dom">
                <textarea id="core_log" class="oc-input-textarea" style="display:inline" data-update="change" rows="32" cols="60" readonly="readonly" ></textarea>
                <div class="radio-button">
                    <input type="radio" id="info" name="radios" value="info" checked onclick="return switch_log_level(this.value)"/>
                    <label for="info"><%:Info%></label>
                    <input type="radio" id="warning" name="radios" value="warning" onclick="return switch_log_level(this.value)"/>
                    <label for="warning"><%:Warning%></label>
                    <input type="radio" id="error" name="radios" value="error" onclick="return switch_log_level(this.value)"/>
                    <label for="error"><%:Error%></label>
                    <input type="radio" id="debug" name="radios" value="debug" onclick="return switch_log_level(this.value)"/>
                    <label for="debug"><%:Debug%></label>
                    <input type="radio" id="silent" name="radios" value="silent" onclick="return switch_log_level(this.value)"/>
                    <label for="silent"><%:Silent%></label>
                </div>
            </div>
            <div class="dom">
                <textarea id="debug_log" class="oc-input-textarea oc-hidden" data-update="change" rows="32" cols="60" readonly="readonly" ></textarea>
                <div id="debug-render-wrapper">
                    <div id="debug-rendered"></div>
                    <div class="oc-usage-help-float" id="debug-copy-source" onclick="copyDebugSource()" title="<%:Copy Debug%>"><svg width="20" height="20"><use href="#oc-icon-copy"/></svg></div>
                </div>
            </div>
        </div>
    </div>

<div class="oc oc-text-center oc-w100">
    <div class="select-popup-footer log-footer">
        <button type="button" class="footer-btn" id="stop_refresh_button" onclick="return stop_refresh()"><svg width="14" height="14"><use href="#oc-icon-player-pause"/></svg><span><%:Stop Refresh%></span></button>
        <button type="button" class="footer-btn" id="start_refresh_button" onclick="return start_refresh()"><svg width="14" height="14"><use href="#oc-icon-player-play"/></svg><span><%:Start Refresh%></span></button>
        <button type="button" class="footer-btn" id="del_log_button" onclick="return del_log()"><svg width="14" height="14"><use href="#oc-icon-trash"/></svg><span><%:Clean%></span></button>
        <button type="button" class="footer-btn" id="down_log_button" onclick="return download_log()"><svg width="14" height="14"><use href="#oc-icon-download"/></svg><span><%:Download Log%></span></button>
        <button type="button" class="footer-btn" id="gen_debug_button" onclick="return gen_debug_logs()"><svg width="14" height="14"><use href="#oc-icon-play-bugs"/></svg><span><%:Generate Logs%></span></button>
    </div>
</div>
</body>

<script type="text/javascript">//<![CDATA[
window.levelTranslations = {
	'info': '<%:Info%>',
	'warning': '<%:Warning%>',
	'error': '<%:Error%>',
	'debug': '<%:Debug%>',
	'tip': '<%:Tip%>',
	'watchdog': '<%:Watchdog%>',
	'fatal': '<%:Fatal%>'
};
var r;
var s;
var log_len = 0;
var lv = document.getElementById('cbid.openclash.config.clog');
var cl = document.getElementById('core_log');
var dl = document.getElementById('debug_log');
var animatingOC = false;
var animatingCore = false;
var animatingDebug = false;
var coreWebSocket = null;
var coreLogBuffer = [];
var currentLogLevel = 'info';
var wsRetryDelay = 2000;
var wsRetryTimer = null;
var isPolling = false;
var core_refresh = true;
var wsHasData = false;
var coreEditorInitialized = false;
var editor_oc;
var editor_core;
var editor_debug;
var coreLogAccumulator = '';
var pollInterval = 1000;

function accumulateCoreLog(linesArr) {
    var reversed = linesArr.slice().reverse();
    var batchStr = '';
    for (var k = 0; k < reversed.length; k++) {
        batchStr += reversed[k] + '\n';
    }
    if (coreLogAccumulator) {
        var accFirstLineEnd = coreLogAccumulator.indexOf('\n');
        var accFirstLine = accFirstLineEnd > 0 ? coreLogAccumulator.substring(0, accFirstLineEnd) : coreLogAccumulator;
        var batchTrimmed = batchStr.slice(0, -1);
        var batchLastLineStart = batchTrimmed.lastIndexOf('\n');
        var batchLastLine = batchLastLineStart > 0 ? batchTrimmed.substring(batchLastLineStart + 1) : batchTrimmed;
        if (accFirstLine === batchLastLine) {
            batchStr = batchLastLineStart > 0 ? batchStr.substring(0, batchLastLineStart + 1) : '';
        }
    }
    coreLogAccumulator = batchStr + coreLogAccumulator;
    var accLines = coreLogAccumulator.split('\n');
    if (accLines.length > 2000) {
        accLines = accLines.slice(0, 1999);
        accLines.push('...');
        coreLogAccumulator = accLines.join('\n');
    }
    cl.value = coreLogAccumulator;
}

function setLogTab(log_level) {
    var radio = document.getElementsByName("radios");
    for (i=0; i<radio.length; i++) {
        if (radio[i].value == log_level && ! radio[i].checked) {
            radio[i].checked = true;
        }
    }
};

function get_log_level() {
    XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "log_level")%>', null, function(x, status) {
        if (x && x.status == 200 && status.log_level != "") {
            currentLogLevel = status.log_level;
            setLogTab(currentLogLevel);
            coreLogWebSocket();
        }
    });
};
    
function switch_log_level(value)
{
    if (!coreWebSocket || coreWebSocket.readyState !== WebSocket.OPEN) {
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_log")%>', {log_level: value}, function(x, status) {
            if (!x || x.status != 200) {
                alert(' <%:Log Level%>: ' + value + ' <%:switching failed!%>');
                return
            }
        });
    }

    currentLogLevel = value;
    setLogTab(currentLogLevel);
    coreLogWebSocket();
};

function stop_refresh() {
    clearTimeout(r);
    coreLogWebSocketStop();
    return
};

function start_refresh() {
    clearTimeout(r);
    coreLogWebSocket();
    r=setTimeout("poll_log()",1000);
    return
};

function createAndDownloadFile(fileName, content) {
    var aTag = document.createElement('a');
    var blob = new Blob([content]);
    aTag.download = fileName;
    aTag.href = URL.createObjectURL(blob);
    aTag.click();
    URL.revokeObjectURL(blob);
};

function download_log(){
    var dt = new Date();
    var timestamp = dt.getFullYear()+"-"+(dt.getMonth()+1)+"-"+dt.getDate()+"-"+dt.getHours()+"-"+dt.getMinutes()+"-"+dt.getSeconds();

    var activeTabId = 0;
    var titles = document.getElementsByName('tab-header');
    for(var i=0; i<titles.length; i++){
        if(titles[i].className === 'oc-tab') {
            activeTabId = i;
            break;
        }
    }

    var downloadContent = "";

    if (activeTabId === 2) {
        var debug_content = dl.value || '';
        if (!debug_content.trim()) return;
        downloadContent += debug_content;
        createAndDownloadFile("OpenClash-Debug-"+timestamp+".log", downloadContent);
        return;
    }

    var oc_content = "";
    if (typeof editor_oc !== 'undefined' && editor_oc) {
        oc_content = editor_oc.getValue();
    } else if (lv && lv.value) {
        oc_content = lv.value;
    } else if (lv && lv.innerHTML) {
        oc_content = lv.innerHTML;
    }

    oc_content = oc_content.split('\n').filter(function(line) { 
        return line.indexOf("】<%:Downloading URL%>【") === -1; 
    }).join('\n');

    var core_content = "";
    if (typeof editor_core !== 'undefined' && editor_core) {
        core_content = editor_core.getValue();
    } else if (cl && cl.value) {
        core_content = cl.value;
    } else if (cl && cl.innerHTML) {
        core_content = cl.innerHTML;
    }

    if (!oc_content.trim() && !core_content.trim()) {
        return;
    }

    if (oc_content.trim()) {
        downloadContent += "<%:Plugin Logs%>:\n" + oc_content + "\n\n";
    }
    if (core_content.trim()) {
        downloadContent += "<%:Core Logs%>:\n" + core_content;
    }

    createAndDownloadFile("OpenClash-"+timestamp+".log", downloadContent);
    return;
};

function restoreDebugEditor() {
    var wrapper = document.getElementById('debug-render-wrapper');
    var rendered = document.getElementById('debug-rendered');
    if (wrapper) { wrapper.style.display = 'none'; }
    if (rendered) { rendered.innerHTML = ''; }
    dl.value = "";
    if (typeof editor_debug !== 'undefined' && editor_debug) {
        editor_debug.setValue("");
        if (editor_debug.dom) {
            editor_debug.dom.style.setProperty('display', '', 'important');
        }
        editor_debug.refresh();
    }
}

function del_log() {
    var activeTabId = 0;
    var titles = document.getElementsByName('tab-header');
    for(var i=0; i<titles.length; i++){
        if(titles[i].className === 'oc-tab') {
            activeTabId = i;
            break;
        }
    }

    if (activeTabId === 2) {
        restoreDebugEditor();
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "del_log")%>', {type: "debug"}, function(x, data){
            if (typeof editor_debug === 'undefined' || !editor_debug) {
                if (typeof markdown_editor === 'function' && dl) {
                    markdown_editor(dl, 'debug', true, '100%', '540px');
                }
            }
        });
    } else {
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "del_log")%>', null, function(x, data){
            lv.value = "";
            cl.value = "";
            coreLogBuffer = [];
            coreLogAccumulator = '';
            log_len = 0;
            if (typeof editor_oc !== 'undefined' && editor_oc) {
                editor_oc.setValue(lv.value);
            }
            if (typeof editor_core !== 'undefined' && editor_core) {
                editor_core.setValue(cl.value);
            }
        });
    }
    return
};

function p(s) {
    return s < 10 ? '0' + s: s;
};

function getWebSocketConnectionInfo(callback) {
    XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "conn_status")%>', null, function(x, status) {
        if (x && x.status == 200 && status) {
            callback(status);
        } else {
            callback(null);
        }
    });
}

function coreLogWebSocketStop() {
    if (wsRetryTimer) {
        clearTimeout(wsRetryTimer);
        wsRetryTimer = null;
    }

    var ws = coreWebSocket;
    coreWebSocket = null;

    if (!ws) return;

    ["onopen", "onmessage", "onerror", "onclose"].forEach(function(event) {
        try {
            ws[event] = null;
        } catch (e) {}
    });

    try {
        if (ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING) {
            ws.close();
        }
    } catch (e) {}

    coreLogBuffer = [];
    wsHasData = false;
}

function coreLogWebSocketRetry() {
    wsRetryTimer = setTimeout(function() {
        coreLogWebSocket();
    }, wsRetryDelay);
}

function coreLogWebSocket() {
    coreLogWebSocketStop();

    getWebSocketConnectionInfo(function(status) {
        if (!status || !status.daip || !status.cn_port || !status.clash) {
            return;
        }

        var protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
        var host, port;

        if (status.daip && window.location.hostname === status.daip) {
            host = status.daip;
            port = status.cn_port;
        } else if (status.daip && window.location.hostname !== status.daip && 
                   status.db_foward_domain && status.db_foward_port) {
            host = status.db_foward_domain;
            port = status.db_foward_port;
        } else {
            host = status.daip;
            port = status.cn_port;
        }

        var token = status.dase;
        var level = currentLogLevel;
        var wsUrl = protocol + host + ":" + port + "/logs?token=" + token + "&level=" + level;

        try {
            coreWebSocket = new WebSocket(wsUrl);
            coreWebSocket.onopen = function() {
                coreLogBuffer = [];
                wsHasData = false;
            };

            coreWebSocket.onmessage = function(event) {
                if (event.data) {
                    try {
                        var msgData = JSON.parse(event.data);
                        if (msgData && msgData.payload) {
                            wsHasData = true;
                            var now = new Date();
                            var timestamp = now.getFullYear() + "-" + p(now.getMonth() + 1) + "-" + p(now.getDate()) + " " + 
                                           p(now.getHours()) + ":" + p(now.getMinutes()) + ":" + p(now.getSeconds());
                            var type = msgData.type || 'info';
                            var translatedType = levelTranslations[type] || type;
                            coreLogBuffer.push(timestamp + " [" + translatedType + "] " + msgData.payload);
                        }
                    } catch (e) {
                        coreLogWebSocketRetry();
                    }
                }
            };

            coreWebSocket.onerror = function(error) {
                coreLogWebSocketRetry();
            };

            coreWebSocket.onclose = function(event) {
                coreWebSocket = null;
                if (!event.wasClean) {
                    coreLogWebSocketRetry();
                }
            };
        } catch (e) {
            coreLogWebSocketRetry();
        }
    });
}

function gen_debug_logs()
{
    var btn = document.getElementById('gen_debug_button');
    var btnText = btn ? btn.querySelector('span') : null;
    var btnSvg = btn ? btn.querySelector('svg') : null;
    var originalText = btnText ? btnText.textContent : '';
    var originalSvg = btnSvg ? btnSvg.outerHTML : '';
    if (btn) {
        if (btnText) { btnText.textContent = '<%:Generating...%>'; }
        btn.disabled = true;
        if (btnSvg) {
            var spinner = document.createElement('span');
            spinner.className = 'loading-spinner oc-btn-spinner';
            btnSvg.replaceWith(spinner);
        }
    }

    function restoreGenerateBtn() {
        if (!btn) return;
        if (btnText) { btnText.textContent = originalText; }
        btn.disabled = false;
        var sp = btn.querySelector('.loading-spinner');
        if (sp) { sp.outerHTML = originalSvg; }
    }

    restoreDebugEditor();

    var titles = document.getElementsByName('tab-header');
    if (titles.length > 2 && titles[2].className !== 'oc-tab') {
        titles[2].click();
    }

    if (typeof editor_debug === 'undefined' || !editor_debug) {
        if (typeof markdown_editor === 'function' && dl) {
            markdown_editor(dl, 'debug', true, '100%', '540px');
        }
    }

    var xhr = new XMLHttpRequest();
    xhr.timeout = 0;
    xhr.open('GET', '<%=luci.dispatcher.build_url("admin", "services", "openclash", "gen_debug_logs")%>', true);
    xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

    var lastLen = 0;
    xhr.onprogress = function() {
        var text = xhr.responseText;
        if (text.length <= lastLen) return;
        lastLen = text.length;
        var lines = text.split('\n');
        lines.pop();
        if (lines.length > 0 && lines[0].length === 8192 && /^ +$/.test(lines[0])) {
            lines.shift();
        }
        var preview = lines.join('\n');
        if (editor_debug && editor_debug.setValue) {
            editor_debug.setValue(preview);
            if (editor_debug.scrollDOM) editor_debug.scrollDOM.scrollTop = editor_debug.scrollDOM.scrollHeight;
        } else {
            dl.value = preview;
        }
    };

    xhr.onload = function() {
        restoreGenerateBtn();
        var finalText = xhr.responseText || '';

        if (xhr.status != 200 && finalText == "") {
            if (editor_debug && editor_debug.setValue) {
                editor_debug.setValue('<%:Some error occurred!%>');
            } else {
                dl.value = '<%:Some error occurred!%>';
            }
            return;
        }

        // reload after sed, because log changed
        load_debug_log();
    };

    xhr.ontimeout = function() {
        restoreGenerateBtn();
        if (xhr.responseText == "") {
            if (editor_debug && editor_debug.setValue) {
                editor_debug.setValue('<%:Request Timeout...%>');
            } else {
                dl.value = '<%:Request Timeout...%>';
            }
        }
    };

    xhr.onerror = function() {
        restoreGenerateBtn();
        if (xhr.responseText == "") {
            if (editor_debug && editor_debug.setValue) {
                editor_debug.setValue('<%:Some error occurred!%>');
            } else {
                dl.value = '<%:Some error occurred!%>';
            }
        }
    };

    xhr.send();
    return;
};

function load_debug_log()
{
    XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "get_debug_logs")%>', null, function(x, status) {
        if (x && x.status == 200 && x.responseText != "")
        {
            dl.value = x.responseText;
            if (typeof editor_debug !== 'undefined' && editor_debug && editor_debug.setValue) {
                editor_debug.setValue(x.responseText);
                setCursorToFirstLineEnd(editor_debug);
            }
            renderDebugView();
        }
        else
        {
            if (typeof markdown_editor === 'function' && dl) {
                markdown_editor(dl, 'debug', true, '100%', '540px');
            }
        }
    });
};

function line_tolocal(input) {
    var trans_local = new Array();
    var local_count = 0;
    var lines;

    if (Array.isArray(input)) {
        lines = input;
    } else {
        lines = input.trim().split('\n');
    }

    for (var i = 0; i < lines.length; i++) {
        var v = lines[i];
        if (!v) continue;

        try {
            if (v.indexOf('time=') >= 0) {
                var regex = /(time=)"([^"]*)"/g;
                var res = regex.exec(v);

                if (res) {
                    var dt = new Date(res[2]);

                    if (!isNaN(dt.getTime())) {
                        var timestamp = dt.getFullYear() + "-" + p(dt.getMonth() + 1) + "-" + p(dt.getDate()) + " " + 
                                       p(dt.getHours()) + ":" + p(dt.getMinutes()) + ":" + p(dt.getSeconds());
                        var levelMatch = v.match(/level=(\w+)/);
                        var level = levelMatch ? levelMatch[1] : 'info';
                        var translatedLevel = levelTranslations[level] || level;
                        var msgMatch = v.match(/msg="(.*)"/);
                        var msg = msgMatch ? msgMatch[1] : '';
                        trans_local[local_count] = timestamp + " [" + translatedLevel + "] " + msg;
                        local_count++;
                        continue;
                    }
                }
            }

            try {
                var dtt = new Date(v.substring(0, 19));

                if (!isNaN(dtt.getTime()) && v.substring(0, 19).match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/)) {
                    trans_local[local_count] = v;
                    local_count++;
                } else {
                    trans_local[local_count] = v;
                    local_count++;
                }
            } catch (e) {
                trans_local[local_count] = v;
                local_count++;
            }
        } catch (e) {
            trans_local[local_count] = v;
            local_count++;
        }
    }

    return trans_local;
};

function setCursorToFirstLineEnd(editor) {
    if (!editor || !editor.state) return;
    var doc = editor.state.doc;
    if (doc.lines >= 1) {
        var fl = doc.line(1);
        editor.dispatch({ selection: { anchor: fl.to }, scrollIntoView: true });
    }
}

function smoothlyDisplayLogs(newLines, target, isEditor, currentContent, isActiveTab) {
    var scrollPosition = null;
    var isAtTop = false;
    var cursorPos = null;
    var selectionRange = null;

    if (isEditor && target) {
        scrollPosition = target.getScrollInfo();
        isAtTop = (scrollPosition.top < 20);

        if (target.hasFocus()) {
            cursorPos = target.getCursor();

            if (cursorPos.line === 0) {
                cursorPos = null;
                selectionRange = null;
            } else if (target.somethingSelected()) {
                selectionRange = {
                    from: target.getCursor(true),
                    to: target.getCursor(false)
                };
            } else {
                selectionRange = null;
            }
        } else {
            cursorPos = null;
            selectionRange = null;
        }
    } else if (!isEditor && target) {
        scrollPosition = target.scrollTop;
        isAtTop = (target.scrollTop < 20);
    }

    if ((target === editor_oc && animatingOC) || (target === editor_core && animatingCore) || (target === editor_debug && animatingDebug) || !isActiveTab) {
        var lines = newLines.slice().reverse();
        var content = lines.join('\n') + (lines.length > 0 ? '\n' : '') + (currentContent || '');
        var allLines = content.split("\n");
        if (allLines.length > 2000) {
            allLines = allLines.slice(0, 1999);
            allLines.push("...");
            content = allLines.join("\n");
        }

        if (isEditor) {
            var addedLines = lines.length;
            target.setValue(content);

            if (!isAtTop && scrollPosition) {
                if (cursorPos) {
                    cursorPos.line += addedLines;
                    target.setCursor(cursorPos);
                    target.scrollIntoView({line: cursorPos.line, ch: cursorPos.ch}, 300);
                } else {
                    target.scrollTo(scrollPosition.left, scrollPosition.top);
                }

                if (selectionRange) {
                    selectionRange.from.line += addedLines;
                    selectionRange.to.line += addedLines;
                    target.setSelection(selectionRange.from, selectionRange.to);
                    target.scrollIntoView({
                        from: selectionRange.from,
                        to: selectionRange.to
                    }, 300);
                }
            } else if (isAtTop) {
                target.scrollTo(0, 0);
                if (!cursorPos) {
                    var firstLineLen = target.state.doc.line(1).length;
                    target.setCursor({line: 0, ch: firstLineLen});
                }
            }
        } else {
            var oldScrollTop = scrollPosition;
            target.innerHTML = content;
            if (!isAtTop && oldScrollTop) {
                target.scrollTop = oldScrollTop;
            }
        }
        return;
    }

    if (target === editor_oc || target === lv) {
        animatingOC = true;
    } else if (target === editor_debug) {
        animatingDebug = true;
    } else {
        animatingCore = true;
    }

    var totalLines = newLines.length;
    var batchSize, interval;

    if (totalLines <= 10) {
        batchSize = 5;
        interval = 80;
    } else if (totalLines <= 50) {
        batchSize = 25;
        interval = 50;
    } else if (totalLines <= 200) {
        batchSize = 60;
        interval = 30;
    } else if (totalLines <= 500) {
        batchSize = 150;
        interval = 20;
    } else if (totalLines <= 1000) {
        batchSize = 300;
        interval = 12;
    } else if (totalLines <= 1500) {
        batchSize = 400;
        interval = 8;
    } else {
        batchSize = 500;
        interval = 5;
    }

    var displayedContent = currentContent || "";
    var logLines = newLines.slice();
    var currentBatchCount = 0;
    var accumulatedContent = "";

    function displayNextBatch() {
        if (currentBatchCount >= logLines.length) {
            if (target === editor_oc || target === lv) {
                animatingOC = false;
            } else if (target === editor_debug) {
                animatingDebug = false;
            } else {
                animatingCore = false;
            }

            if (isEditor && !isAtTop && cursorPos) {
                cursorPos.line += logLines.length;
                target.setCursor(cursorPos);

                if (selectionRange) {
                    selectionRange.from.line += logLines.length;
                    selectionRange.to.line += logLines.length;
                    target.setSelection(selectionRange.from, selectionRange.to);

                    target.scrollIntoView({
                        from: selectionRange.from,
                        to: selectionRange.to
                    }, 300);
                } else {
                    target.scrollIntoView({line: cursorPos.line, ch: cursorPos.ch}, 300);
                }
            }
            if (isEditor && !cursorPos) {
                setCursorToFirstLineEnd(target);
            }
            return;
        }

        var nextBatchSize = Math.min(batchSize, logLines.length - currentBatchCount);

        var batchLines = logLines.slice(currentBatchCount, currentBatchCount + nextBatchSize).reverse();
        currentBatchCount += nextBatchSize;

        if (accumulatedContent) {
            accumulatedContent = batchLines.join("\n") + "\n" + accumulatedContent;
        } else {
            accumulatedContent = batchLines.join("\n");
        }

        var content = accumulatedContent + (displayedContent ? "\n" + displayedContent : "");

        if (isEditor) {
            var currentScrollInfo = isAtTop ? null : target.getScrollInfo();
            target.setValue(content);

            if (!isAtTop && currentScrollInfo) {
                target.scrollTo(currentScrollInfo.left, currentScrollInfo.top);
            }
        } else {
            var currentScrollTop = isAtTop ? null : target.scrollTop;
            target.innerHTML = content;
            if (!isAtTop && currentScrollTop !== null) {
                target.scrollTop = currentScrollTop;
            }
        }

        if (currentBatchCount < logLines.length) {
            setTimeout(displayNextBatch, interval);
        } else {
            if (target === editor_oc || target === lv) {
                animatingOC = false;
            } else if (target === editor_debug) {
                animatingDebug = false;
            } else {
                animatingCore = false;
            }

            if (isEditor && !isAtTop && cursorPos) {
                cursorPos.line += logLines.length;
                target.setCursor(cursorPos);

                if (selectionRange) {
                    selectionRange.from.line += logLines.length;
                    selectionRange.to.line += logLines.length;
                    target.setSelection(selectionRange.from, selectionRange.to);

                    target.scrollIntoView({
                        from: selectionRange.from,
                        to: selectionRange.to
                    }, 300);
                } else {
                    target.scrollIntoView({line: cursorPos.line, ch: cursorPos.ch}, 300);
                }
            }
            if (isEditor && !cursorPos) {
                setCursorToFirstLineEnd(target);
            }

            if (isEditor) {
                var finalContent = target.getValue();
                var finalLines = finalContent.split("\n");
                if (finalLines.length > 2000) {
                    finalLines = finalLines.slice(0, 1999);
                    finalLines.push("...");
                    target.setValue(finalLines.join("\n"));
                    if (!cursorPos) {
                        setCursorToFirstLineEnd(target);
                    }
                }
            }
        }
    }

    displayNextBatch();
}

function poll_log(){
    var activeTabId = 0;
    var titles = document.getElementsByName('tab-header');
    var core_logs = [];
    var oc_logs = [];
    var currentCoreContent = "";
    var currentContent = "";
    var bufferToProcess = [];

    clearTimeout(r);

    for(var i=0; i<titles.length; i++){
        if(titles[i].className === 'oc-tab') {
            activeTabId = i;
            break;
        }
    }

    if (coreWebSocket && coreWebSocket.readyState == WebSocket.OPEN) {
        core_refresh = !wsHasData;
        if (coreLogBuffer.length > 0) {
            bufferToProcess = coreLogBuffer;
            coreLogBuffer = [];
            core_logs = line_tolocal(bufferToProcess);
            if (editor_core && activeTabId === 1) {
                currentCoreContent = editor_core.getValue();
                smoothlyDisplayLogs(core_logs, editor_core, true, currentCoreContent, true);
            } else {
                accumulateCoreLog(core_logs);
            }
        }
    } else {
        core_refresh = true;
    }

    if (isPolling) {
        r = setTimeout("poll_log()", pollInterval);
        return;
    }

    if (activeTabId === 2) {
        return;
    }
    
    isPolling = true;

    XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "refresh_log")%>', 
        { 
            log_len: log_len, core_refresh: core_refresh
        },
        function(x, status) {
            if (x && x.status == 200) {
                if (status) {
                    if (!status.update) {
                        isPolling = false;
                        r = setTimeout("poll_log()", pollInterval);
                        return;
                    }

                    if (status.len) {
                        log_len = status.len;
                    }

                    if (status.oc_log && status.oc_log !== "") {
                        oc_logs = line_tolocal(status.oc_log);

                        if (oc_logs.length > 0) {
                            if (editor_oc) {
                                currentContent = editor_oc.getValue();
                                smoothlyDisplayLogs(oc_logs, editor_oc, true, currentContent, activeTabId === 0);
                            } else if (lv) {
                                currentContent = lv.innerHTML;
                                smoothlyDisplayLogs(oc_logs, lv, false, currentContent, activeTabId === 0);
                            }
                        }
                    }

                    if (status.core_log && status.core_log !== "") {
                        core_logs = line_tolocal(status.core_log);

                        if (core_logs.length > 0) {
                            if (editor_core && activeTabId === 1) {
                                currentCoreContent = editor_core.getValue();
                                smoothlyDisplayLogs(core_logs, editor_core, true, currentCoreContent, true);
                            } else {
                                accumulateCoreLog(core_logs);
                            }
                        }
                    }
                }
            }
            isPolling = false;
            r = setTimeout("poll_log()", pollInterval);
        }
    );
};

window.onload = function(){
    var titles = document.getElementsByName('tab-header');
    var divs = document.getElementsByClassName('dom');
    if(titles.length != divs.length) return;

    var tc = document.getElementById('tab-content');
    if (tc) tc.style.position = 'relative';

    divs[0].classList.add('active');

    for(var i=0; i<titles.length; i++){
        var li = titles[i];
        li.id = i;
        function handleTabSwitch(tab) {
            return function(e) {
                if (tab.className === 'oc-tab') return;
                var prevTabId = -1;
                for(var j=0; j<titles.length; j++){
                    if (titles[j].className === 'oc-tab') prevTabId = j;
                    titles[j].className = 'oc-tab-disabled';
                    divs[j].classList.remove('active');
                }
                tab.className = 'oc-tab';
                divs[tab.id].classList.add('active');

                if (prevTabId == 1 && editor_core) {
                    coreLogAccumulator = editor_core.getValue();
                    cl.value = coreLogAccumulator;
                }

                if (tab.id == 0 && typeof editor_oc !== 'undefined' && editor_oc) {
                    setCursorToFirstLineEnd(editor_oc);
                }

                if (tab.id == 1 && !coreEditorInitialized) {
                    coreEditorInitialized = true;
                    if (typeof log_editor === 'function' && cl) {
                        cl.value = coreLogAccumulator.replace(/\n+$/, '');
                        log_editor(cl, 'core', true, '100%', '540px');
                    }
                } else if (tab.id == 1 && editor_core) {
                    editor_core.setValue(coreLogAccumulator.replace(/\n+$/, ''));
                    editor_core.refresh();
                    setCursorToFirstLineEnd(editor_core);
                }

                if (tab.id == 2 && typeof editor_debug !== 'undefined' && editor_debug) {
                    setCursorToFirstLineEnd(editor_debug);
                }

                pollInterval = (tab.id == 1) ? 5000 : 1000;
                clearTimeout(r);
                poll_log();
            };
        }

        li.onclick = handleTabSwitch(li);
        li.ontouchstart = handleTabSwitch(li);
    }
    get_log_level();
    poll_log();
    setTimeout(function() {
        load_debug_log();
    }, 500);
};

function renderDebugView(src) {
    var wrapper = document.getElementById('debug-render-wrapper');
    var rendered = document.getElementById('debug-rendered');
    if (!wrapper || !rendered) return;
    if (!src) {
        if (typeof editor_debug !== 'undefined' && editor_debug) {
            src = editor_debug.getValue();
        } else if (dl) {
            src = dl.value || '';
        }
    }
    if (!src) return;

    if (typeof editor_debug !== 'undefined' && editor_debug && editor_debug.dom) {
        editor_debug.dom.style.setProperty('display', 'none', 'important');
    }
    var cms = dl.parentNode.querySelectorAll('.cm-editor');
    for (var i = 0; i < cms.length; i++) {
        cms[i].style.setProperty('display', 'none', 'important');
    }

    if (typeof CM6 !== 'undefined' && CM6.renderMarkdown) {
        rendered.innerHTML = CM6.renderMarkdown(src);
    } else {
        rendered.innerHTML = src.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>');
    }

    var pres = rendered.querySelectorAll('pre');
    var copyIcon = '<svg width="14" height="14"><use href="#oc-icon-copy"/></svg>';
    for (var i = 0; i < pres.length; i++) {
        (function(pre) {
            if (pre.parentNode && pre.parentNode.classList.contains('pre-copy-wrap')) return;
            var btn = document.createElement('div');
            btn.className = 'pre-copy-btn';
            btn.title = '<%:Copy%>';
            btn.innerHTML = copyIcon;
            btn.onclick = function(e) {
                e.stopPropagation();
                var text = pre.textContent || '';
                ocCopyToClipboard(text, btn);
            };
            var wrap = document.createElement('div');
            wrap.className = 'pre-copy-wrap';
            pre.parentNode.insertBefore(wrap, pre);
            wrap.appendChild(pre);
            wrap.appendChild(btn);
        })(pres[i]);
    }

    wrapper.style.display = 'block';
    dl.style.display = 'none';
    if (dl.parentNode && dl.parentNode.className.indexOf('oc') < 0) {
        dl.parentNode.classList.add('oc');
    }
}

function copyDebugSource() {
    var src = '';
    if (typeof editor_debug !== 'undefined' && editor_debug) {
        src = editor_debug.getValue();
    } else if (dl) {
        src = dl.value || '';
    }
    if (!src) return;
    ocCopyToClipboard(src, document.getElementById('debug-copy-source'));
}

//]]>
</script>
<%+cbi/valuefooter%>
