"""
Wraps leaflet Polyline, Polygon, Rectangle, Circle, and CircleMarker

"""

from branca.element import MacroElement
from jinja2 import Template

from folium.map import Marker, Popup, Tooltip
from folium.utilities import camelize, get_bounds, validate_locations


def path_options(line=False, radius=False, **kwargs):
    """
    Contains options and constants shared between vector overlays
    (Polygon, Polyline, Circle, CircleMarker, and Rectangle).

    Parameters
    ----------
    stroke: Bool, True
        Whether to draw stroke along the path.
        Set it to false to disable borders on polygons or circles.
    color: str, '#3388ff'
        Stroke color.
    weight: int, 3
        Stroke width in pixels.
    opacity: float, 1.0
        Stroke opacity.
    line_cap: str, 'round' (lineCap)
        A string that defines shape to be used at the end of the stroke.
        https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linecap
    line_join: str, 'round' (lineJoin)
        A string that defines shape to be used at the corners of the stroke.
        https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-linejoin
    dash_array: str, None (dashArray)
        A string that defines the stroke dash pattern.
        Doesn't work on Canvas-powered layers in some old browsers.
        https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray
    dash_offset:, str, None (dashOffset)
        A string that defines the distance into the dash pattern to start the dash.
        Doesn't work on Canvas-powered layers in some old browsers.
        https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dashoffset
    fill: Bool, False
        Whether to fill the path with color.
        Set it to false to disable filling on polygons or circles.
    fill_color: str, default to `color` (fillColor)
        Fill color. Defaults to the value of the color option.
    fill_opacity: float, 0.2 (fillOpacity)
        Fill opacity.
    fill_rule: str, 'evenodd' (fillRule)
        A string that defines how the inside of a shape is determined.
        https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule
    bubbling_mouse_events: Bool, True (bubblingMouseEvents)
        When true a mouse event on this path will trigger the same event on the
        map (unless L.DomEvent.stopPropagation is used).
    gradient: bool, default None
        When a gradient on the stroke and fill is available,
        allows turning it on or off.

    Note that the presence of `fill_color` will override `fill=False`.

    This function accepts both snake_case and lowerCamelCase equivalents.

    See https://leafletjs.com/reference.html#path

    """

    kwargs = {camelize(key): value for key, value in kwargs.items()}

    extra_options = {}
    if line:
        extra_options = {
            "smoothFactor": kwargs.pop("smoothFactor", 1.0),
            "noClip": kwargs.pop("noClip", False),
        }
    if radius:
        extra_options.update({"radius": radius})

    color = kwargs.pop("color", "#3388ff")
    fill_color = kwargs.pop("fillColor", False)
    if fill_color:
        fill = True
    elif not fill_color:
        fill_color = color
        fill = kwargs.pop("fill", False)

    gradient = kwargs.pop("gradient", None)
    if gradient is not None:
        extra_options.update({"gradient": gradient})

    default = {
        "stroke": kwargs.pop("stroke", True),
        "color": color,
        "weight": kwargs.pop("weight", 3),
        "opacity": kwargs.pop("opacity", 1.0),
        "lineCap": kwargs.pop("lineCap", "round"),
        "lineJoin": kwargs.pop("lineJoin", "round"),
        "dashArray": kwargs.pop("dashArray", None),
        "dashOffset": kwargs.pop("dashOffset", None),
        "fill": fill,
        "fillColor": fill_color,
        "fillOpacity": kwargs.pop("fillOpacity", 0.2),
        "fillRule": kwargs.pop("fillRule", "evenodd"),
        "bubblingMouseEvents": kwargs.pop("bubblingMouseEvents", True),
    }
    default.update(extra_options)
    return default


class BaseMultiLocation(MacroElement):
    """Base class for vector classes with multiple coordinates.

    Not for direct consumption

    """

    def __init__(self, locations, popup=None, tooltip=None):
        super().__init__()
        self.locations = validate_locations(locations)
        if popup is not None:
            self.add_child(popup if isinstance(popup, Popup) else Popup(str(popup)))
        if tooltip is not None:
            self.add_child(
                tooltip if isinstance(tooltip, Tooltip) else Tooltip(str(tooltip))
            )

    def _get_self_bounds(self):
        """Compute the bounds of the object itself."""
        return get_bounds(self.locations)


class PolyLine(BaseMultiLocation):
    """Draw polyline overlays on a map.

    See :func:`folium.vector_layers.path_options` for the `Path` options.

    Parameters
    ----------
    locations: list of points (latitude, longitude)
        Latitude and Longitude of line (Northing, Easting)
    popup: str or folium.Popup, default None
        Input text or visualization for object displayed when clicking.
    tooltip: str or folium.Tooltip, default None
        Display a text when hovering over the object.
    smooth_factor: float, default 1.0
        How much to simplify the polyline on each zoom level.
        More means better performance and smoother look,
        and less means more accurate representation.
    no_clip: Bool, default False
        Disable polyline clipping.
    **kwargs
        Other valid (possibly inherited) options. See:
        https://leafletjs.com/reference.html#polyline

    """

    _template = Template(
        """
        {% macro script(this, kwargs) %}
            var {{ this.get_name() }} = L.polyline(
                {{ this.locations|tojson }},
                {{ this.options|tojson }}
            ).addTo({{this._parent.get_name()}});
        {% endmacro %}
        """
    )

    def __init__(self, locations, popup=None, tooltip=None, **kwargs):
        super().__init__(locations, popup=popup, tooltip=tooltip)
        self._name = "PolyLine"
        self.options = path_options(line=True, **kwargs)


class Polygon(BaseMultiLocation):
    """Draw polygon overlays on a map.

    See :func:`folium.vector_layers.path_options` for the `Path` options.

    Parameters
    ----------
    locations: list of points (latitude, longitude)
        Latitude and Longitude of line (Northing, Easting)
    popup: string or folium.Popup, default None
        Input text or visualization for object displayed when clicking.
    tooltip: str or folium.Tooltip, default None
        Display a text when hovering over the object.
    **kwargs
        Other valid (possibly inherited) options. See:
        https://leafletjs.com/reference.html#polygon

    """

    _template = Template(
        """
        {% macro script(this, kwargs) %}
            var {{ this.get_name() }} = L.polygon(
                {{ this.locations|tojson }},
                {{ this.options|tojson }}
            ).addTo({{this._parent.get_name()}});
        {% endmacro %}
        """
    )

    def __init__(self, locations, popup=None, tooltip=None, **kwargs):
        super().__init__(locations, popup=popup, tooltip=tooltip)
        self._name = "Polygon"
        self.options = path_options(line=True, **kwargs)


class Rectangle(BaseMultiLocation):
    """Draw rectangle overlays on a map.

    See :func:`folium.vector_layers.path_options` for the `Path` options.

    Parameters
    ----------
    bounds: list of points (latitude, longitude)
        Latitude and Longitude of line (Northing, Easting)
    popup: string or folium.Popup, default None
        Input text or visualization for object displayed when clicking.
    tooltip: str or folium.Tooltip, default None
        Display a text when hovering over the object.
    **kwargs
        Other valid (possibly inherited) options. See:
        https://leafletjs.com/reference.html#rectangle

    """

    _template = Template(
        """
        {% macro script(this, kwargs) %}
            var {{this.get_name()}} = L.rectangle(
                {{ this.locations|tojson }},
                {{ this.options|tojson }}
            ).addTo({{this._parent.get_name()}});
        {% endmacro %}
        """
    )

    def __init__(self, bounds, popup=None, tooltip=None, **kwargs):
        super().__init__(bounds, popup=popup, tooltip=tooltip)
        self._name = "rectangle"
        self.options = path_options(line=True, **kwargs)


class Circle(Marker):
    """
    Class for drawing circle overlays on a map.

    It's an approximation and starts to diverge from a real circle closer to
    the poles (due to projection distortion).

    See :func:`folium.vector_layers.path_options` for the `Path` options.

    Parameters
    ----------
    location: tuple[float, float]
        Latitude and Longitude pair (Northing, Easting)
    popup: string or folium.Popup, default None
        Input text or visualization for object displayed when clicking.
    tooltip: str or folium.Tooltip, default None
        Display a text when hovering over the object.
    radius: float
        Radius of the circle, in meters.
    **kwargs
        Other valid (possibly inherited) options. See:
        https://leafletjs.com/reference.html#circle

    """

    _template = Template(
        """
        {% macro script(this, kwargs) %}
            var {{ this.get_name() }} = L.circle(
                {{ this.location|tojson }},
                {{ this.options|tojson }}
            ).addTo({{ this._parent.get_name() }});
        {% endmacro %}
        """
    )

    def __init__(self, location=None, radius=50, popup=None, tooltip=None, **kwargs):
        super().__init__(location, popup=popup, tooltip=tooltip)
        self._name = "circle"
        self.options = path_options(line=False, radius=radius, **kwargs)


class CircleMarker(Marker):
    """
    A circle of a fixed size with radius specified in pixels.

    See :func:`folium.vector_layers.path_options` for the `Path` options.

    Parameters
    ----------
    location: tuple[float, float]
        Latitude and Longitude pair (Northing, Easting)
    popup: string or folium.Popup, default None
        Input text or visualization for object displayed when clicking.
    tooltip: str or folium.Tooltip, default None
        Display a text when hovering over the object.
    radius: float, default 10
        Radius of the circle marker, in pixels.
    **kwargs
        Other valid (possibly inherited) options. See:
        https://leafletjs.com/reference.html#circlemarker

    """

    _template = Template(
        """
        {% macro script(this, kwargs) %}
            var {{ this.get_name() }} = L.circleMarker(
                {{ this.location|tojson }},
                {{ this.options|tojson }}
            ).addTo({{ this._parent.get_name() }});
        {% endmacro %}
        """
    )

    def __init__(self, location=None, radius=10, popup=None, tooltip=None, **kwargs):
        super().__init__(location, popup=popup, tooltip=tooltip)
        self._name = "CircleMarker"
        self.options = path_options(line=False, radius=radius, **kwargs)
