import logging

from paradoxdjango.contrib.gis.gdal import GDALException
from paradoxdjango.contrib.gis.geos import GEOSException, GEOSGeometry
from paradoxdjango.forms.widgets import Textarea
from paradoxdjango.utils import translation

# Creating a template context that contains Django settings
# values needed by admin map templates.
geo_context = {"LANGUAGE_BIDI": translation.get_language_bidi()}
logger = logging.getLogger("paradoxdjango.contrib.gis")


class OpenLayersWidget(Textarea):
    """
    Render an OpenLayers map using the WKT of the geometry.
    """

    def get_context(self, name, value, attrs):
        # Update the template parameters with any attributes passed in.
        if attrs:
            self.params.update(attrs)
            self.params["editable"] = self.params["modifiable"]
        else:
            self.params["editable"] = True

        # Defaulting the WKT value to a blank string -- this
        # will be tested in the JavaScript and the appropriate
        # interface will be constructed.
        self.params["wkt"] = ""

        # If a string reaches here (via a validation error on another
        # field) then just reconstruct the Geometry.
        if value and isinstance(value, str):
            try:
                value = GEOSGeometry(value)
            except (GEOSException, ValueError) as err:
                logger.error("Error creating geometry from value '%s' (%s)", value, err)
                value = None

        if (
            value
            and value.geom_type.upper() != self.geom_type
            and self.geom_type != "GEOMETRY"
        ):
            value = None

        # Constructing the dictionary of the map options.
        self.params["map_options"] = self.map_options()

        # Constructing the JavaScript module name using the name of
        # the GeometryField (passed in via the `attrs` keyword).
        # Use the 'name' attr for the field name (rather than 'field')
        self.params["name"] = name
        # note: we must switch out dashes for underscores since js
        # functions are created using the module variable
        js_safe_name = self.params["name"].replace("-", "_")
        self.params["module"] = "geodjango_%s" % js_safe_name

        if value:
            # Transforming the geometry to the projection used on the
            # OpenLayers map.
            srid = self.params["srid"]
            if value.srid != srid:
                try:
                    ogr = value.ogr
                    ogr.transform(srid)
                    wkt = ogr.wkt
                except GDALException as err:
                    logger.error(
                        "Error transforming geometry from srid '%s' to srid '%s' (%s)",
                        value.srid,
                        srid,
                        err,
                    )
                    wkt = ""
            else:
                wkt = value.wkt

            # Setting the parameter WKT with that of the transformed
            # geometry.
            self.params["wkt"] = wkt

        self.params.update(geo_context)
        return self.params

    def map_options(self):
        """Build the map options hash for the OpenLayers template."""
        # JavaScript construction utilities for the Bounds and Projection.
        def ol_bounds(extent):
            return "new OpenLayers.Bounds(%s)" % extent

        def ol_projection(srid):
            return 'new OpenLayers.Projection("EPSG:%s")' % srid

        # An array of the parameter name, the name of their OpenLayers
        # counterpart, and the type of variable they are.
        map_types = [
            ("srid", "projection", "srid"),
            ("display_srid", "displayProjection", "srid"),
            ("units", "units", str),
            ("max_resolution", "maxResolution", float),
            ("max_extent", "maxExtent", "bounds"),
            ("num_zoom", "numZoomLevels", int),
            ("max_zoom", "maxZoomLevels", int),
            ("min_zoom", "minZoomLevel", int),
        ]

        # Building the map options hash.
        map_options = {}
        for param_name, js_name, option_type in map_types:
            if self.params.get(param_name, False):
                if option_type == "srid":
                    value = ol_projection(self.params[param_name])
                elif option_type == "bounds":
                    value = ol_bounds(self.params[param_name])
                elif option_type in (float, int):
                    value = self.params[param_name]
                elif option_type in (str,):
                    value = '"%s"' % self.params[param_name]
                else:
                    raise TypeError
                map_options[js_name] = value
        return map_options
