# w.c.s. - web application for online forms
# Copyright (C) 2005-2016  Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import collections
import json

try:
    from PIL import Image
    from PIL.ExifTags import TAGS, GPSTAGS
except ImportError:
    Image = None

from django.utils.six.moves.urllib import parse as urlparse
from quixote import get_publisher

from ..qommon import _
from ..qommon import get_logger
from ..qommon.errors import ConnectionError
from ..qommon.form import RadiobuttonsWidget, ComputedExpressionWidget, CheckboxWidget
from ..qommon.misc import http_get_page, normalize_geolocation
from wcs.workflows import WorkflowStatusItem, register_item_class

class GeolocateWorkflowStatusItem(WorkflowStatusItem):
    description = N_('Geolocation')
    key = 'geolocate'
    category = 'formdata-action'

    method = 'address_string'
    address_string = None
    map_variable = None
    photo_variable = None
    overwrite = True

    def get_parameters(self):
        return ('method', 'address_string', 'map_variable', 'photo_variable',
                'overwrite', 'condition')

    def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
        super(GeolocateWorkflowStatusItem, self).add_parameters_widgets(
                form, parameters, prefix=prefix, formdef=formdef)
        methods = collections.OrderedDict(
                [('address_string', _('Address String')),
                 ('map_variable', _('Map Variable')),
                 ('photo_variable', _('Photo Variable'))])

        if Image is None:
            del methods['photo_variable']

        if 'method' in parameters:
            form.add(RadiobuttonsWidget, '%smethod' % prefix,
                    title=_('Method'),
                    options=list(methods.items()),
                    value=self.method,
                    attrs={'data-dynamic-display-parent': 'true'})
        if 'address_string' in parameters:
            form.add(ComputedExpressionWidget, '%saddress_string' % prefix, size=50,
                    title=_('Address String'), value=self.address_string,
                    attrs={
                        'data-dynamic-display-child-of': '%smethod' % prefix,
                        'data-dynamic-display-value': methods.get('address_string'),
                    })
        if 'map_variable' in parameters:
            form.add(ComputedExpressionWidget, '%smap_variable' % prefix, size=50,
                    title=_('Map Variable'), value=self.map_variable,
                    attrs={
                        'data-dynamic-display-child-of': '%smethod' % prefix,
                        'data-dynamic-display-value': methods.get('map_variable'),
                    })
        if 'photo_variable' in parameters:
            form.add(ComputedExpressionWidget, '%sphoto_variable' % prefix, size=50,
                    title=_('Photo Variable'), value=self.photo_variable,
                    attrs={
                        'data-dynamic-display-child-of': '%smethod' % prefix,
                        'data-dynamic-display-value': methods.get('photo_variable'),
                    })
        if 'overwrite' in parameters:
            form.add(CheckboxWidget, '%soverwrite' % prefix,
                    title=_('Overwrite existing geolocation'),
                    value=self.overwrite)

    def perform(self, formdata):
        if not self.method:
            return
        if not formdata.formdef.geolocations:
            return
        geolocation_point = list(formdata.formdef.geolocations.keys())[0]
        if not formdata.geolocations:
            formdata.geolocations = {}
        if formdata.geolocations.get(geolocation_point) and not self.overwrite:
            return
        location = getattr(self, 'geolocate_' + self.method)(formdata)
        if location:
            formdata.geolocations[geolocation_point] = location
            formdata.store()

    def geolocate_address_string(self, formdata):
        try:
            address = self.compute(self.address_string, raises=True)
        except Exception as e:
            get_logger().error('error in template for address string [%r]', e)
            return

        if not address:
            get_logger().debug('error geolocating string (empty string)')
            return
        url = get_publisher().get_geocoding_service_url()
        if '?' in url:
            url += '&'
        else:
            url += '?'
        url += 'q=%s' % urlparse.quote(address)
        url += '&format=json'
        url += '&accept-language=%s' % (get_publisher().get_site_language() or 'en')

        try:
            response, status, data, auth_header = http_get_page(url, raise_on_http_errors=True)
        except ConnectionError as e:
            get_logger().error('error calling geocoding service [%s]', str(e))
            return
        try:
            data = json.loads(data)
        except ValueError:
            get_logger().error('non-JSON response from geocoding service')
            return
        if len(data) == 0:
            get_logger().info('error finding location')
            return
        coords = data[0]
        return normalize_geolocation({'lon': coords['lon'], 'lat': coords['lat']})

    def geolocate_map_variable(self, formdata):
        value = self.compute(self.map_variable)
        if not value:
            return

        try:
            lat, lon = str(value).split(';')
            lat_lon = normalize_geolocation({'lon': lon, 'lat': lat})
        except Exception as e:
            get_logger().error('error geolocating from map variable [%r]', e)
            return

        return lat_lon

    def geolocate_photo_variable(self, formdata):
        if Image is None:
            get_logger().error('error geolocating from file (missing PIL)')
            return

        value = self.compute(self.photo_variable)
        if not value:
            get_logger().debug('error geolocating from photo, no image')
            return

        if not hasattr(value, 'get_file_pointer'):
            get_logger().error('error geolocating from photo, invalid variable')
            return

        try:
            image = Image.open(value.get_file_pointer())
        except IOError:
            get_logger().error('error geolocating from photo, invalid file')
            return

        try:
            exif_data = image._getexif()
        except AttributeError:
            get_logger().debug('error geolocating from photo, failed to get EXIF data')
            return

        if exif_data:
            gps_info = exif_data.get(0x8825)
            if gps_info:
                # lat_ref will be N/S, lon_ref wil l be E/W
                # lat and lon will be degrees/minutes/seconds (value, denominator),
                # like ((33, 1), (51, 1), (2191, 100))
                lat, lon = gps_info[2], gps_info[4]
                try:
                    lat_ref = gps_info[1]
                except KeyError:
                    lat_ref = 'N'
                try:
                    lon_ref = gps_info[3]
                except KeyError:
                    lon_ref = 'E'
                lat = (1.0*lat[0][0]/lat[0][1] + 1.0*lat[1][0]/lat[1][1]/60 + 1.0*lat[2][0]/lat[2][1]/3600)
                lon = (1.0*lon[0][0]/lon[0][1] + 1.0*lon[1][0]/lon[1][1]/60 + 1.0*lon[2][0]/lon[2][1]/3600)
                if lat_ref == 'S':
                    lat = -lat
                if lon_ref == 'W':
                    lon = -lon
                return normalize_geolocation({'lon': lon, 'lat': lat})
        return


register_item_class(GeolocateWorkflowStatusItem)
