import json

from branca.element import MacroElement

from folium.elements import JSCSSMixin
from folium.folium import Map
from folium.template import Template
from folium.utilities import get_bounds, remove_empty


class TimestampedGeoJson(JSCSSMixin, MacroElement):
    """
    Creates a TimestampedGeoJson plugin from timestamped GeoJSONs to append
    into a map with Map.add_child.

    A geo-json is timestamped if:

    * it contains only features of types LineString, MultiPoint, MultiLineString,
      Polygon and MultiPolygon.
    * each feature has a 'times' property with the same length as the
      coordinates array.
    * each element of each 'times' property is a timestamp in ms since epoch,
      or in ISO string.

    Eventually, you may have Point features with a 'times' property being an
    array of length 1.

    Parameters
    ----------
    data: file, dict or str.
        The timestamped geo-json data you want to plot.

        * If file, then data will be read in the file and fully embedded in
          Leaflet's javascript.
        * If dict, then data will be converted to json and embedded in the
          javascript.
        * If str, then data will be passed to the javascript as-is.
    transition_time: int, default 200.
        The duration in ms of a transition from between timestamps.
    loop: bool, default True
        Whether the animation shall loop.
    auto_play: bool, default True
        Whether the animation shall start automatically at startup.
    add_last_point: bool, default True
        Whether a point is added at the last valid coordinate of a LineString.
    period: str, default "P1D"
        Used to construct the array of available times starting
        from the first available time. Format: ISO8601 Duration
        ex: 'P1M' 1/month, 'P1D' 1/day, 'PT1H' 1/hour, and 'PT1M' 1/minute
    duration: str, default None
        Period of time which the features will be shown on the map after their
        time has passed. If None, all previous times will be shown.
        Format: ISO8601 Duration
        ex: 'P1M' 1/month, 'P1D' 1/day, 'PT1H' 1/hour, and 'PT1M' 1/minute

    Examples
    --------
    >>> TimestampedGeoJson(
    ...     {
    ...         "type": "FeatureCollection",
    ...         "features": [
    ...             {
    ...                 "type": "Feature",
    ...                 "geometry": {
    ...                     "type": "LineString",
    ...                     "coordinates": [[-70, -25], [-70, 35], [70, 35]],
    ...                 },
    ...                 "properties": {
    ...                     "times": [1435708800000, 1435795200000, 1435881600000],
    ...                     "tooltip": "my tooltip text",
    ...                 },
    ...             }
    ...         ],
    ...     }
    ... )

    See https://github.com/socib/Leaflet.TimeDimension for more information.

    """

    _template = Template("""
        {% macro script(this, kwargs) %}
            L.Control.TimeDimensionCustom = L.Control.TimeDimension.extend({
                _getDisplayDateFormat: function(date){
                    var newdate = new moment(date);
                    console.log(newdate)
                    return newdate.format("{{this.date_options}}");
                }
            });
            {{this._parent.get_name()}}.timeDimension = L.timeDimension(
                {
                    period: {{ this.period|tojson }},
                }
            );
            var timeDimensionControl = new L.Control.TimeDimensionCustom(
                {{ this.options|tojavascript }}
            );
            {{this._parent.get_name()}}.addControl(this.timeDimensionControl);

            var geoJsonLayer = L.geoJson({{this.data}}, {
                    pointToLayer: function (feature, latLng) {
                        if (feature.properties.icon == 'marker') {
                            if(feature.properties.iconstyle){
                                return new L.Marker(latLng, {
                                    icon: L.icon(feature.properties.iconstyle)});
                            }
                            //else
                            return new L.Marker(latLng);
                        }
                        if (feature.properties.icon == 'circle') {
                            if (feature.properties.iconstyle) {
                                return new L.circleMarker(latLng, feature.properties.iconstyle)
                                };
                            //else
                            return new L.circleMarker(latLng);
                        }
                        //else

                        return new L.Marker(latLng);
                    },
                    style: function (feature) {
                        return feature.properties.style;
                    },
                    onEachFeature: function(feature, layer) {
                        if (feature.properties.popup) {
                        layer.bindPopup(feature.properties.popup);
                        }
                        if (feature.properties.tooltip) {
                        layer.bindTooltip(feature.properties.tooltip);
                        }
                    }
                })

            var {{this.get_name()}} = L.timeDimension.layer.geoJson(
                geoJsonLayer,
                {
                    updateTimeDimension: true,
                    addlastPoint: {{ this.add_last_point|tojson }},
                    duration: {{ this.duration }},
                }
            ).addTo({{this._parent.get_name()}});
        {% endmacro %}
        """)  # noqa

    default_js = [
        (
            "jquery3.7.1",
            "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js",
        ),
        (
            "jqueryui1.10.2",
            "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js",
        ),
        (
            "iso8601",
            "https://cdn.jsdelivr.net/npm/iso8601-js-period@0.2.1/iso8601.min.js",
        ),
        (
            "leaflet.timedimension",
            "https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.1/dist/leaflet.timedimension.min.js",
        ),
        # noqa
        (
            "moment",
            "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js",
        ),
    ]
    default_css = [
        (
            "highlight.js_css",
            "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/8.4/styles/default.min.css",
        ),
        (
            "leaflet.timedimension_css",
            "https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.1/dist/leaflet.timedimension.control.css",
        ),
    ]

    def __init__(
        self,
        data,
        transition_time=200,
        loop=True,
        auto_play=True,
        add_last_point=True,
        period="P1D",
        min_speed=0.1,
        max_speed=10,
        loop_button=False,
        date_options="YYYY-MM-DD HH:mm:ss",
        time_slider_drag_update=False,
        duration=None,
        speed_slider=True,
    ):
        super().__init__()
        self._name = "TimestampedGeoJson"

        if "read" in dir(data):
            self.embed = True
            self.data = data.read()
        elif type(data) is dict:
            self.embed = True
            self.data = json.dumps(data)
        else:
            self.embed = False
            self.data = data
        self.add_last_point = bool(add_last_point)
        self.period = period
        self.date_options = date_options
        self.duration = "undefined" if duration is None else '"' + duration + '"'

        self.options = remove_empty(
            position="bottomleft",
            min_speed=min_speed,
            max_speed=max_speed,
            auto_play=auto_play,
            loop_button=loop_button,
            time_slider_drag_update=time_slider_drag_update,
            speed_slider=speed_slider,
            player_options={
                "transitionTime": int(transition_time),
                "loop": loop,
                "startOver": True,
            },
        )

    def render(self, **kwargs):
        assert isinstance(
            self._parent, Map
        ), "TimestampedGeoJson can only be added to a Map object."
        super().render(**kwargs)

    def _get_self_bounds(self):
        """
        Computes the bounds of the object itself (not including it's children)
        in the form [[lat_min, lon_min], [lat_max, lon_max]].

        """
        if not self.embed:
            raise ValueError("Cannot compute bounds of non-embedded GeoJSON.")

        data = json.loads(self.data)
        if "features" not in data.keys():
            # Catch case when GeoJSON is just a single Feature or a geometry.
            if not (isinstance(data, dict) and "geometry" in data.keys()):
                # Catch case when GeoJSON is just a geometry.
                data = {"type": "Feature", "geometry": data}
            data = {"type": "FeatureCollection", "features": [data]}

        return get_bounds(data, lonlat=True)
