<!DOCTYPE html>
<html>
<head>
    <title>JSFormatter Tool</title>
    <link rel="stylesheet" href="../../UserInterface/External/CodeMirror/codemirror.css">
    <link rel="stylesheet" href="codemirror-additions.css">
    <script src="../../UserInterface/External/CodeMirror/codemirror.js"></script>
    <script src="../../UserInterface/External/CodeMirror/javascript.js"></script>

    <script src="../../UserInterface/External/Esprima/esprima.js"></script>
    <script src="../../UserInterface/Workers/Formatter/FormatterContentBuilder.js"></script>
    <script src="../../UserInterface/Workers/Formatter/FormatterUtilities.js"></script>
    <script src="../../UserInterface/Workers/Formatter/ESTreeWalker.js"></script>
    <script src="../../UserInterface/Workers/Formatter/JSFormatter.js"></script>
    <script src="JSFormatterDebug.js"></script>
</head>
<body>
    <h1>Debug JSFormatter</h1>

    <!-- Controls -->
    <button id="populate">Populate</button>
    <select id="load-individual-test"><option>-- Load Test --</option></select>
    <small>
        <label><input id="program" type="radio" name="program-or-module"> Program</label>
        <label><input id="module" type="radio" name="program-or-module" checked> Module</label>
    </small>
    <button id="run-tests">Run All Tests</button>
    <button id="clear">Clear</button>
    <button id="select-output">Select Output</button>
    <button id="run-again">Run Again</button>
    <button id="save-as-url">Save URL</button>
    <small id="time"></small>
    <br><br>

    <!-- Editor -->
    <textarea id="code" name="code"></textarea>

    <!-- Output -->
    <pre id="pretty"></pre>
    <pre id="debug"></pre>

    <script>
    const testURLPrefix = "../../../../LayoutTests/inspector/formatting/resources/javascript-tests/";

    let tests = [
        "if-statement.js",
        "import.js",
        "for-statements.js",
        "while-statement.js",
        "do-while-statement.js",
        "try-catch-finally-statements.js",
        "switch-case-default.js",
        "object-array-literal.js",
        "unary-binary-expressions.js",
        "logic-expressions.js",
        "ternary-expressions.js",
        "new-expression.js",
        "numbers.js",
        "label-break-continue-block.js",
        "with-statement.js",
        "return-statement.js",
        "other-statements.js",
        "variable-declaration.js",
        "functions.js",
        "classes.js",
        "arrow-functions.js",
        "generators.js",
        "comments-and-preserve-newlines.js",
        "comments-only.js",
        "template-strings.js",
        "optional-chaining.js",
        "private-classes.js",
        "sample-webinspector-object.js",
        "sample-normal-utilities.js",
        "sample-jquery.js",
        "modules.js",
    ];

    // Initial values from URL.
    let queryParams = {};
    if (window.location.search.length > 0) {
        let searchString = window.location.search.substring(1);
        let groups = searchString.split("&");
        for (let i = 0; i < groups.length; ++i) {
            let pair = groups[i].split("=");
            queryParams[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
        }
    }

    // Initial content.
    let content = "(function(){let a=1;return a+1;})();";
    if (queryParams.content)
        content = queryParams.content || "";
    if (queryParams.program)
        document.getElementById("program").checked = true;

    // Setup CodeMirror.
    let cm = CodeMirror.fromTextArea(document.getElementById("code"), {lineNumbers: true});
    cm.setValue(content);
    cm.setOption("mode", "text/javascript");

    // Populate button to populate with some canned content.
    document.getElementById("populate").addEventListener("click", function(event) {
        loadIndividualTest("sample-jquery.js");
    });

    // Fill the individual tests button.
    let testSelectElement = document.getElementById("load-individual-test");
    testSelectElement.addEventListener("change", function(event) {
        let selectedOption = testSelectElement.selectedOptions[0];
        let test = selectedOption.value;
        loadIndividualTest(test);
        testSelectElement.selectedIndex = 0;
    });

    // Load up the known tests.
    for (let test of tests) {
        let option = testSelectElement.appendChild(document.createElement("option"));
        option.textContent = test;
        option.value = test;
    }

    // Program / Module checkboxes.
    let programCheckbox = document.getElementById("program");
    let moduleCheckbox = document.getElementById("module");
    programCheckbox.addEventListener("change", refresh);
    moduleCheckbox.addEventListener("change", refresh);

    // Run Tests button.
    document.getElementById("run-tests").addEventListener("click", function(event) {
        cm.setValue("/* Running Tests... */");
        refresh();
        runAllTests();
    });

    // Clear button.
    document.getElementById("clear").addEventListener("click", function(event) {
        cm.setValue("");
        refresh();
    });

    // Select output button.
    document.getElementById("select-output").addEventListener("click", function(event) {
        let range = document.createRange();
        range.selectNodeContents(document.getElementById("pretty"));
        let selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(range);
    });

    // Run again button.
    document.getElementById("run-again").addEventListener("click", function(event) {
        refresh();
    });

    // Save as URL button.
    document.getElementById("save-as-url").addEventListener("click", function(event) {
        let content = cm.getValue();
        let queryString = "?content=" + window.encodeURIComponent(content);
        if (programCheckbox.checked)
            queryString += "&program";
        window.location.search = queryString;
    });

    // Refresh after changes after a short delay.
    let timer = null;
    cm.on("change", function(codeMirror, change) {
        if (timer)
            clearTimeout(timer)
        timer = setTimeout(function() {
            clearTimeout(timer);
            timer = null;
            refresh();
        }, 500);
    });

    // Output elements.
    let timeOutput = document.getElementById("time");
    let prettyPre = document.getElementById("pretty");
    let debugPre = document.getElementById("debug");

    // Current value of checkboxes.
    function currentSourceType() {
        if (programCheckbox.checked)
            return JSFormatter.SourceType.Script;
        if (moduleCheckbox.checked)
            return JSFormatter.SourceType.Module;
        console.assert(false, "Program or Module radio button should be checked");
        return undefined;
    }

    function refresh() {
        if (timer)
            clearTimeout(timer);

        // Time the formatter.
        let startTime = Date.now();
        let formatter = new JSFormatter(cm.getValue(), currentSourceType());
        let endTime = Date.now();

        // Show debug info.
        let debugText;
        try {
            let debugFormatter = new JSFormatterDebug(cm.getValue(), currentSourceType());
            debugText = debugFormatter.debugText;
        } catch (error) {
            debugText = "Parse error: " + JSON.stringify(error, null, 2);
        }

        // Output the results.
        timeOutput.innerText = (endTime - startTime) + "ms";
        prettyPre.innerText = formatter.formattedText;
        debugPre.innerText = debugText;
    }

    setTimeout(refresh);

    // Testing.

    function isModuleTest(test) {
        return test === "modules.js";
    }

    function loadIndividualTest(test) {
        let testURL = testURLPrefix + test;
        let xhr = new XMLHttpRequest;
        xhr.open("GET", testURL, true);
        xhr.onload = () => { cm.setValue(xhr.responseText); setTimeout(refresh); }
        xhr.send();

        if (isModuleTest(test))
            moduleCheckbox.checked = true;
        else
            programCheckbox.checked = true;
    }

    function runAllTests() {
        let index = -1;
        let results = [];
        setTimeout(runNextTest, 0);

        function runNextTest() {
            // Next test.
            index++;

            // Done.
            if (index >= tests.length) {
                printResults();
                return;
            }

            // Load test and expected results.
            let test = tests[index];
            let testURL = testURLPrefix + test;
            let expectedURL = testURL.replace(/\.([^\.]+)$/, "-expected.$1");
            let xhr1 = new XMLHttpRequest;
            xhr1.open("GET", testURL, false);
            xhr1.send();
            let xhr2 = new XMLHttpRequest;
            xhr2.open("GET", expectedURL, false);
            xhr2.send();
            let testData = xhr1.responseText;
            let expectedData = xhr2.responseText;

            // Run the test.
            let sourceType = isModuleTest(test) ? JSFormatter.SourceType.Module : JSFormatter.SourceType.Script;
            let formatter = new JSFormatter(testData, sourceType);

            // Compare results.
            let pass = formatter.formattedText === expectedData;
            results.push("/* " + (pass ? "PASS" : "!! FAIL") + ": " + test + " */");

            // Output failures to console.
            if (!pass) {
                console.log("Test", test);
                console.log("Formatted Output", formatter.formattedText ? formatter.formattedText.length : null);
                console.log(formatter.formattedText);
                console.log("Expected Output", expectedData.length);
                console.log(expectedData);
            }

            runNextTest();
        }

        function printResults() {
            cm.setValue(results.join("\n"));
            cm.refresh();
        }
    }
    </script>
</body>
</html>
