# -*- coding: utf-8 -*-

# Copyright(C) 2013      dud
#
# This file is part of a woob module.
#
# This woob module is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This woob module 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this woob module. If not, see <http://www.gnu.org/licenses/>.

from collections import OrderedDict

from woob.tools.backend import Module, BackendConfig
from woob.capabilities.base import UserError
from woob.capabilities.gauge import CapGauge, GaugeSensor, Gauge, GaugeMeasure, SensorNotFound
from woob.tools.value import Value, ValueBackendPassword

from .browser import VelibBrowser


__all__ = ['jcvelauxModule']


SENSOR_TYPES = OrderedDict([('available_bikes', 'Available bikes'),
                            ('available_bike_stands', 'Free stands'),
                            ('bike_stands', 'Total stands')])

CITIES = ("Rouen", "Toulouse", "Luxembourg", "Valence", "Stockholm",
          "Goteborg", "Santander", "Amiens", "Lillestrom", "Mulhouse", "Lyon",
          "Ljubljana", "Seville", "Namur", "Nancy", "Creteil", "Bruxelles-Capitale",
          "Cergy-Pontoise", "Vilnius", "Toyama", "Kazan", "Marseille", "Nantes",
          "Besancon")


class BikeMeasure(GaugeMeasure):
    def __repr__(self):
        return '<GaugeMeasure level=%d>' % self.level


class jcvelauxModule(Module, CapGauge):
    NAME = 'jcvelaux'
    DESCRIPTION = ('City bike renting availability information.\nCities: %s' %
                   ', '.join(CITIES))
    MAINTAINER = 'Herve Werner'
    EMAIL = 'dud225@hotmail.com'
    VERSION = '3.6'
    LICENSE = 'AGPLv3'

    BROWSER = VelibBrowser

    CONFIG = BackendConfig(Value('city', label='City', default='Lyon',
                                 choices=CITIES + ("ALL",)),
                           ValueBackendPassword('api_key', label='Optional API key',
                                                default='', noprompt=True))

    def __init__(self, *a, **kw):
        super(jcvelauxModule, self).__init__(*a, **kw)
        self.cities = None

    def create_default_browser(self):
        api_key = self.config['api_key'].get()
        return self.create_browser(api_key)

    def _make_gauge(self, info):
        gauge = Gauge(info['id'])
        gauge.name = info['name']
        gauge.city = info['city']
        gauge.object = 'bikes'
        return gauge

    def _make_sensor(self, sensor_type, info, gauge):
        id = '%s.%s' % (sensor_type, gauge.id)
        sensor = GaugeSensor(id)
        sensor.gaugeid = gauge.id
        sensor.name = SENSOR_TYPES[sensor_type]
        sensor.address = '%s' % info['address']
        sensor.longitude = info['longitude']
        sensor.latitude = info['latitude']
        sensor.history = []
        return sensor

    def _make_measure(self, sensor_type, info, gauge):
        id = '%s.%s' % (sensor_type, gauge.id)

        measure = BikeMeasure(id)
        measure.date = info['last_update']
        measure.level = float(info[sensor_type])
        return measure

    def _parse_gauge(self, info):
        gauge = self._make_gauge(info)
        gauge.sensors = []

        for type in SENSOR_TYPES:
            sensor = self._make_sensor(type, info, gauge)
            measure = self._make_measure(type, info, gauge)
            sensor.lastvalue = measure
            gauge.sensors.append(sensor)

        return gauge

    def _contract(self):
        contract = self.config.get('city').get()
        if contract.lower() == 'all':
            contract = None
        return contract

    def iter_gauges(self, pattern=None):
        if pattern is None:
            for jgauge in self.browser.get_station_list(contract=self._contract()):
                yield self._parse_gauge(jgauge)
        else:
            lowpattern = pattern.lower()
            for jgauge in self.browser.get_station_list(contract=self._contract()):
                gauge = self._parse_gauge(jgauge)
                if lowpattern in gauge.name.lower() or lowpattern in gauge.city.lower():
                    yield gauge

    def iter_sensors(self, gauge, pattern=None):
        if not isinstance(gauge, Gauge):
            gauge = self._get_gauge_by_id(gauge)
            if gauge is None:
                raise SensorNotFound()

        if pattern is None:
            for sensor in gauge.sensors:
                yield sensor
        else:
            lowpattern = pattern.lower()
            for sensor in gauge.sensors:
                if lowpattern in sensor.name.lower():
                    yield sensor

    def get_last_measure(self, sensor):
        if not isinstance(sensor, GaugeSensor):
            sensor = self._get_sensor_by_id(sensor)
        if sensor is None:
            raise SensorNotFound()
        return sensor.lastvalue

    def _fetch_cities(self):
        if self.cities:
            return

        self.cities = {}
        jcontract = self.browser.get_contracts_list()
        for jcontract in jcontract:
            for city in jcontract['cities']:
                self.cities[city.lower()] = jcontract['name']

    def _get_gauge_by_id(self, id):
        jgauge = self.browser.get_station_infos(id)
        if jgauge:
            return self._parse_gauge(jgauge)
        else:
            return None

    def _get_sensor_by_id(self, id):
        try:
            sensor_name, gauge_name, contract = id.split('.')
        except ValueError:
            raise UserError('Expected format NAME.ID.CITY for sensor: %r' % id)

        gauge = self._get_gauge_by_id('%s.%s' % (gauge_name, contract))
        if not gauge:
            raise SensorNotFound()
        for sensor in gauge.sensors:
            if sensor.id.lower() == id.lower():
                return sensor
