import numpy as np
from bokeh.models import BBoxTileSource, QUADKEYTileSource, WMTSTileSource

from ...core.options import SkipRendering
from ...element.tiles import _ATTRIBUTIONS
from .element import ElementPlot


class TilePlot(ElementPlot):
    style_opts = ["alpha", "render_parents", "level", "smoothing", "min_zoom", "max_zoom"]
    selection_display = None

    def get_extents(self, element, ranges, range_type="combined", **kwargs):
        extents = super().get_extents(element, ranges, range_type)
        if (
            not self.overlaid
            and all(e is None or not np.isfinite(e) for e in extents)
            and range_type in ("combined", "data")
        ):
            x0, x1 = (-20037508.342789244, 20037508.342789244)
            y0, y1 = (-20037508.342789255, 20037508.342789244)
            global_extent = (x0, y0, x1, y1)
            return global_extent
        return extents

    def get_data(self, element, ranges, style):
        if not isinstance(element.data, (str, dict)):
            SkipRendering(
                "WMTS element data must be a URL string, dictionary, or "
                "xyzservices.TileProvider, bokeh cannot "
                f"render {element.data!r}"
            )
        if element.data is None:
            raise ValueError("Tile source URL may not be None with the bokeh backend")
        elif isinstance(element.data, dict):
            tile_source = WMTSTileSource
        elif "{Q}" in element.data:
            tile_source = QUADKEYTileSource
        elif all(kw in element.data for kw in ("{XMIN}", "{XMAX}", "{YMIN}", "{YMAX}")):
            tile_source = BBoxTileSource
        elif all(kw in element.data for kw in ("{X}", "{Y}", "{Z}")):
            tile_source = WMTSTileSource
        else:
            raise ValueError(
                "Tile source URL format not recognized. "
                "Must contain {X}/{Y}/{Z}, {XMIN}/{XMAX}/{YMIN}/{YMAX} "
                "or {Q} template strings."
            )
        if isinstance(element.data, dict):
            params = {
                "url": element.data.build_url(scale_factor="@2x"),
                "min_zoom": element.data.get("min_zoom", 0),
                "max_zoom": element.data.get("max_zoom", 20),
                "attribution": element.data.html_attribution,
            }
        else:
            params = {"url": element.data}
            for zoom in ("min_zoom", "max_zoom"):
                if zoom in style:
                    params[zoom] = style[zoom]
            for key, attribution in _ATTRIBUTIONS.items():
                if all(k in element.data for k in key):
                    params["attribution"] = attribution
        return {}, {"tile_source": tile_source(**params)}, style

    def _update_glyph(self, renderer, properties, mapping, glyph, source=None, data=None):
        glyph.url = mapping["tile_source"].url
        glyph.update(**{k: v for k, v in properties.items() if k in glyph.properties()})
        renderer.update(**{k: v for k, v in properties.items() if k in renderer.properties()})

    def _init_glyph(self, plot, mapping, properties):
        """Returns a Bokeh glyph object."""
        tile_source = mapping["tile_source"]
        level = properties.pop("level", "glyph")
        renderer = plot.add_tile(tile_source, level=level)
        renderer.alpha = properties.get("alpha", 1)
        return renderer, tile_source
