# 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.

"""Tests for Here Map Widget for Jupyter inspector backend"""

import pytest
from here.inspector import Color, Theme, inspect, options
from here.inspector.heremap import HEREMapInspector
from here_map_widget import GeoData, GeoJSON, Map
from shapely.geometry import LinearRing, LineString, Point, Polygon


def test_default_theme(here_inspector):
    """Test default theme is correctly set."""
    m = here_inspector.backend()
    assert isinstance(m, Map)
    assert here_inspector._theme == options.default_theme
    assert m.basemap.name == "HEREv3.reducedDay"


def test_change_default_theme():
    """Test changing the default theme."""
    try:
        options.default_theme = Theme.DARK_MAP
        options.api_key = "DUMMY"
        i = HEREMapInspector()
        m = i.backend()
        options.default_theme = Theme.LIGHT_MAP  # clean-up
        assert i._theme == Theme.DARK_MAP
        assert m.basemap.name == "HEREv3.reducedNight"
    finally:
        options.api_key = None


def test_reuse_default_theme(here_inspector):
    """Test reusing the default theme after setting some other first."""
    here_inspector.set_theme(Theme.LIGHT_BLANK)
    here_inspector.set_theme()
    m = here_inspector.backend()
    assert here_inspector._theme == options.default_theme
    assert m.basemap.name == "HEREv3.reducedDay"


def test_default_colors(here_inspector):
    """Test default colors are correctly set."""
    _ = here_inspector.backend()
    assert here_inspector._colors == options.default_colors


def test_change_default_colors():
    """Test changing the default colors."""
    try:
        options.api_key = "DUMMY"
        options.default_colors = list(reversed(options.default_colors))
        i = HEREMapInspector()
        _ = i.backend()
        options.default_colors = list(reversed(options.default_colors))  # clean-up
        assert i._colors == list(reversed(options.default_colors))
    finally:
        options.api_key = None


def test_reuse_default_colors(here_inspector):
    """Test reusing the default colors after setting some others first."""
    here_inspector.set_colors([Color.BLUE, Color.YELLOW])
    here_inspector.set_colors()
    _ = here_inspector.backend()
    assert here_inspector._colors == options.default_colors


def test_default_basemap(here_inspector):
    """Test default basemap is correctly set."""
    m = here_inspector.backend()
    assert here_inspector._custom_basemap is None
    assert m.basemap.name == "HEREv3.reducedDay"


def test_custom_basemap(here_inspector):
    """Test custom basemap is correctly set."""
    from here_map_widget import basemaps

    here_inspector.set_basemap(basemaps.CartoDB.Positron)
    m = here_inspector.backend()
    assert here_inspector._custom_basemap == basemaps.CartoDB.Positron
    assert m.basemap.name == "CartoDB.Positron"


def test_custom_basemap_dictionary(here_inspector):
    """Test custom basemap is correctly set."""
    osm = {
        "url": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        "max_zoom": 19,
        "html_attribution": '&copy; <a href="https://www.openstreetmap.org/copyright">'
        "OpenStreetMap</a> contributors",
        "attribution": "(C) OpenStreetMap contributors",
        "name": "OpenStreetMap.Mapnik",
    }
    here_inspector.set_basemap(osm)
    m = here_inspector.backend()
    assert here_inspector._custom_basemap == osm
    assert m.basemap.name == "OpenStreetMap.Mapnik"


def test_reuse_default_basemap(here_inspector):
    """Test reusing the default basemap after setting one other first."""
    from here_map_widget import basemaps

    here_inspector.set_basemap(basemaps.CartoDB.Positron)
    here_inspector.set_basemap()
    m = here_inspector.backend()
    assert here_inspector._custom_basemap is None
    assert m.basemap.name == "HEREv3.reducedDay"


def test_shapely(here_inspector):
    from shapely.geometry import LineString, Point, Polygon

    pt1 = Point(30, 50)
    pt2 = Point(32, 49)
    ls1 = LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)])
    poly1 = Polygon([(28, 46), (28, 52), (29, 53), (31, 53), (33, 48), (32, 47), (28, 46)])

    here_inspector.add_features(
        [pt1, pt2],
        "A",
        hover_style={"fillColor": "rgba(245, 176, 65, 0.5)", "strokeColor": "black"},
    )  # collection of shapely geometries
    here_inspector.add_features([pt2, ls1, poly1], "B")  # collection of shapely geometries
    here_inspector.add_features([poly1])  # collection of shapely geometries
    here_inspector.add_features([])  # empty collection
    m = here_inspector.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        ("A", GeoData),
        ("B", GeoData),
        ("Unnamed 1", GeoData),
        ("Unnamed 2", GeoData),
    ]

    assert layers == expected


def test_geojson_features(here_inspector):
    from geojson import Feature, LineString, Point, Polygon

    pt1 = Feature(geometry=Point((30, 50)))
    pt2 = Feature(geometry=Point((32, 49)))
    ls1 = Feature(geometry=LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)]))
    poly1 = Feature(
        geometry=Polygon(
            [
                [(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)],
                [(-5.21, 23.51), (15.21, -10.81), (-20.51, 1.51), (-5.21, 23.51)],
            ]
        )
    )

    def to_dict(x):
        return dict(x.items())

    here_inspector.add_features([pt1, pt2], "A")  # single GeoJSON features
    here_inspector.add_features([pt2, ls1, poly1], "B")  # single GeoJSON features
    here_inspector.add_features([poly1])  # one single GeoJSON feature
    here_inspector.add_features([])
    here_inspector.add_features(
        list(map(to_dict, [pt1, pt2])), "C"
    )  # single GeoJSON feature dictionaries
    here_inspector.add_features(
        [to_dict(g) for g in [pt2, ls1, poly1]], "D"
    )  # single GeoJSON feature dictionaries
    here_inspector.add_features(to_dict(poly1), "E")  # one single GeoJSON feature dictionary
    here_inspector.add_features(
        [to_dict(poly1), pt1], "F"
    )  # mix of  single GeoJSON feature dictionary or GeoJSON features
    m = here_inspector.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        ("A", GeoJSON),
        ("B", GeoJSON),
        ("Unnamed 1", GeoJSON),
        ("Unnamed 2", GeoData),
        ("C", GeoJSON),
        ("D", GeoJSON),
        ("E", GeoJSON),
        ("F", GeoJSON),
    ]

    assert layers == expected


def test_geojson_collections(here_inspector):
    from geojson import Feature, FeatureCollection, LineString, Point, Polygon

    pt1 = Feature(geometry=Point((30, 50)))
    pt2 = Feature(geometry=Point((32, 49)))
    ls1 = Feature(geometry=LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)]))
    poly1 = Feature(
        geometry=Polygon(
            [
                [(2.38, 57.322), (23.194, -20.28), (-120.43, 19.15), (2.38, 57.322)],
                [(-5.21, 23.51), (15.21, -10.81), (-20.51, 1.51), (-5.21, 23.51)],
            ]
        )
    )

    def to_dict(x):
        return dict(x.items())

    c1 = FeatureCollection([pt1, pt2, ls1, poly1])
    c2 = to_dict(c1)
    c3 = FeatureCollection([])

    here_inspector.add_features(c1, "C1")  # single GeoJSON feature collection
    here_inspector.add_features(c2, "C2")  # single GeoJSON feature collection dictionary
    here_inspector.add_features(c3, "C3")  # empty GeoJSON feature collection
    m = here_inspector.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [("C1", GeoJSON), ("C2", GeoJSON), ("C3", GeoJSON)]

    assert layers == expected


def test_geoseries(here_inspector):
    from geopandas import GeoSeries
    from shapely.geometry import LineString, Point, Polygon

    pt1 = Point(30, 50)
    pt2 = Point(32, 49)
    ls1 = LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)])
    poly1 = Polygon([(28, 46), (28, 52), (29, 53), (31, 53), (33, 48), (32, 47), (28, 46)])

    gs1 = GeoSeries([pt1, pt2, poly1])
    gs2 = GeoSeries([pt1, ls1, poly1])
    gs3 = GeoSeries([pt1, pt2])

    assert gs1.dtype == "geometry"
    assert gs2.dtype == "geometry"
    assert gs3.dtype == "geometry"

    here_inspector.add_features(gs1, "A")  # a GeoSeries
    here_inspector.add_features(gs2)  # a GeoSeries
    here_inspector.add_features(gs3, "B")  # a GeoSeries
    m = here_inspector.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        ("A", GeoData),
        ("Unnamed 1", GeoData),
        ("B", GeoData),
    ]

    assert layers == expected


def test_geodataframe(here_inspector):
    from geopandas import GeoDataFrame
    from shapely.geometry import LineString, Point, Polygon

    pt1 = Point(30, 50)
    pt2 = Point(32, 49)
    ls1 = LineString([(29, 49), (30, 51), (31, 49), (32, 50), (33, 51)])
    poly1 = Polygon([(28, 46), (28, 52), (29, 53), (31, 53), (33, 48), (32, 47), (28, 46)])

    gdf1 = GeoDataFrame({"geometry": [pt1, pt2, poly1]})
    gdf2 = GeoDataFrame(geometry=[pt1, ls1, poly1])
    gdf3 = GeoDataFrame(geometry=[pt1, pt2])

    here_inspector.add_features(gdf1, "A")  # a GeoDataFrame
    here_inspector.add_features(gdf2)  # a GeoDataFrame
    here_inspector.add_features(gdf3, "B")  # a GeoDataFrame
    m = here_inspector.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        ("A", GeoData),
        ("Unnamed 1", GeoData),
        ("B", GeoData),
    ]

    assert layers == expected


def test_tiling_grid(here_inspector):
    from shapely.geometry import Point

    pt1 = Point(30, 50)
    pt2 = Point(32, 49)

    tiles1 = [4, 5]
    tiles2 = [17, 18, 19]

    here_inspector.add_tiles(tiles1, name="G1")
    here_inspector.add_tiles(tiles2, name="G2")
    here_inspector.add_features([pt1, pt2], "X")
    m = here_inspector.backend()

    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [("G1", GeoJSON), ("G2", GeoJSON), ("X", GeoData)]

    assert layers == expected


def test_single_shapes_inspect():
    """Test render single Shapely BaseGeometry subclasses with inspect."""
    from geojson.utils import coords
    from shapely.geometry import LineString

    try:
        options.inspector_class = HEREMapInspector
        options.api_key = "DUMMY"
        line = LineString([[35, 50], [35, 55]])
        m = inspect(features=line, center=line.centroid)
        assert len(m.layers) == 1
        assert list(coords(m.layers[0].data)) == list(line.coords)
    finally:
        options.inspector_class = None
        options.api_key = None


def test_single_shapes_here_map_inspector(here_inspector):
    """Test render single Shapely BaseGeometry subclasses with HEREMapInspector."""
    from geojson.utils import coords
    from shapely.geometry import LineString

    line = LineString([[30, 50], [35, 55]])
    here_inspector.set_center(line.centroid)
    here_inspector.set_zoom(10)
    here_inspector.add_features(line)
    m = here_inspector.backend()
    layers = [layer for layer in m.layers if type(layer) == GeoData]
    assert len(layers) == 1
    assert list(coords(layers[0].data)) == list(line.coords)


def test_add_point(here_inspector):
    """Test by adding single point with HEREMapInspector."""
    pt1 = Point(30, 50)
    here_inspector.add_features(pt1)
    m = here_inspector.backend()
    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        ("Unnamed 1", GeoData),
    ]
    assert layers == expected


def test_no_area_polygon(here_inspector):
    """Test for polygon having 0 area and only one point with HEREMapInspector."""
    poly1 = Polygon([(28, 46), (28, 46), (28, 46), (28, 46), (28, 46)])
    here_inspector.add_features([poly1])  # collection of shapely geometries
    m = here_inspector.backend()
    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        ("Unnamed 1", GeoData),
    ]
    assert layers == expected


def test_no_length_line(here_inspector):
    """Test with line with 0 length"""
    line = LineString([[35, 35], [35, 35]])
    here_inspector.add_features(line)
    m = here_inspector.backend()
    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [
        ("Unnamed 1", GeoData),
    ]
    assert layers == expected


def test_no_linearring(here_inspector):
    """Test with 0 area linear ring"""
    linear_ring = LinearRing(
        [
            (13, 13),
            (13, 13),
            (13, 13),
        ]
    )
    here_inspector.add_features(linear_ring)
    m = here_inspector.backend()
    layers = [(layer.name, type(layer)) for layer in m.layers]
    expected = [("Unnamed 1", GeoData)]
    assert layers == expected


def test_api_key_exception():
    """Test exception is thrown if api_key is not set in options."""
    with pytest.raises(ValueError):
        HEREMapInspector()
