# -*- coding:utf-8 -*-
# Copyright (c) 2020 Himanshu Chauhan
# Copyright (c) 2020 Stephan Ehlers
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import time
from urllib.parse import urlencode

from libqtile.widget import base
from libqtile.widget.generic_poll_text import GenPollUrl

# See documentation: https://openweathermap.org/current
QUERY_URL = 'http://api.openweathermap.org/data/2.5/weather?'
DEFAULT_APP_ID = '7834197c2338888258f8cb94ae14ef49'


class OpenWeatherResponseError(Exception):
    def __init__(self, resp_code, err_str=None):
        self.resp_code = resp_code
        self.err_str = err_str


def flatten_json(obj):
    out = {}

    def __inner(_json, name=''):
        if type(_json) is dict:
            for key, value in _json.items():
                __inner(value, name + key + '_')
        elif type(_json) is list:
            for i in range(len(_json)):
                __inner(_json[i], name + str(i) + '_')
        else:
            out[name[:-1]] = _json
    __inner(obj)
    return out


def degrees_to_direction(degrees):
    val = int(degrees / 22.5 + .5)
    arr = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE',
           'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW',
           'NNW']
    return arr[(val % 16)]


class _OpenWeatherResponseParser:
    def __init__(self, response, dateformat, timeformat):
        self.dateformat = dateformat
        self.timeformat = timeformat
        self.data = self._parse(response)
        self._remap(self.data)
        if int(self.data['cod']) != 200:
            raise OpenWeatherResponseError(int(self.data['cod']))

    def _parse(self, response):
        return flatten_json(response)

    def _remap(self, data):
        data['location_lat'] = data.get('coord_lat', None)
        data['location_long'] = data.get('coord_lon', None)
        data['location_city'] = data.get('name', None)
        data['location_cityid'] = data.get('id', None)
        data['location_country'] = data.get('sys_country', None)
        data['sunrise'] = self._get_sunrise_time()
        data['sunset'] = self._get_sunset_time()
        data['isotime'] = self._get_dt()
        data['wind_direction'] = self._get_wind_direction()
        data['weather'] = data.get('weather_0_main', None)
        data['weather_details'] = data.get('weather_0_description', None)
        data['humidity'] = data.get('main_humidity', None)
        data['pressure'] = data.get('main_pressure', None)
        data['temp'] = data.get('main_temp', None)

    def _get_wind_direction(self):
        wd = self.data.get('wind_deg', None)
        if wd is None:
            return None
        return degrees_to_direction(wd)

    def _get_sunrise_time(self):
        dt = self.data.get('sys_sunrise', None)
        if dt is None:
            return None
        return time.strftime(self.timeformat, time.localtime(dt))

    def _get_sunset_time(self):
        dt = self.data.get('sys_sunset', None)
        if dt is None:
            return None
        return time.strftime(self.timeformat, time.localtime(dt))

    def _get_dt(self):
        dt = self.data.get('dt', None)
        if dt is None:
            return None
        return time.strftime(self.dateformat + self.timeformat, time.localtime(dt))


class OpenWeather(GenPollUrl):
    """ A weather widget, data provided by the OpenWeather API.

    Some format options:
        - location_city
        - location_cityid
        - location_country
        - location_lat
        - location_long

        - weather
        - weather_details
        - units_temperature
        - units_wind_speed
        - isotime
        - humidity
        - pressure
        - sunrise
        - sunset
        - temp
        - visibility
        - wind_speed
        - wind_deg
        - wind_direction

        - weather_0_icon  # See: https://openweathermap.org/weather-conditions; TODO: Use icons.
        - main_feels_like
        - main_temp_min
        - main_temp_max
        - clouds_all
    """
    orientations = base.ORIENTATION_HORIZONTAL
    defaults = [
        # One of (cityid, location, zip, coordinates) must be set.
        (
            'app_key',
            DEFAULT_APP_ID,
            """Open Weather access key. A default is provided, but
            for prolonged use obtaining your own is suggested:
            https://home.openweathermap.org/users/sign_up"""
        ),
        (
            'cityid',
            None,
            """City ID. Can be looked up on e.g.:
            https://openweathermap.org/find
            Takes precedence over location and coordinates.
            Note that this is not equal to a WOEID."""
        ),
        (
            'location',
            None,
            """Name of the city. Country name can be appended
            like cambridge,NZ. Takes precedence over zip-code."""
        ),
        (
            'zip',
            None,
            """Zip code (USA) or "zip code,country code" for
            other countries. E.g. 12345,NZ. Takes precedence over
            coordinates."""
        ),
        (
            'coordinates',
            None,
            """Dictionary containing latitude and longitude
               Example: coordinates={"longitude": "77.22",
                                     "latitude": "28.67"}"""
        ),
        (
            'format',
            '{location_city}: {main_temp} °{units_temperature} {humidity}% {weather_details}',
            'Display format'
        ),
        ('metric', True, 'True to use metric/C, False to use imperial/F'),
        (
            'dateformat',
            '%Y-%m-%d ',
            """Format for dates, defaults to ISO.
            For details see: https://docs.python.org/3/library/time.html#time.strftime"""
        ),
        (
            'timeformat',
            '%H:%M',
            """Format for times, defaults to ISO.
            For details see: https://docs.python.org/3/library/time.html#time.strftime"""
        ),
        ('language', 'en',
         """Language of response. List of languages supported can
         be seen at: https://openweathermap.org/current under
         Multilingual support""")
    ]

    def __init__(self, **config):
        GenPollUrl.__init__(self, **config)
        self.add_defaults(OpenWeather.defaults)

    @property
    def url(self):
        if not self.cityid and not self.location and not self.zip and not self.coordinates:
            return None

        params = {
            'appid': self.app_key or DEFAULT_APP_ID,
            'units': 'metric' if self.metric else 'imperial'
        }
        if self.cityid:
            params['id'] = self.cityid
        elif self.location:
            params['q'] = self.location
        elif self.zip:
            params['zip'] = self.zip
        elif self.coordinates:
            params['lat'] = self.coordinates['latitude']
            params['lon'] = self.coordinates['longitude']

        if self.language:
            params['lang'] = self.language

        return QUERY_URL + urlencode(params)

    def parse(self, response):
        try:
            rp = _OpenWeatherResponseParser(response, self.dateformat, self.timeformat)
        except OpenWeatherResponseError as e:
            return 'Error {}'.format(e.resp_code)

        data = rp.data
        data['units_temperature'] = 'C' if self.metric else 'F'
        data['units_wind_speed'] = 'Km/h' if self.metric else 'm/h'

        return self.format.format(**data)
