import sublime
import sublime_plugin
from .core.configurations import is_supported_syntax
from .core.documents import is_transient_view
from .core.protocol import Range
from .core.protocol import Request
from .core.registry import session_for_view, sessions_for_view, client_from_session, configs_for_scope
from .core.settings import settings, client_configs
from .core.typing import Any, List, Dict, Optional
from .core.url import filename_to_uri
from .core.views import range_to_region


color_phantoms_by_view = dict()  # type: Dict[int, sublime.PhantomSet]


class LspColorListener(sublime_plugin.ViewEventListener):
    def __init__(self, view: sublime.View) -> None:
        super().__init__(view)
        self._stored_point = -1
        self.initialized = False
        self.enabled = False

    @classmethod
    def is_applicable(cls, _settings: Any) -> bool:
        syntax = _settings.get('syntax')
        is_supported = syntax and is_supported_syntax(syntax, client_configs.all)
        disabled_by_user = 'colorProvider' in settings.disabled_capabilities
        return is_supported and not disabled_by_user

    @property
    def phantom_set(self) -> sublime.PhantomSet:
        return color_phantoms_by_view.setdefault(self.view.id(), sublime.PhantomSet(self.view, "lsp_color"))

    def on_activated_async(self) -> None:
        if not self.initialized:
            self.initialize()

    def initialize(self, is_retry: bool = False) -> None:
        configs = configs_for_scope(self.view)
        if not configs:
            self.initialized = True  # no server enabled, re-open file to activate feature.
        sessions = list(sessions_for_view(self.view, 'colorProvider'))
        if sessions:
            self.initialized = True
            self.enabled = True
            self.send_color_request()
        elif not is_retry:
            # session may be starting, try again once in a second.
            sublime.set_timeout_async(lambda: self.initialize(is_retry=True), 1000)
        else:
            self.initialized = True  # we retried but still no session available.

    def on_modified_async(self) -> None:
        if self.enabled:
            self.schedule_request()

    def schedule_request(self) -> None:
        sel = self.view.sel()
        if len(sel) < 1:
            return

        current_point = sel[0].begin()
        if self._stored_point != current_point:
            self._stored_point = current_point
            sublime.set_timeout_async(lambda: self.fire_request(current_point), 800)

    def fire_request(self, current_point: int) -> None:
        if current_point == self._stored_point:
            self.send_color_request()

    def send_color_request(self) -> None:
        if is_transient_view(self.view):
            return

        client = client_from_session(session_for_view(self.view, 'colorProvider'))
        if client:
            file_path = self.view.file_name()
            if file_path:
                params = {
                    "textDocument": {
                        "uri": filename_to_uri(file_path)
                    }
                }
                client.send_request(
                    Request.documentColor(params),
                    self.handle_response
                )

    def handle_response(self, response: Optional[List[dict]]) -> None:
        color_infos = response if response else []
        phantoms = []
        for color_info in color_infos:
            color = color_info['color']
            red = color['red'] * 255
            green = color['green'] * 255
            blue = color['blue'] * 255
            alpha = color['alpha']

            content = """
            <style>html {{padding: 0}}</style>
            <div style='padding: 0.4em;
                        margin-top: 0.2em;
                        border: 1px solid color(var(--foreground) alpha(0.25));
                        background-color: rgba({}, {}, {}, {})'>
            </div>""".format(red, green, blue, alpha)

            range = Range.from_lsp(color_info['range'])
            region = range_to_region(range, self.view)

            phantoms.append(sublime.Phantom(region, content, sublime.LAYOUT_INLINE))

        self.phantom_set.update(phantoms)


def remove_color_boxes(view: sublime.View) -> None:
    phantom_set = color_phantoms_by_view.get(view.id())
    if phantom_set:
        phantom_set.update([])