# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Author: Philippe Normand <philippe@fluendo.com>

"""
Shoutcast Resource Provider
"""

import re
import string

from twisted.web2 import responsecode
from twisted.web2.stream import BufferedStream
from twisted.internet import defer, task

from elisa.core.media_uri import MediaUri
from elisa.core.components.resource_provider import ResourceProvider
from elisa.plugins.http_client.http_client import ElisaAdvancedHttpClient
from elisa.plugins.shoutcast.countries import countries
from elisa.plugins.shoutcast import models
from elisa.plugins.base.models.media import PlayableModel
from elisa.core.components.model import Model

from elisa.extern.coherence.et import parse_xml

class ShoutcastResource(ResourceProvider):
    """
    This class implements support for the shoutcast servers and playlist
    """
    base_url = "www.shoutcast.com"

    pl_file_getter = re.compile('File.=(.*?)\n')
    pl_title_getter = re.compile('Title.=(.*?)\n')

    interesting_genres = ["Alternative", "Hardcore", "Industrial", "Punk",
                          "Blues", "Folk", "Classical",
                          "Country", "Electronic", "Ambient",
                          "House", "Trance", "Techno", "Hiphop",
                          "Jazz", "Latin", "Pop",
                          "Metal", "Rnb", "Classic", "Contemporary",
                          "Funk", "Gospel", "World",
                          "Reggae", "Instrumental"]

    decades = ["50s", "60s", "70s","80s", "90s"]

    default_config = {'genres': interesting_genres,
                      'decades': decades
                      }

    supported_uri = "shoutcast://.*"

    def __init__(self):
        super(ShoutcastResource, self).__init__()
        base_url = self.base_url
        server_uri = MediaUri('http://%s' % base_url)
        port = server_uri.port or 80
        self._client = ElisaAdvancedHttpClient(host=server_uri.host,
                                               port=port)
        self._all_genres_url = str("http://%s/sbin/newxml.phtml" % base_url)
        self._all_genres = []

    def clean(self):
        if not self._client._closed:
            dfr = self._client.close()
        else:
            dfr = defer.succeed(None)

        def parent_clean(result):
            return super(ShoutcastResource, self).clean()
        
        dfr.addCallback(parent_clean)
        return dfr

    def _get_genres(self):
        if self._all_genres:
            dfr = defer.succeed(self._all_genres)
        else:
            def received_all_data(xml_data):
                xml = parse_xml(xml_data)
                for elem in xml.findall('genre'):
                    name = elem.get('name')
                    self._all_genres.append(name)
                return self._all_genres

            dfr = self._client.request(self._all_genres_url)
            dfr.addCallback(self._request_callback)
            dfr.addCallback(received_all_data)
        return dfr

    def _load_decades(self, model):

        def load_decades_iter(model):
            for decade in self.config['decades']:
                genre = models.ShoutcastGenreModel()
                genre.name = unicode(decade)
                genre.uri = MediaUri('shoutcast://genre%s' % decade)
                model.genres.append(genre)
                yield model

        dfr = task.coiterate(load_decades_iter(model))
        dfr.addCallback(lambda gen: model)
        return dfr

    def _load_countries(self, model):

        def load_countries_iter(genres, model):
            for country_name_code in countries:
                country_name = country_name_code['name'].lower()
                i = 0
                while i < len(genres):
                    name = genres[i]
                    if name.lower().find(country_name) > -1:
                        uri = MediaUri('shoutcast://genre/%s' % name)
                        genre = models.ShoutcastGenreModel()
                        genre.name = unicode(name)
                        genre.uri = uri
                        model.genres.append(genre)
                        del genres[i]
                        break

                    i+= 1
                yield model

        dfr = self._get_genres()
        dfr.addCallback(lambda genres: task.coiterate(load_countries_iter(genres, model)))
        dfr.addCallback(lambda gen: model)
        return dfr

    def _load_all_genres(self, model):

        def load_genres_iter(genres, model):
            for name in genres:
                uri = MediaUri('shoutcast://genre/%s' % name)
                genre = models.ShoutcastGenreModel()
                genre.name = unicode(name)
                genre.uri = uri
                model.genres.append(genre)
                yield model

        dfr = self._get_genres()
        dfr.addCallback(lambda genres: task.coiterate(load_genres_iter(genres, model)))
        dfr.addCallback(lambda gen: model)
        return dfr

    def _load_genres(self, model):

        def load_genres_iter(genres, model):
            for name in self.config['genres']:
                if name in genres:
                    uri = MediaUri('shoutcast://genre/%s' % name)
                    genre = models.ShoutcastGenreModel()
                    genre.name = unicode(name)
                    genre.uri = uri
                    model.genres.append(genre)
                yield model

        dfr = self._get_genres()
        dfr.addCallback(lambda genres: task.coiterate(load_genres_iter(genres, model)))
        dfr.addCallback(lambda gen: model)
        return dfr

    def _load_stations(self, genre_model):

        def received_all_data(xml_data):
            xml = parse_xml(xml_data)
            tune_in = xml.find('tunein')
            base = tune_in.get('base')

            for elem in xml.findall('station'):
                station = self._radio_station_from_xml(elem, tunein_base=base)
                genre_model.stations.append(station)
            return genre_model

        if len(genre_model.stations):
            dfr = defer.succeed(genre_model)
        else:
            url = '%s?genre=%s' % (self._all_genres_url, genre_model.name)
            url = str(url)

            dfr = self._client.request(url)
            dfr.addCallback(self._request_callback)
            dfr.addCallback(received_all_data)
        return dfr

    def _radio_station_from_xml(self, elem, tunein_base=None):
        tunein_base = tunein_base or ""
        uri = MediaUri('shoutcast://station/%s' % tunein_base)
        uri.set_param('station_id', elem.get('id'))

        station = models.ShoutcastRadioStationModel()
        station.genre = unicode(elem.get('genre'))
        station.get_playable = uri
        station.bitrate = unicode(elem.get('br', ''))
        station.currently_playing = unicode(elem.get('ct', ''))
        station.listeners_count = int(elem.get('lc','0'))

        name = unicode(elem.get('name'))
        old_name = name

        # remove duplicate parenthesis, brackets and curly braces
        for sub_crap in ("\(", "\)", "\[", "\]", "\{", "\}", "\:", "\."):
            name = re.sub("%s+" % sub_crap,
                          r"%s" % sub_crap[1],
                          name)

        # remove everything between parenthesis, brackets and curly braces
        for left_crap, right_crap in (("\(", "\)"), ("\[", "\]"), ("\{", "\}")):
            name = re.sub("%s(.*)%s" % (left_crap, right_crap),
                          "", name)

        # transform strings like "S o m e string" to "Some string"
        # strings like "Somestring With S p A C E S" to "Somestring With SpACES"
        words = []
        building = []
        for word in name.split():
            if len(word) > 1:
                if building:
                    words.append(''.join(building))
                    building = []
                words.append(word)
            else:
                building.append(word)
        if building:
            words.append(''.join(building))
        name = ' '.join(words)

        # remove punctuation
        for crap in string.punctuation:
            name = name.replace(crap, "")

        station.name = string.capwords(name)

        if not station.name:
            # there was only crap in that station name, nothing usable came out,
            # let's put the old name.
            station.name = old_name

        return station

    def _load_station(self, url_path, station_id, model):

        def received_all_data(data):
            file_match = self.pl_file_getter.search(data)
            if file_match:
                link = file_match.groups()[0]
                model.uri = MediaUri(link)

            title_match = self.pl_title_getter.search(data)
            if title_match:
                model.title = title_match.groups()[0]
            return model

        url = "http://%s%s?id=%s" % (self.base_url, url_path, station_id)
        url = str(url)
        dfr = self._client.request(url)
        dfr.addCallback(self._request_callback)
        dfr.addCallback(received_all_data)
        return dfr

    def _search(self, query, model):
        def received_all_data(xml_data):
            xml = parse_xml(xml_data)
            tune_in = xml.find('tunein')
            base = tune_in.get('base')

            for elem in xml.findall('station'):
                station = self._radio_station_from_xml(elem, tunein_base=base)
                model.stations.append(station)
            return model

        url = '%s?search=%s' % (self._all_genres_url, query)
        url = str(url)

        dfr = self._client.request(url)
        dfr.addCallback(self._request_callback)
        dfr.addCallback(received_all_data)
        return dfr


    def _request_callback(self, response):
        dfr = BufferedStream(response.stream).readExactly()
        return dfr

    def get(self, uri, context_model=None):
        """

        /genres (-> list of user-configured genres)
        /genre/genre_name (-> list of streams)
        /station/path/to/stream/base?id=station_id

        /popular_stations (-> list of stations)
        /stations_by_country (-> list of countries)
        /country/name (-> list of stations)
        /decades (-> list of user-configured decades)
        /decade/name (-> list of stations)

        /search/query (-> list of stations)

        /all_genres (-> all genres available on shoutcast)
        """
        action = uri.host
        parameters = [p for p in uri.path[1:].split('/') if p]

        if action == 'popular_stations':
            model, dfr = self.get(MediaUri('shoutcast://genre/Top500'),
                                  context_model=context_model)
        elif action == 'stations_by_country':
            model = models.ShoutcastGenresModel()
            dfr = self._load_countries(model)
        elif action == 'decades':
            model = models.ShoutcastGenresModel()
            dfr = self._load_decades(model)
        elif action == 'genres':
            # list user-configured genres
            model = models.ShoutcastGenresModel()
            dfr = self._load_genres(model)
        elif action == 'all_genres':
            # list all genres
            model = models.ShoutcastGenresModel()
            dfr = self._load_all_genres(model)

        elif action == 'genre':
            # list all stations of the genre
            genre = parameters[0]
            model = models.ShoutcastGenreModel()
            model.name = genre
            dfr = self._load_stations(model)
        elif action == 'station':
            model = PlayableModel()
            dfr = self._load_station(uri.path, uri.get_param('station_id'),
                                     model)
        elif action == 'search':
            model = Model()
            model.stations = []
            query = parameters[0]
            dfr = self._search(query, model)
        else:
            model = None
            dfr = defer.fail(NotImplementedError())

        return (model, dfr)
