#!/usr/bin/env python
"""Module for calling Artist related last.fm web services API methods"""

__author__ = "Abhinav Sarkar <abhinav@abhinavsarkar.net>"
__version__ = "0.2"
__license__ = "GNU Lesser General Public License"
__package__ = "lastfm"

from lastfm.base import LastfmBase
from lastfm.mixins import Cacheable, Searchable, Sharable, Shoutable, Taggable
from lastfm.lazylist import lazylist
from lastfm.decorators import cached_property, top_property

class Artist(LastfmBase, Cacheable, Sharable, Shoutable, Searchable, Taggable):
    """A class representing an artist."""
    def init(self,
                 api,
                 name = None,
                 mbid = None,
                 url = None,
                 image = None,
                 streamable = None,
                 stats = None,
                 similar = None,
                 top_tags = None,
                 bio = None,
                 subject = None):
        """
        Create an Artist object by providing all the data related to it.
        
        @param api:             an instance of L{Api}
        @type api:              L{Api}
        @param name:            the artist name
        @type name:             L{str}
        @param mbid:            MBID of the artist
        @type mbid:             L{str}
        @param url:             URL of the artist on last.fm
        @type url:              L{str}
        @param image:           the images of the artist in various sizes
        @type image:            L{dict}
        @param streamable:      flag indicating if the artist is streamable from last.fm
        @type streamable:       L{bool}
        @param stats:           the artist statistics
        @type stats:            L{Stats}
        @param similar:         artists similar to the provided artist
        @type similar:          L{list} of L{Artist}
        @param top_tags:        top tags for the artist
        @type top_tags:         L{list} of L{Tag}
        @param bio:             biography of the artist
        @type bio:              L{Wiki}
        @param subject:         the subject to which this instance belongs to
        @type subject:          L{User} OR L{Artist} OR L{Tag} OR L{Track} OR L{WeeklyChart}
        
        @raise InvalidParametersError: If an instance of L{Api} is not provided as the first
                                       parameter then an Exception is raised.
        """
        if not isinstance(api, Api):
            raise InvalidParametersError("api reference must be supplied as an argument")
        Sharable.init(self, api)
        Shoutable.init(self, api)
        Taggable.init(self, api)
        
        self._api = api
        self._name = name
        self._mbid = mbid
        self._url = url
        self._image = image
        self._streamable = streamable
        self._stats = stats and Stats(
                             subject = self,
                             listeners = stats.listeners,
                             playcount = stats.playcount,
                             weight = stats.weight,
                             match = stats.match,
                             rank = stats.rank
                            )
        self._similar = similar
        self._top_tags = top_tags
        self._bio = bio and Wiki(
                         subject = self,
                         published = bio.published,
                         summary = bio.summary,
                         content = bio.content
                        )
        self._subject = subject

    @property
    def name(self):
        """
        name of the artist
        @rtype: L{str}
        """
        return self._name

    @property
    def mbid(self):
        """
        MBID of the artist
        @rtype: L{str}
        """
        if self._mbid is None:
            self._fill_info()
        return self._mbid

    @property
    def url(self):
        """
        url of the artist's page
        @rtype: L{str}
        """
        if self._url is None:
            self._fill_info()
        return self._url

    @property
    def image(self):
        """
        images of the artist
        @rtype: L{dict}
        """
        if self._image is None:
            self._fill_info()
        return self._image

    @property
    def streamable(self):
        """
        is the artist streamable on last.fm
        @rtype: L{bool}
        """
        if self._streamable is None:
            self._fill_info()
        return self._streamable

    @property
    def stats(self):
        """
        stats for the artist
        @rtype: L{Stats}
        """
        if self._stats is None:
            self._fill_info()
        return self._stats

    def get_similar(self, limit = None):
        """
        Get the artists similar to this artist.
        
        @param limit: the number of artists returned (optional)
        @type limit:  L{int}
        
        @return:      artists similar to this artist
        @rtype:       L{list} of L{Artist}
        """
        params = self._default_params({'method': 'artist.getSimilar'})
        if limit is not None:
            params.update({'limit': limit})
        data = self._api._fetch_data(params).find('similarartists')
        self._similar = [
                          Artist(
                                 self._api,
                                 subject = self,
                                 name = a.findtext('name'),
                                 mbid = a.findtext('mbid'),
                                 stats = Stats(
                                               subject = a.findtext('name'),
                                               match = float(a.findtext('match')),
                                               ),
                                 url = 'http://' + a.findtext('url'),
                                 image = {'large': a.findtext('image')}
                                 )
                          for a in data.findall('artist')
                          ]
        return self._similar[:]

    @property
    def similar(self):
        """
        artists similar to this artist
        @rtype: L{list} of L{Artist}
        """
        if self._similar is None or len(self._similar) < 6:
            return self.get_similar()
        return self._similar[:]

    @top_property("similar")
    def most_similar(self):
        """
        artist most similar to this artist
        @rtype: L{Artist}
        """
        pass

    @property
    def top_tags(self):
        """
        top tags for the artist
        @rtype: L{list} of L{Tag}
        """
        if self._top_tags is None or len(self._top_tags) < 6:
            params = self._default_params({'method': 'artist.getTopTags'})
            data = self._api._fetch_data(params).find('toptags')
            self._top_tags = [
                              Tag(
                                  self._api,
                                  subject = self,
                                  name = t.findtext('name'),
                                  url = t.findtext('url')
                                  )
                              for t in data.findall('tag')
                              ]
        return self._top_tags[:]

    @top_property("top_tags")
    def top_tag(self):
        """
        top tag for the artist
        @rtype: L{Tag}
        """
        pass

    @property
    def bio(self):
        """
        biography of the artist
        @rtype: L{Wiki}
        """
        if self._bio is None:
            self._fill_info()
        return self._bio

    @cached_property
    def events(self):
        """
        events for the artist
        @rtype: L{lazylist} of L{Event}
        """
        params = self._default_params({'method': 'artist.getEvents'})
        data = self._api._fetch_data(params).find('events')

        return [
                Event.create_from_data(self._api, e)
                for e in data.findall('event')
                ]

    @cached_property
    def top_albums(self):
        """
        top albums of the artist
        @rtype: L{list} of L{Album}
        """
        params = self._default_params({'method': 'artist.getTopAlbums'})
        data = self._api._fetch_data(params).find('topalbums')

        return [
                Album(
                     self._api,
                     subject = self,
                     name = a.findtext('name'),
                     artist = self,
                     mbid = a.findtext('mbid'),
                     url = a.findtext('url'),
                     image = dict([(i.get('size'), i.text) for i in a.findall('image')]),
                     stats = Stats(
                                   subject = a.findtext('name'),
                                   playcount = int(a.findtext('playcount')),
                                   rank = int(a.attrib['rank'])
                                   )
                     )
                for a in data.findall('album')
                ]

    @top_property("top_albums")
    def top_album(self):
        """
        top album of the artist
        @rtype: L{Album}
        """
        pass

    @cached_property
    def top_fans(self):
        """
        top fans of the artist
        @rtype: L{list} of L{User}
        """
        params = self._default_params({'method': 'artist.getTopFans'})
        data = self._api._fetch_data(params).find('topfans')
        return [
                User(
                     self._api,
                     subject = self,
                     name = u.findtext('name'),
                     url = u.findtext('url'),
                     image = dict([(i.get('size'), i.text) for i in u.findall('image')]),
                     stats = Stats(
                                   subject = u.findtext('name'),
                                   weight = int(u.findtext('weight'))
                                   )
                     )
                for u in data.findall('user')
                ]

    @top_property("top_fans")
    def top_fan(self):
        """
        top fan of the artist
        @rtype: L{User}"""
        pass

    @cached_property
    def top_tracks(self):
        """
        top tracks of the artist
        @rtype: L{list} of L{Track}
        """
        params = self._default_params({'method': 'artist.getTopTracks'})
        data = self._api._fetch_data(params).find('toptracks')
        return [
                Track(
                      self._api,
                      subject = self,
                      name = t.findtext('name'),
                      artist = self,
                      mbid = t.findtext('mbid'),
                      stats = Stats(
                                    subject = t.findtext('name'),
                                    playcount = int(t.findtext('playcount')),
                                    rank = int(t.attrib['rank'])
                                    ),
                      streamable = (t.findtext('streamable') == '1'),
                      full_track = (t.find('streamable').attrib['fulltrack'] == '1'),
                      image = dict([(i.get('size'), i.text) for i in t.findall('image')]),
                      )
                for t in data.findall('track')
                ]

    @top_property("top_tracks")
    def top_track(self):
        """
        topmost track of the artist
        @rtype: L{Track}
        """
        pass

    @staticmethod
    def get_info(api, artist = None, mbid = None):
        """
        Get the data for the artist.
        
        @param api:      an instance of L{Api}
        @type api:       L{Api}
        @param artist:   the name of the artist
        @type artist:    L{str}
        @param mbid:     MBID of the artist
        @type mbid:      L{str}
        
        @return:         an Artist object corresponding the provided artist name
        @rtype:          L{Artist}
        
        @raise lastfm.InvalidParametersError: Either artist or mbid parameter has to 
                                              be provided. Otherwise exception is raised.
        
        @note: Use the L{Api.get_artist} method instead of using this method directly.
        """
        data = Artist._fetch_data(api, artist, mbid)

        a = Artist(api, name = data.findtext('name'))
        a._fill_info()
        return a

    def _default_params(self, extra_params = {}):
        if not self.name:
            raise InvalidParametersError("artist has to be provided.")
        params = {'artist': self.name}
        params.update(extra_params)
        return params

    @staticmethod
    def _fetch_data(api,
                artist = None,
                mbid = None):
        params = {'method': 'artist.getInfo'}
        if not (artist or mbid):
            raise InvalidParametersError("either artist or mbid has to be given as argument.")
        if artist:
            params.update({'artist': artist})
        elif mbid:
            params.update({'mbid': mbid})
        return api._fetch_data(params).find('artist')

    def _fill_info(self):
        data = Artist._fetch_data(self._api, self.name)
        self._name = data.findtext('name')
        self._mbid = data.findtext('mbid')
        self._url = data.findtext('url')
        self._image = dict([(i.get('size'), i.text) for i in data.findall('image')])
        self._streamable = (data.findtext('streamable') == 1)
        if not self._stats:
            self._stats = Stats(
                             subject = self,
                             listeners = int(data.findtext('stats/listeners')),
                             playcount = int(data.findtext('stats/playcount'))
                             )
        self._similar = [
                          Artist(
                                 self._api,
                                 subject = self,
                                 name = a.findtext('name'),
                                 url = a.findtext('url'),
                                 image = dict([(i.get('size'), i.text) for i in a.findall('image')])
                                 )
                          for a in data.findall('similar/artist')
                          ]
        self._top_tags = [
                          Tag(
                              self._api,
                              subject = self,
                              name = t.findtext('name'),
                              url = t.findtext('url')
                              )
                          for t in data.findall('tags/tag')
                          ]
        self._bio = Wiki(
                         self,
                         published = data.findtext('bio/published').strip() and
                                        datetime(*(time.strptime(
                                                              data.findtext('bio/published').strip(),
                                                              '%a, %d %b %Y %H:%M:%S +0000'
                                                              )[0:6])),
                         summary = data.findtext('bio/summary'),
                         content = data.findtext('bio/content')
                         )

    @staticmethod
    def _search_yield_func(api, artist):
        return Artist(
                      api,
                      name = artist.findtext('name'),
                      mbid = artist.findtext('mbid'),
                      url = artist.findtext('url'),
                      image = dict([(i.get('size'), i.text) for i in artist.findall('image')]),
                      streamable = (artist.findtext('streamable') == '1'),
                      )
    @staticmethod
    def _hash_func(*args, **kwds):
        try:
            return hash(kwds['name'].lower())
        except KeyError:
            try:
                return hash(args[1].lower())
            except IndexError:
                raise InvalidParametersError("name has to be provided for hashing")

    def __hash__(self):
        return self.__class__._hash_func(name = self.name)

    def __eq__(self, other):
        if self.mbid and other.mbid:
            return self.mbid == other.mbid
        if self.url and other.url:
            return self.url == other.url
        return self.name == other.name

    def __lt__(self, other):
        return self.name < other.name

    def __repr__(self):
        return "<lastfm.Artist: %s>" % self._name

from datetime import datetime
import time

from lastfm.album import Album
from lastfm.api import Api
from lastfm.error import InvalidParametersError
from lastfm.event import Event
from lastfm.stats import Stats
from lastfm.tag import Tag
from lastfm.track import Track
from lastfm.user import User
from lastfm.wiki import Wiki
