import tkinter as tk

from thonny import get_workbench
from thonny.common import TextRange
from thonny.languages import tr
from thonny.ui_utils import select_sequence

BLOCK_COMMENT_PREFIX = "# "


def _get_focused_writable_text():
    widget = get_workbench().focus_get()
    # In Ubuntu when moving from one menu to another, this may give None when text is actually focused
    if isinstance(widget, tk.Text) and (
        not hasattr(widget, "is_read_only") or not widget.is_read_only()
    ):
        return widget
    else:
        return None


def _writable_text_is_focused():
    return _get_focused_writable_text() is not None


def _selection_is_line_commented(text):
    sel_range = _get_focused_code_range(text)

    for lineno in range(sel_range.lineno, sel_range.end_lineno + 1):
        line = text.get(str(lineno) + ".0", str(lineno) + ".end")
        if not line.startswith(BLOCK_COMMENT_PREFIX):
            return False

    return True


def _select_lines(text, first_line, last_line):
    text.tag_remove("sel", "1.0", tk.END)
    text.tag_add("sel", str(first_line) + ".0", str(last_line) + ".end")


def _toggle_selection_comment(text):
    if _selection_is_line_commented(text):
        _uncomment_selection(text)
    else:
        _comment_selection(text)


def _comment_selection(text):
    """Adds ## in front of all selected lines if any lines are selected,
    or just the current line otherwise"""

    sel_range = _get_focused_code_range(text)

    for lineno in range(sel_range.lineno, sel_range.end_lineno + 1):
        text.insert(str(lineno) + ".0", BLOCK_COMMENT_PREFIX)

    if sel_range.end_lineno > sel_range.lineno:
        _select_lines(text, sel_range.lineno, sel_range.end_lineno)

    text.edit_separator()


def _uncomment_selection(text):
    sel_range = _get_focused_code_range(text)

    for lineno in range(sel_range.lineno, sel_range.end_lineno + 1):
        line = text.get(str(lineno) + ".0", str(lineno) + ".end")
        if line.startswith(BLOCK_COMMENT_PREFIX):
            text.delete(str(lineno) + ".0", str(lineno) + "." + str(len(BLOCK_COMMENT_PREFIX)))


def _get_focused_code_range(text):
    if len(text.tag_ranges("sel")) > 0:
        lineno, col_offset = map(int, text.index(tk.SEL_FIRST).split("."))
        end_lineno, end_col_offset = map(int, text.index(tk.SEL_LAST).split("."))

        if end_lineno > lineno and end_col_offset == 0:
            # SelectAll includes nonexisting extra line
            end_lineno -= 1
            end_col_offset = int(text.index(str(end_lineno) + ".end").split(".")[1])
    else:
        lineno, col_offset = map(int, text.index(tk.INSERT).split("."))
        end_lineno, end_col_offset = lineno, col_offset

    return TextRange(lineno, col_offset, end_lineno, end_col_offset)


def _cmd_toggle_selection_comment():
    text = _get_focused_writable_text()
    if text is not None:
        _toggle_selection_comment(text)


def _cmd_comment_selection():
    text = _get_focused_writable_text()
    if text is not None:
        _comment_selection(text)


def _cmd_uncomment_selection():
    text = _get_focused_writable_text()
    if text is not None:
        _uncomment_selection(text)


def _cmd_indent_selection():
    text = _get_focused_writable_text()
    if text is not None and hasattr(text, "indent_region"):
        text.indent_region()


def _cmd_dedent_selection():
    text = _get_focused_writable_text()
    if text is not None and hasattr(text, "dedent_region"):
        text.dedent_region()


def _cmd_replace_tabs():
    text = _get_focused_writable_text()
    if text is not None:
        orig_lines = text.get("1.0", "end").splitlines(keepends=True)
        new_lines = []
        for line in orig_lines:
            leading_tab_count = 0
            for char in line:
                if char == "\t":
                    leading_tab_count += 1
                else:
                    break
            new_lines.append(leading_tab_count * "    " + line[leading_tab_count:])

        text.delete("1.0", "end")

        text.insert("1.0", "".join(new_lines))


def load_plugin() -> None:
    get_workbench().add_command(
        "indent",
        "edit",
        tr("Indent selected lines"),
        _cmd_indent_selection,
        tester=_writable_text_is_focused,
        accelerator="Tab",
        group=49,
    )

    get_workbench().add_command(
        "dedent",
        "edit",
        tr("Dedent selected lines"),
        _cmd_dedent_selection,
        tester=_writable_text_is_focused,
        accelerator="Shift+Tab",
        group=49,
    )

    get_workbench().add_command(
        "replace_tabs",
        "edit",
        tr("Replace tabs with spaces"),
        _cmd_replace_tabs,
        tester=_writable_text_is_focused,
        group=49,
    )

    get_workbench().add_command(
        "toggle_comment",
        "edit",
        tr("Toggle comment"),
        _cmd_toggle_selection_comment,
        default_sequence=select_sequence("<Control-Key-3>", "<Command-Key-3>"),
        tester=_writable_text_is_focused,
        group=50,
    )

    get_workbench().add_command(
        "comment_selection",
        "edit",
        tr("Comment out"),
        _cmd_comment_selection,
        default_sequence="<Alt-Key-3>",
        tester=_writable_text_is_focused,
        group=50,
    )

    get_workbench().add_command(
        "uncomment_selection",
        "edit",
        tr("Uncomment"),
        _cmd_uncomment_selection,
        default_sequence="<Alt-Key-4>",
        tester=_writable_text_is_focused,
        group=50,
    )
