from __future__ import unicode_literals

from functools import total_ordering

from django.contrib.gis.geos import (
    LinearRing, LineString, Point, Polygon, fromstr,
)
from django.utils import six
from django.utils.encoding import python_2_unicode_compatible
from django.utils.html import html_safe


@html_safe
@python_2_unicode_compatible
class GEvent(object):
    """
    A Python wrapper for the Google GEvent object.

    Events can be attached to any object derived from GOverlayBase with the
    add_event() call.

    For more information please see the Google Maps API Reference:
     https://developers.google.com/maps/documentation/javascript/reference#event

    Example:

      from django.shortcuts import render
      from django.contrib.gis.maps.google import GoogleMap, GEvent, GPolyline

      def sample_request(request):
          polyline = GPolyline('LINESTRING(101 26, 112 26, 102 31)')
          event = GEvent('click',
            'function() { location.href = "http://www.google.com"}')
          polyline.add_event(event)
          return render(request, 'mytemplate.html', {
              'google': GoogleMap(polylines=[polyline]),
          })
    """

    def __init__(self, event, action):
        """
        Initializes a GEvent object.

        Parameters:

        event:
          string for the event, such as 'click'. The event must be a valid
          event for the object in the Google Maps API.
          There is no validation of the event type within Django.

        action:
          string containing a Javascript function, such as
          'function() { location.href = "newurl";}'
          The string must be a valid Javascript function. Again there is no
          validation fo the function within Django.
        """
        self.event = event
        self.action = action

    def __str__(self):
        "Returns the parameter part of a GEvent."
        return '"%s", %s' % (self.event, self.action)


@html_safe
@python_2_unicode_compatible
class GOverlayBase(object):
    def __init__(self):
        self.events = []

    def latlng_from_coords(self, coords):
        "Generates a JavaScript array of GLatLng objects for the given coordinates."
        return '[%s]' % ','.join('new GLatLng(%s,%s)' % (y, x) for x, y in coords)

    def add_event(self, event):
        "Attaches a GEvent to the overlay object."
        self.events.append(event)

    def __str__(self):
        "The string representation is the JavaScript API call."
        return '%s(%s)' % (self.__class__.__name__, self.js_params)


class GPolygon(GOverlayBase):
    """
    A Python wrapper for the Google GPolygon object.  For more information
    please see the Google Maps API Reference:
     https://developers.google.com/maps/documentation/javascript/reference#Polygon
    """
    def __init__(self, poly,
                 stroke_color='#0000ff', stroke_weight=2, stroke_opacity=1,
                 fill_color='#0000ff', fill_opacity=0.4):
        """
        The GPolygon object initializes on a GEOS Polygon or a parameter that
        may be instantiated into GEOS Polygon.  Please note that this will not
        depict a Polygon's internal rings.

        Keyword Options:

          stroke_color:
            The color of the polygon outline. Defaults to '#0000ff' (blue).

          stroke_weight:
            The width of the polygon outline, in pixels.  Defaults to 2.

          stroke_opacity:
            The opacity of the polygon outline, between 0 and 1.  Defaults to 1.

          fill_color:
            The color of the polygon fill.  Defaults to '#0000ff' (blue).

          fill_opacity:
            The opacity of the polygon fill.  Defaults to 0.4.
        """
        if isinstance(poly, six.string_types):
            poly = fromstr(poly)
        if isinstance(poly, (tuple, list)):
            poly = Polygon(poly)
        if not isinstance(poly, Polygon):
            raise TypeError('GPolygon may only initialize on GEOS Polygons.')

        # Getting the envelope of the input polygon (used for automatically
        # determining the zoom level).
        self.envelope = poly.envelope

        # Translating the coordinates into a JavaScript array of
        # Google `GLatLng` objects.
        self.points = self.latlng_from_coords(poly.shell.coords)

        # Stroke settings.
        self.stroke_color, self.stroke_opacity, self.stroke_weight = stroke_color, stroke_opacity, stroke_weight

        # Fill settings.
        self.fill_color, self.fill_opacity = fill_color, fill_opacity

        super(GPolygon, self).__init__()

    @property
    def js_params(self):
        return '%s, "%s", %s, %s, "%s", %s' % (self.points, self.stroke_color, self.stroke_weight, self.stroke_opacity,
                                               self.fill_color, self.fill_opacity)


class GPolyline(GOverlayBase):
    """
    A Python wrapper for the Google GPolyline object.  For more information
    please see the Google Maps API Reference:
     https://developers.google.com/maps/documentation/javascript/reference#Polyline
    """
    def __init__(self, geom, color='#0000ff', weight=2, opacity=1):
        """
        The GPolyline object may be initialized on GEOS LineStirng, LinearRing,
        and Polygon objects (internal rings not supported) or a parameter that
        may instantiated into one of the above geometries.

        Keyword Options:

          color:
            The color to use for the polyline.  Defaults to '#0000ff' (blue).

          weight:
            The width of the polyline, in pixels.  Defaults to 2.

          opacity:
            The opacity of the polyline, between 0 and 1.  Defaults to 1.
        """
        # If a GEOS geometry isn't passed in, try to construct one.
        if isinstance(geom, six.string_types):
            geom = fromstr(geom)
        if isinstance(geom, (tuple, list)):
            geom = Polygon(geom)
        # Generating the lat/lng coordinate pairs.
        if isinstance(geom, (LineString, LinearRing)):
            self.latlngs = self.latlng_from_coords(geom.coords)
        elif isinstance(geom, Polygon):
            self.latlngs = self.latlng_from_coords(geom.shell.coords)
        else:
            raise TypeError('GPolyline may only initialize on GEOS LineString, LinearRing, and/or Polygon geometries.')

        # Getting the envelope for automatic zoom determination.
        self.envelope = geom.envelope
        self.color, self.weight, self.opacity = color, weight, opacity
        super(GPolyline, self).__init__()

    @property
    def js_params(self):
        return '%s, "%s", %s, %s' % (self.latlngs, self.color, self.weight, self.opacity)


@total_ordering
class GIcon(object):
    """
    Creates a GIcon object to pass into a Gmarker object.

    The keyword arguments map to instance attributes of the same name. These,
    in turn, correspond to a subset of the attributes of the official GIcon
    javascript object:

    https://developers.google.com/maps/documentation/javascript/reference#Icon

    Because a Google map often uses several different icons, a name field has
    been added to the required arguments.

    Required Arguments:
        varname:
            A string which will become the basis for the js variable name of
            the marker, for this reason, your code should assign a unique
            name for each GIcon you instantiate, otherwise there will be
            name space collisions in your javascript.

    Keyword Options:
        image:
            The url of the image to be used as the icon on the map defaults
            to 'G_DEFAULT_ICON'

        iconsize:
            a tuple representing the pixel size of the foreground (not the
            shadow) image of the icon, in the format: (width, height) ex.:

            GIcon('fast_food',
                  image="/media/icon/star.png",
                  iconsize=(15,10))

            Would indicate your custom icon was 15px wide and 10px height.

        shadow:
            the url of the image of the icon's shadow

        shadowsize:
            a tuple representing the pixel size of the shadow image, format is
            the same as ``iconsize``

        iconanchor:
            a tuple representing the pixel coordinate relative to the top left
            corner of the icon image at which this icon is anchored to the map.
            In (x, y) format.  x increases to the right in the Google Maps
            coordinate system and y increases downwards in the Google Maps
            coordinate system.)

        infowindowanchor:
            The pixel coordinate relative to the top left corner of the icon
            image at which the info window is anchored to this icon.
    """
    def __init__(self, varname, image=None, iconsize=None,
                 shadow=None, shadowsize=None, iconanchor=None,
                 infowindowanchor=None):
        self.varname = varname
        self.image = image
        self.iconsize = iconsize
        self.shadow = shadow
        self.shadowsize = shadowsize
        self.iconanchor = iconanchor
        self.infowindowanchor = infowindowanchor

    def __eq__(self, other):
        return self.varname == other.varname

    def __lt__(self, other):
        return self.varname < other.varname

    def __hash__(self):
        # XOR with hash of GIcon type so that hash('varname') won't
        # equal hash(GIcon('varname')).
        return hash(self.__class__) ^ hash(self.varname)


class GMarker(GOverlayBase):
    """
    A Python wrapper for the Google GMarker object.  For more information
    please see the Google Maps API Reference:
     https://developers.google.com/maps/documentation/javascript/reference#Marker

    Example:

      from django.shortcuts import render
      from django.contrib.gis.maps.google.overlays import GMarker, GEvent

      def sample_request(request):
          marker = GMarker('POINT(101 26)')
          event = GEvent('click',
                         'function() { location.href = "http://www.google.com"}')
          marker.add_event(event)
          return render(request, 'mytemplate.html', {
              'google': GoogleMap(markers=[marker]),
          })
    """
    def __init__(self, geom, title=None, draggable=False, icon=None):
        """
        The GMarker object may initialize on GEOS Points or a parameter
        that may be instantiated into a GEOS point.  Keyword options map to
        GMarkerOptions -- so far only the title option is supported.

        Keyword Options:
         title:
           Title option for GMarker, will be displayed as a tooltip.

         draggable:
           Draggable option for GMarker, disabled by default.
        """
        # If a GEOS geometry isn't passed in, try to construct one.
        if isinstance(geom, six.string_types):
            geom = fromstr(geom)
        if isinstance(geom, (tuple, list)):
            geom = Point(geom)
        if isinstance(geom, Point):
            self.latlng = self.latlng_from_coords(geom.coords)
        else:
            raise TypeError('GMarker may only initialize on GEOS Point geometry.')
        # Getting the envelope for automatic zoom determination.
        self.envelope = geom.envelope
        # TODO: Add support for more GMarkerOptions
        self.title = title
        self.draggable = draggable
        self.icon = icon
        super(GMarker, self).__init__()

    def latlng_from_coords(self, coords):
        return 'new GLatLng(%s,%s)' % (coords[1], coords[0])

    def options(self):
        result = []
        if self.title:
            result.append('title: "%s"' % self.title)
        if self.icon:
            result.append('icon: %s' % self.icon.varname)
        if self.draggable:
            result.append('draggable: true')
        return '{%s}' % ','.join(result)

    @property
    def js_params(self):
        return '%s, %s' % (self.latlng, self.options())
