// ck-find-result and ck-find-result_selected are the styles ck-editor
// uses for highlighting matches, use the same one on CodeMirror
// for consistency
import utils from "../services/utils.js";

const FIND_RESULT_SELECTED_CSS_CLASSNAME = "ck-find-result_selected";
const FIND_RESULT_CSS_CLASSNAME = "ck-find-result";

export default class FindInCode {
    constructor(parent) {
        /** @property {FindWidget} */
        this.parent = parent;
    }

    async getCodeEditor() {
        return this.parent.noteContext.getCodeEditor();
    }

    async getInitialSearchTerm() {
        const codeEditor = await this.getCodeEditor();

        // highlightSelectionMatches is the overlay that highlights
        // the words under the cursor. This occludes the search
        // markers style, save it, disable it. Will be restored when
        // the focus is back into the note
        this.oldHighlightSelectionMatches = codeEditor.getOption("highlightSelectionMatches");
        codeEditor.setOption("highlightSelectionMatches", false);

        // Fill in the findbox with the current selection if any
        const selectedText = codeEditor.getSelection()
        if (selectedText !== "") {
            return selectedText;
        }
    }

    async performFind(searchTerm, matchCase, wholeWord) {
        let findResult = null;
        let totalFound = 0;
        let currentFound = -1;

        // See https://codemirror.net/addon/search/searchcursor.js for tips
        const codeEditor = await this.getCodeEditor();
        const doc = codeEditor.doc;
        const text = doc.getValue();

        // Clear all markers
        if (this.findResult != null) {
            codeEditor.operation(() => {
                for (let i = 0; i < this.findResult.length; ++i) {
                    const marker = this.findResult[i];
                    marker.clear();
                }
            });
        }

        if (searchTerm !== "") {
            searchTerm = utils.escapeRegExp(searchTerm);

            // Find and highlight matches
            // Find and highlight matches
            // XXX Using \\b and not using the unicode flag probably doesn't
            //     work with non ascii alphabets, findAndReplace uses a more
            //     complicated regexp, see
            //     https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/utils.js#L145
            const wholeWordChar = wholeWord ? "\\b" : "";
            const re = new RegExp(wholeWordChar + searchTerm + wholeWordChar,
                'g' + (matchCase ? '' : 'i'));
            let curLine = 0;
            let curChar = 0;
            let curMatch = null;
            findResult = [];
            // All those markText take several seconds on eg this ~500-line
            // script, batch them inside an operation so they become
            // unnoticeable. Alternatively, an overlay could be used, see
            // https://codemirror.net/addon/search/match-highlighter.js ?
            codeEditor.operation(() => {
                for (let i = 0; i < text.length; ++i) {
                    // Fetch next match if it's the first time or
                    // if past the current match start
                    if ((curMatch == null) || (curMatch.index < i)) {
                        curMatch = re.exec(text);
                        if (curMatch == null) {
                            // No more matches
                            break;
                        }
                    }
                    // Create a non-selected highlight marker for the match, the
                    // selected marker highlight will be done later
                    if (i === curMatch.index) {
                        let fromPos = { "line" : curLine, "ch" : curChar };
                        // XXX If multiline is supported, this needs to
                        //     recalculate curLine since the match may span
                        //     lines
                        let toPos = { "line" : curLine, "ch" : curChar + curMatch[0].length};
                        // XXX or css = "color: #f3"
                        let marker = doc.markText( fromPos, toPos, { "className" : FIND_RESULT_CSS_CLASSNAME });
                        findResult.push(marker);

                        // Set the first match beyond the cursor as current
                        // match
                        if (currentFound === -1) {
                            const cursorPos = codeEditor.getCursor();
                            if ((fromPos.line > cursorPos.line) ||
                                ((fromPos.line === cursorPos.line) &&
                                    (fromPos.ch >= cursorPos.ch))){
                                currentFound = totalFound;
                            }
                        }

                        totalFound++;
                    }
                    // Do line and char position tracking
                    if (text[i] === "\n") {
                        curLine++;
                        curChar = 0;
                    } else {
                        curChar++;
                    }
                }
            });
        }

        this.findResult = findResult;

        // Calculate curfound if not already, highlight it as selected
        if (totalFound > 0) {
            currentFound = Math.max(0, currentFound)
            let marker = findResult[currentFound];
            let pos = marker.find();
            codeEditor.scrollIntoView(pos.to);
            marker.clear();
            findResult[currentFound] = doc.markText( pos.from, pos.to,
                { "className" : FIND_RESULT_SELECTED_CSS_CLASSNAME }
            );
        }

        return {
            totalFound,
            currentFound: currentFound + 1
        };
    }

    async findNext(direction, currentFound, nextFound) {
        const codeEditor = await this.getCodeEditor();
        const doc = codeEditor.doc;

        //
        // Dehighlight current, highlight & scrollIntoView next
        //

        let marker = this.findResult[currentFound];
        let pos = marker.find();
        marker.clear();
        marker = doc.markText(
            pos.from, pos.to,
            { "className" : FIND_RESULT_CSS_CLASSNAME }
        );
        this.findResult[currentFound] = marker;

        marker = this.findResult[nextFound];
        pos = marker.find();
        marker.clear();
        marker = doc.markText(
            pos.from, pos.to,
            { "className" : FIND_RESULT_SELECTED_CSS_CLASSNAME }
        );
        this.findResult[nextFound] = marker;

        codeEditor.scrollIntoView(pos.from);
    }

    async findBoxClosed(totalFound, currentFound) {
        const codeEditor = await this.getCodeEditor();

        if (totalFound > 0) {
            const doc = codeEditor.doc;
            const pos = this.findResult[currentFound].find();
            // Note setting the selection sets the cursor to
            // the end of the selection and scrolls it into
            // view
            doc.setSelection(pos.from, pos.to);
            // Clear all markers
            codeEditor.operation(() => {
                for (let i = 0; i < this.findResult.length; ++i) {
                    let marker = this.findResult[i];
                    marker.clear();
                }
            });
        }
        // Restore the highlightSelectionMatches setting
        codeEditor.setOption("highlightSelectionMatches", this.oldHighlightSelectionMatches);
        this.findResult = null;

        codeEditor.focus();
    }
}
