# Copyright (C) 2019-2022 HERE Global B.V. and its affiliate(s).
# All rights reserved.
#
# This software and other materials contain proprietary information
# controlled by HERE and are protected by applicable copyright legislation.
# Any use and utilization of this software and other materials and
# disclosure to any third parties is conditional upon having a separate
# agreement with HERE for the access, use, utilization or disclosure of this
# software. In the absence of such agreement, the use of the software is not
# allowed.

"""HERE Platform Python SDK, Jupyter GeoData Inspector package
"""

from typing import Any, Dict, Mapping, Optional, Sequence, Tuple, Union, cast

from here.inspector.base import Inspector  # noqa F401
from here.inspector.heremap import HEREMapInspector
from here.inspector.ipyleaflet import IpyleafletInspector
from here.inspector.ipyleaflet.basemaps import external_basemaps, here_basemaps  # noqa F401
from here.inspector.styles import Color, Theme
from here.inspector.utils import Features, Tiles, options  # noqa F401
from shapely.geometry import Point

__version__ = "2.11.0"


def inspect(
    features: Optional[Features] = None,
    name=None,
    style: Optional[Union[Color, Dict]] = None,
    layers: Optional[
        Union[
            Mapping[Any, Features],
            Sequence[Union[Tuple[Any, Features], Tuple[Any, Features, Union[Color, Dict]]]],
        ]
    ] = None,
    layers_style: Optional[Mapping[Any, Union[Color, Dict]]] = None,
    tiles: Optional[Tiles] = None,
    tiles_style: Optional[Union[Color, Dict]] = None,
    center: Optional[Point] = None,
    zoom: Optional[int] = None,
    theme: Optional[Theme] = None,
):
    """
    Shorthand function to instantiate, configure and show the inspector.

    It supports the majority of the features of the inspector. For finer control,
    please manually instantiate, configure and ``show()`` an ``Inspector`` implementation.

    :param features: an optional set of attributed geometries.
      Supported types:
      - ``gpd.GeoSeries``, unattributed geometries
      - ``gpd.GeoDataFrame``, attributes geometries
      - ``BaseGeometry``, individual geometries
      - ``Iterable`` of `BaseGeometry`, it can be any container
                     but also a ``Generator`` of unattributed geometries
      - ``Iterable`` of pairs of ``BaseGeometry`` and ``Dict``, it can be any container
                     but also a ``Generator`` of geometries paired with attributes
      - ``Dict``     a parsed GeoJSON ``FeatureCollection`` or ``Feature``
      - ``Iterable`` of parsed GeoJSON `Feature`, it can be any container
                     but also a ``Generator`` of parsed GeoJSON features
      - ``None`` no single set of features is rendered
    :param name: the name of features, used as layer name.
    :param style: an optional style for the features. If not present, a default one is picked.
      It can be a generic inspector style, as defined in ``here.inspector.styles``,
      or directly a style dictionary, compatible with the inspector implementation.
    :param layers: optional set of layers, each with a set of attributed geometries.
      It can be a ``Mapping`` containing layer names and attributed geometries.
      It can also be a ``Sequence`` of tuples, each with 2 or 3 components: the layer name,
      the attributed geometries, and an optional style, removing the need to use ``layers_style``.
      Supported types for the attributed geometries:
      - ``gpd.GeoSeries``, unattributed geometries
      - ``gpd.GeoDataFrame``, attributes geometries
      - ``Iterable`` of `BaseGeometry`, it can be any container
                     but also a ``Generator`` of unattributed geometries
      - ``Iterable`` of pairs of ``BaseGeometry`` and ``Dict``, it can be any container
                     but also a ``Generator`` of geometries paired with attributes
      - ``Dict``     a parsed GeoJSON ``FeatureCollection`` or ``Feature``
      - ``Iterable`` of parsed GeoJSON `Feature`, it can be any container
                     but also a ``Generator`` of parsed GeoJSON features
    :param layers_style: an optional dictionary with one style per layer:
      layers not mentioned in the dictionary are styled with a default style.
      Each style can be a generic inspector style, as defined in ``here.inspector.styles``,
      or directly a style dictionary, compatible with the inspector implementation.
    :param tiles: an optional tiling grid. Supported types:
      - ``pd.Series`` of tile identifiers
      - ``Iterable``  of tile identifiers, it can be any container but also a ``Generator``
      - ``None`` no tiling grid is rendered
    :param tiles_style:
      an optional style for the tiling grid. If not present, a default one is picked.
      It can be a generic inspector style, as defined in ``here.inspector.styles``,
      or directly a style dictionary, compatible with the inspector implementation.
    :param center: the center point of the map
    :param zoom: the zoom level, from 0 to 31
    :param theme: the inspector visual theme, default is used if not specified
    :return: an object renderable by Jupyter
    :raises ValueError: in case the combination of input parameters is not supported
    """
    inspector = new_inspector()

    if features is not None:
        # features is a set of geofeatures
        inspector.add_features(features=features, name=name, style=style)

    if isinstance(layers, Mapping):
        style_idx = layers_style or {}
        # layers contain multiple sets of geofeatures, each is a separate layer
        for layer_name, layer_features in layers.items():
            inspector.add_features(
                features=layer_features, name=layer_name, style=style_idx.get(layer_name)
            )
    elif isinstance(layers, Sequence):
        # layers contain multiple sets of geofeatures, each is a separate layer,
        # an optional style is specified inline in the tuple
        for layer in layers:
            if len(layer) == 2:
                layer = cast(Tuple[Any, Features], layer)
                layer_name, layer_features = layer
                layer_style = None
            elif len(layer) == 3:
                layer = cast(Tuple[Any, Features, Union[Color, Dict]], layer)
                layer_name, layer_features, layer_style = layer
            else:
                raise ValueError("'layers' parameter in an unrecognized format")
            inspector.add_features(features=layer_features, name=layer_name, style=layer_style)
    elif layers is not None:
        raise ValueError("'layers' parameter in an unrecognized format")

    if tiles is not None:
        # tiles is a tiling grid
        inspector.add_tiles(tiles=tiles, style=tiles_style)

    if center is not None:
        inspector.set_center(center=center)

    if zoom is not None:
        inspector.set_zoom(zoom=zoom)

    if theme is not None:
        inspector.set_theme(theme=theme)

    return inspector.show()


def new_inspector() -> Inspector:
    """
    Instantiate a new inspector.

    :return: A new inspector.
    :raises ValueError: If invalid ``inspector_class`` attribute is set in ``options``.
    """
    if options.inspector_class is None:
        return IpyleafletInspector()
    elif options.inspector_class.__name__ == "IpyleafletInspector":
        return IpyleafletInspector()
    elif options.inspector_class.__name__ == "HEREMapInspector":
        return HEREMapInspector()
    else:
        raise ValueError("Invalid inspector class set in options.")
