import {basicSetup, EditorView} from "codemirror"
import {autocompletion, snippetCompletion} from "@codemirror/autocomplete"
import {javascript} from "@codemirror/lang-javascript";
import {syntaxHighlighting} from "@codemirror/language";
import {Compartment} from "@codemirror/state";
import {keymap} from "@codemirror/view";
import {insertTab} from "@codemirror/commands";
import {basicLightTheme, basicLightHighlightStyle} from "./cm6-themes/packages/basic-light/src";
import {basicDarkTheme, basicDarkHighlightStyle} from "./cm6-themes/packages/basic-dark/src";
import {gruvboxDarkTheme, gruvboxDarkHighlightStyle} from "./cm6-themes/packages/gruvbox-dark/src";
import {gruvboxLightTheme, gruvboxLightHighlightStyle} from "./cm6-themes/packages/gruvbox-light/src";
import {materialDarkTheme, materialDarkHighlightStyle} from "./cm6-themes/packages/material-dark/src";
import {nordTheme, nordHighlightStyle} from "./cm6-themes/packages/nord/src";
import {solarizedDarkTheme, solarizedDarkHighlightStyle} from "./cm6-themes/packages/solarized-dark/src";
import {solarizedLightTheme, solarizedLightHighlightStyle} from "./cm6-themes/packages/solarized-light/src";
import {languagesStrings, userLanguage} from "./language.js";
import {closeChangeFontSizeDialog, closeChangeThemeDialog} from "./dialogs.js";
import {closeMenu} from "./navigation.js";

export let editor;

const themeConfig = new Compartment();
const syntaxHighlightConfig = new Compartment();
const autocompleteConfig = new Compartment();

const themeMap = new Map([
    ["basic-lightTheme", basicLightTheme],
    ["basic-darkTheme", basicDarkTheme],
    ["gruvbox-lightTheme", gruvboxLightTheme],
    ["gruvbox-darkTheme", gruvboxDarkTheme],
    ["material-darkTheme", materialDarkTheme],
    ["nordTheme", nordTheme],
    ["solarized-darkTheme", solarizedDarkTheme],
    ["solarized-lightTheme", solarizedLightTheme],
]);

const highlightStyleMap = new Map([
    ["basic-lightHighlightStyle", basicLightHighlightStyle],
    ["basic-darkHighlightStyle", basicDarkHighlightStyle],
    ["gruvbox-darkHighlightStyle", gruvboxDarkHighlightStyle],
    ["gruvbox-lightHighlightStyle", gruvboxLightHighlightStyle],
    ["material-darkHighlightStyle", materialDarkHighlightStyle],
    ["nordHighlightStyle", nordHighlightStyle],
    ["solarized-darkHighlightStyle", solarizedDarkHighlightStyle],
    ["solarized-lightHighlightStyle", solarizedLightHighlightStyle],
]);

const completions = [
    {label: "vor();", type: "function"},
    {label: "move();", type: "function"},
    {label: "linksUm();", type: "function"},
    {label: "turnLeft();", type: "function"},
    {label: "pickGrain();", type: "function"},
    {label: "putGrain();", type: "function"},
    {label: "nimm();", type: "function"},
    {label: "gib();", type: "function"},
    {label: "vornFrei();", type: "function"},
    {label: "frontIsClear();", type: "function"},
    {label: "kornDa();", type: "function"},
    {label: "grainAvailable();", type: "function"},
    {label: "maulLeer();", type: "function"},
    {label: "mouthEmpty();", type: "function"},
    {label: "hamster", type: "variable"},
]

function myCompletions(context) {
    let before = context.matchBefore(/\w+/);
    // If completion wasn't explicitly started and there
    // is no word before the cursor, don't open completions.
    if (!context.explicit && !before) {
        return null;
    } 
        
    return {
        from: before ? before.from : context.pos,
        options: completions,
    };
}

/**
 * Erstellt die Textstücke, für die die automatische Textvervollständigung funktionieren soll.
 * 
 * @param context, Inhalt des Texteditors.
 * @return {null|{options: *[], from}}, die erstellten Textvervollständigungen.
 */
function setUpSnippets(context) {
    const matchBefore = context.matchBefore(/\w*/);

    if (!context.explicit && !matchBefore) {
        return null;
    }

    return {
        from: matchBefore.from,
        options: [
            snippetCompletion('for (let i=${index}; i<${x}; i${++}) {\n\t${function();}\n}', {
                label: 'for (let i=...){}', type: "keyword",
            }),
            snippetCompletion('while (${expr}) {\n\t${function();}\n}', {
                label: 'while (...){}', type: "keyword",
            }),
            snippetCompletion('if (${expr}) {\n\t${function();}\n}', {
                label: 'if (...){}', type: "keyword",
            }),
            snippetCompletion('if (${expr}) {\n\t${function1();}\n} else {\n\t${function2();}\n}', {
                label: 'if (...){} else{}', type: "keyword",
            }),
            snippetCompletion('console.log(${function()});', {
                label: 'console.log(...)', type: 'function',
            }),
            snippetCompletion('liesZahl("${text}");', {
                label: 'liesZahl(...)', type: 'function',
            }),
            snippetCompletion('readNumber("${text}");', {
                label: 'readNumber(...)', type: 'function',
            }),
            snippetCompletion('liesZeichenkette("${text}");', {
                label: 'liesZeichenkette(...)', type: 'function',
            }),
            snippetCompletion('readString("${text}");', {
                label: 'readString(...)', type: 'function',
            }),
            snippetCompletion('schreib("${text}");', {
                label: 'schreib(...)', type: 'function',
            }),
            snippetCompletion('write("${text}");', {
                label: 'write(...)', type: 'function',
            }),
            snippetCompletion('new Hamster(${parameter});', {
                label: 'new Hamster(...)', type: 'function',
            }),
        ],
    };
}

/**
 * Deaktiviert die automatische Textvervollständigung für den Editor.
 */
export function disableAutoCompletion() {
    editor.dispatch({
        effects: autocompleteConfig.reconfigure([
            autocompletion({
                override:[]
            })
        ])
    });
}

/**
 * Aktiviert die automatische Textvervollständigung für den Editor.
 */
export function enableAutoCompletion() {
    editor.dispatch({
        effects: autocompleteConfig.reconfigure([
            autocompletion({
                override:[
                    myCompletions,
                    (context) => {
                        return setUpSnippets(context);
                    }
                ]
            })
        ])
    });
}

/**
 * Erstellt, sobald der Inhalt des Dokuments geladen wurde eine Codemirror 6 EditorView und bettet diese
 * in das Editor-Wrapper-Element ein.
 */
document.addEventListener("DOMContentLoaded", function () {
    editor = new EditorView({
        doc: languagesStrings[userLanguage]['textEditor'],
        parent: document.getElementById('editor-wrapper'),
        extensions: [
            basicSetup,
            javascript(),
            autocompleteConfig.of([autocompletion({
                override: []
            })]),
            keymap.of([{key: "Tab", preventDefault: true, run: insertTab}]),
            themeConfig.of([themeMap.get('basic-darkTheme')]),
            syntaxHighlightConfig.of([syntaxHighlighting(basicDarkHighlightStyle)]),
        ],
    });

    // Schalte die automatische Textvervollständigung ein/aus je nachdem,
    // ob die Checkbox in der index.html un-/checked ist.
    if (document.getElementById('auto-completion-checkbox').checked) {
        enableAutoCompletion();
    } else {
        disableAutoCompletion();
    }
})

/**
 * Ändert die Textgröße des Texteditors.
 * 
 * @param event, das auslösende Event.
 */
export function changeFontSize(event) {
    event.preventDefault();

    let editorWrapper = document.getElementById('editor-wrapper');
    let newSize = document.getElementById('font-size-input').value;
    editorWrapper.style.fontSize = newSize+"px";

    // Fügt ein Leerzeichen am Anfang des Editors ein und löscht es wieder.
    // Workaround, damit sich die Schriftgröße des Gutters ebenfalls anpasst.
    editor.dispatch({
        changes: {
            from: 0,
            to: 0,
            insert: " "
        },
    });
    editor.dispatch({
        changes: {
            from: 0,
            to: 1,
            insert: ""
        },
    });
    
    closeChangeFontSizeDialog();
    closeMenu(event);
}

/**
 * Ändert das Theme bzw. Design des Texteditors.
 * 
 * @param event, das auslösende Event.
 */
export function changeTheme(event) {
    event.preventDefault();

    let themeName = document.querySelector('input[name="theme"]:checked').value;
    let themeString = themeName+"Theme";
    let highlightString = themeName+"HighlightStyle";
    let theme;
    let highlightStyle;
    
    if (themeMap.has(themeString) && highlightStyleMap.has(highlightString)) {
        theme = themeMap.get(themeString);
        highlightStyle = highlightStyleMap.get(highlightString);
        editor.dispatch({effects: themeConfig.reconfigure(theme)});
        editor.dispatch({effects: syntaxHighlightConfig.reconfigure(syntaxHighlighting(highlightStyle))});
    } else {
        editor.dispatch({effects: themeConfig.reconfigure([])});
        editor.dispatch({effects: syntaxHighlightConfig.reconfigure([])});
    }
    
    closeChangeThemeDialog();
    closeMenu(event);
}

/**
 * Ersetzt den Inhalt des Texteditors.
 * 
 * @param content, neuer Inhalt, der angezeigt werden soll.
 */
export function replaceContent(content) {
    editor.dispatch({
        changes: {
            from: 0,
            to: editor.state.doc.length,
            insert: content
        }
    });
}

/**
 * Ersetzt den Starttext des Texteditor.
 */
export function replaceStartText() {
    let content = editor.state.doc.toString();

    if (userLanguage === 'de') {
        content = content.replace(languagesStrings['en']['textEditor'], languagesStrings['de']['textEditor']);
    } else {
        content = content.replace(languagesStrings['de']['textEditor'], languagesStrings['en']['textEditor']);
    }

    replaceContent(content);
}