#!/usr/bin/env python
# -*- coding: utf-8 -*-

import lxml.html
import requests

import re

FNAME = "USERNAME"


class LfmUserError(Exception): pass

class LastFmRetriever(object):
    def __init__(self, db=None):
        self.__db = self.__dbInit(db)
        self.__username = None

        self.__username = self.__readUsername(FNAME)

    def __dbInit(self, db): # TODO Currently this isn't being utilized
        if db == None:
            self.db = db

    def __readUsername(self, fname):
        """
        First line of the file must include a username.
        """
        data = list()

        with open(fname, "r") as f:
            for line in f:
                data.append(line.replace("\n", ""))

        if len(data) == 1:
            return data[0]

    def calculatePopularity(self, allbands, thisband):
        totalplaycount = sum([artist.playcount for artist in allbands])
        return float(thisband.playcount) / totalplaycount * 1000

    def nonAPIparser(self):
        """
        New LastFM is broken in many ways. This method omits their API and
        parses data directly from the website.
        """
        pageidx = 1
        p = re.compile("[0-9,]+")
        libraryurl = "http://www.last.fm/user/%s/library/artists?page=1" % \
                     (self.__username)
        html = requests.get(libraryurl)
        site = lxml.html.fromstring(html.content)

        pagestag = site.xpath('//ul[@class="pagination"]' \
                              +'/li[@class="pages"]/text()')
        pagescount = re.findall("[0-9,]+", " ".join(pagestag)) # [1, n]
        if len(pagescount) == 0:
            pagescount = 1 # Otherwise loop would be skipped
        else:
            pagescount = int(pagescount[1])

        # Go through the all pages
        while pageidx <= pagescount:
            for libitem in site.xpath('//tbody/tr'):
                artist = libitem.xpath('./td[@class="chartlist-name"]/span/a/text()')
                artist = " ".join(artist)

                # Bail early, no need to parse further.
                # Since we use fairly eager matching, LastFM's newest changes
                # hit in this rule too.
                # The mismatch is caused by "Listening History" charts.
                if artist == "":
                    continue

                playcount = libitem.xpath('./td[@class="chartlist-countbar"]' \
                                        + '/span/span/a/text()')
                playcount = " ".join(playcount)
                pcount = re.search(p, playcount)
                pcount = pcount.group().replace(",", "")

                yield {u"name" : artist, \
                       u"playcount" : pcount}

            # Fetch the next page
            pageidx += 1
            libraryurl = "http://www.last.fm/user/%s/library/artists?page=%s" \
                    % (self.__username, pageidx)
            html = requests.get(libraryurl)
            site = lxml.html.fromstring(html.text).getroottree().getroot()

if __name__ == '__main__':
    allbands = list()
    lfmr = LastFmRetriever()

    for item in lfmr.nonAPIparser():
        pcount = int(item["playcount"])
        name = item["name"]
        print "[%5d] %-6s" % (pcount, name)


@@ -1427,19 +1427,30 @@
 ('//
-ul%5B
+li%5Bcontains(
 @class
-=%22
+, %22 
 pagi
@@ -1455,19 +1455,27 @@
 gination
-%22%5D'
+-page%22)%5D' +
  %5C%0A     
@@ -1503,29 +1503,11 @@
     
-+
 '/
-li%5B@class=%22pages%22%5D
+a
 /tex
@@ -1537,39 +1537,34 @@
 t = 
-re.findall(%22%5B0-9,%5D+%22, %22 %22.join(
+max(map(lambda i: int(i), 
 page
@@ -1573,17 +1573,8 @@
 ag))
- # %5B1, n%5D
 %0A   
@@ -1581,20 +1581,16 @@
      if 
-len(
 pagescou
@@ -1595,14 +1595,14 @@
 ount
-)
  == 
-0
+%22%22
 :%0A  
@@ -1663,66 +1663,8 @@
 pped
-%0A        else:%0A            pagescount = int(pagescount%5B1%5D)
 %0A%0A  
@@ -2352,32 +2352,37 @@
 + '/span/span/a/
+span/
 text()')%0A       

Fix Lastfm fetch after they changed the layout. Got unnoticed this far because I had the DB initialized.