from .typing import Optional, List, Generator
from contextlib import contextmanager
import sublime
import sublime_plugin


# about 80 chars per line implies maintaining a buffer of about 40kb per window
SERVER_PANEL_MAX_LINES = 500


OUTPUT_PANEL_SETTINGS = {
    "auto_indent": False,
    "draw_indent_guides": False,
    "draw_white_space": "None",
    "fold_buttons": True,
    "gutter": True,
    "is_widget": True,
    "line_numbers": False,
    "lsp_active": True,
    "margin": 3,
    "match_brackets": False,
    "rulers": [],
    "scroll_past_end": False,
    "tab_size": 4,
    "translate_tabs_to_spaces": False,
    "word_wrap": False
}


class PanelName:
    Diagnostics = "diagnostics"
    References = "references"
    LanguageServers = "language servers"


@contextmanager
def mutable(view: sublime.View) -> Generator:
    view.set_read_only(False)
    yield
    view.set_read_only(True)


def create_output_panel(window: sublime.Window, name: str) -> Optional[sublime.View]:
    panel = window.create_output_panel(name)
    settings = panel.settings()
    for key, value in OUTPUT_PANEL_SETTINGS.items():
        settings.set(key, value)
    return panel


def destroy_output_panels(window: sublime.Window) -> None:
    for field in filter(lambda a: not a.startswith('__'), PanelName.__dict__.keys()):
        panel_name = getattr(PanelName, field)
        panel = window.find_output_panel(panel_name)
        if panel and panel.is_valid():
            panel.settings().set("syntax", "Packages/Text/Plain text.tmLanguage")
            window.destroy_output_panel(panel_name)


def create_panel(window: sublime.Window, name: str, result_file_regex: str, result_line_regex: str,
                 syntax: str) -> Optional[sublime.View]:
    panel = create_output_panel(window, name)
    if not panel:
        return None
    panel.settings().set("result_file_regex", result_file_regex)
    panel.settings().set("result_line_regex", result_line_regex)
    panel.assign_syntax(syntax)
    # Call create_output_panel a second time after assigning the above
    # settings, so that it'll be picked up as a result buffer
    # see: Packages/Default/exec.py#L228-L230
    panel = window.create_output_panel(name)
    # All our panels are read-only
    panel.set_read_only(True)
    return panel


def ensure_panel(window: sublime.Window, name: str, result_file_regex: str, result_line_regex: str,
                 syntax: str) -> Optional[sublime.View]:
    return window.find_output_panel(name) or create_panel(window, name, result_file_regex, result_line_regex, syntax)


class LspClearPanelCommand(sublime_plugin.TextCommand):
    """
    A clear_panel command to clear the error panel.
    """

    def run(self, edit: sublime.Edit) -> None:
        with mutable(self.view):
            self.view.erase(edit, sublime.Region(0, self.view.size()))


class LspUpdatePanelCommand(sublime_plugin.TextCommand):
    """
    A update_panel command to update the error panel with new text.
    """

    def run(self, edit: sublime.Edit, characters: Optional[str] = "") -> None:
        # Clear folds
        self.view.unfold(sublime.Region(0, self.view.size()))

        with mutable(self.view):
            self.view.replace(edit, sublime.Region(0, self.view.size()), characters or "")

        # Clear the selection
        selection = self.view.sel()
        selection.clear()


class LspUpdateServerPanelCommand(sublime_plugin.TextCommand):
    def run(self, edit: sublime.Edit, prefix: str, message: str) -> None:
        with mutable(self.view):
            message = message.replace("\r\n", "\n")  # normalize Windows eol
            self.view.insert(edit, self.view.size(), "{}: {}\n".format(prefix, message))
            total_lines, _ = self.view.rowcol(self.view.size())
            point = 0  # Starting from point 0 in the panel ...
            regions = []  # type: List[sublime.Region]
            for _ in range(0, max(0, total_lines - SERVER_PANEL_MAX_LINES)):
                # ... collect all regions that span an entire line ...
                region = self.view.full_line(point)
                regions.append(region)
                point = region.b
            for region in reversed(regions):
                # ... and erase them in reverse order
                self.view.erase(edit, region)