#!/usr/bin/env python

import sys, os
import urllib, urllib2, cookielib
import popen2
import random
import time
import string

class XmRadioError(Exception):

    def __init__(self, value):
        self.value = value

    def __str__(self):
        return repr(self.value)


class XmRadioOnline:
    """
    Connects to XM Radio Online service and parses all
    the gobbledy gook to get the URL to actually play.
    """
    def __init__(self, user = None, pw = None, channel = None, speed = None):

        self.user = user
        self.pw = pw
        self.channel = channel
        self.speed = speed

        # Store the user ID and password, if specified.
        self.userInfoSave()
        # If user info is blank, load it.
        self.userInfoLoad()

        # Initialize the what's playing info.
        self.info = {}
        self.infoKeys =    [('channelname', 'Channel'),
                            ('artist','Artist'),
                            ('songtitle','Song'),]
        self.infoFormat = '%7s: %s'

    def userInfoSave(self):
        """
        Save the user id and password
        if the user has specified them.
        """
        if self.user == None: return
        if self.pw == None: return

        path = os.path.join(os.path.expanduser('~'), 'music_xm.txt')
        fp = open(path, 'w')

        fp.write(self.user)
        fp.write('\n')
        table = string.maketrans(
            'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM',
            'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
        fp.write(string.translate(self.pw, table))
        fp.write('\n')
        fp.close()

    def userInfoLoad(self):
        """
        Load in the file with the user id and password
        if the user has not specified them.
        """

        if not self.user == None: return
        if not self.pw == None: return

        path = os.path.join(os.path.expanduser('~'), 'music_xm.txt')
        if not os.path.exists(path):
            return

        fp = open(path, 'r')
        self.user = fp.readline().rstrip('\n')
        table = string.maketrans(
            'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM',
            'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
        val = fp.readline().rstrip('\n')
        self.pw = string.translate(val, table)
        fp.close()

    def findData(self, line, m1, m2, m3 = None):
        """
        Tool to find text in 'line' which matches the first delimiter, 'm1'
        and then return what is betwen 'm1' and 'm2'.
        """
        
        if line.find(m1) > -1:
            if m3:
                m1 = m2
                m2 = m3
            stuff = line.split(m1)
            stuff = stuff[1].split(m2)
            return stuff[0]

        return None

    def connect(self, user = None, pw = None, channel = None, speed = None):
        """
        Connect to XMonline to log in and then get the stream information
        and URL.  If you do not want the user and password information stored
        in a file, then pass it in here rather than to the class
        constructor.
        """
        # Check to see if the user wants to override
        # the instance settings for these.
        if user: self.user = user
        if pw: self.pw = pw
        if channel: self.channel = channel
        if speed: self.speed = speed

        # Here is where we have to get logged in
        # and grab the URL.
        
        self.cj = cookielib.CookieJar()
        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))
        self.login_data = urllib.urlencode({'user_id' : self.user,
                                            'pword' : self.pw})
        self.url = 'http://xmro.xmradio.com/xstream/login_servlet.jsp'
        self.resp = self.opener.open(self.url, self.login_data)

        # Check the response to make sure we logged in.
        data = []
        self.loggedInAs = ''
        for line in self.resp.readlines():
            data.append(line.strip())
            #print line.strip()
            if line.find('Invalid email and/or password') > -1:
                raise XmRadioError('Invalid email and/or password')

            self.loggedInAs = self.findData(line, '(Not ', '?')
            if self.loggedInAs:
                break
        
        print 'Logged in as %s' % self.loggedInAs

        # Now get the play URL.
        self.url = 'http://player.xmradio.com/player/2ft/playMedia.jsp?ch=%d&speed=%s' \
                   % (self.channel, self.speed)
        self.resp = self.opener.open(self.url, self.login_data)

        # Check the response to find the URL.
        data = []
        self.musicUrl = None
        m1 = '<PARAM NAME="FileName" VALUE="'
        m2 = '">'
        for line in self.resp.readlines():
            data.append(line.strip())
            #print line.strip()
            self.musicUrl = self.findData(line, m1, m2)
            if self.musicUrl:
                break

        if not self.musicUrl:
            raise XmRadioError('Error getting play URL')

        #print 'URL: %s' % self.musicUrl

        # Now find the stream URL.
        # Set our agent correctly...
        
        self.resp = self.opener.open(self.musicUrl, self.login_data)

        # Check the response to find the URL.
        data = []
        self.streamUrl = None
        m1 = 'mms://'
        m2 = '<Ref href="'
        m3 = '"/>'
        for line in self.resp.readlines():
            data.append(line.strip())
            #print line.strip()
            self.streamUrl = self.findData(line, m1, m2, m3)
            if self.streamUrl:
                break

        if not self.streamUrl:
            raise XmRadioError('Error getting play URL')

        #print 'streamUrl:', self.streamUrl

    def play(self):
        """
        This routine is the one where we actually play our
        URL in music player.  Currently this only works with
        Winamp on Windows.
        """
        #import xmms

        # Start winamp and play the URL
        import subprocess
        prog = os.path.join('C:\\', 'Program Files', 'Winamp', 'winamp.exe')
        print 'Running:', prog, self.streamUrl
        self.playerProcess = subprocess.Popen([prog, self.streamUrl])
        #print 'pid:', self.playerProcess.pid
        #print 'poll:', self.playerProcess.poll()

        # Loop until we connect with the winamp controller.
        import winamp
        for attempt in [1, 2, 3, 4, 5]:
            time.sleep(2)
            try:
                self.player = winamp.winamp()
                # This should return 'playing', 'paused' or 'stopped'
                self.playerStatus = self.player.getPlayingStatus()
                print 'Winamp status:', self.playerStatus
                if self.playerStatus == 'stopped':
                    self.player.play()
                break
            except IOError:
                print 'Could not connect to Winamp'

    def getPlaying(self, loop = 1):
        """
        Return information about what is currently playing.
        """

        while 1:
            # Now get a list of what is playing.
            self.url = 'http://player.xmradio.com/padData/pad_data_servlet.jsp?channel=%d&rnd=%d' \
                       % (self.channel, random.randint(1, 10000000))
            self.resp = self.opener.open(self.url, self.login_data)

            # Check the response.
            changed = 0
            for line in self.resp.readlines():
                for key in self.infoKeys:
                    val = self.findData(line, '<%s>' % key[0],
                                        '</%s>' % key[0])
                    if not val == None:
                        if not self.info.get(key[0], None) == val:
                            changed = 1
                        self.info[key[0]] = val
                        continue

            if changed:
                print ''
                for key in self.infoKeys:
                    val = self.info.get(key[0], '')
                    val = val.replace('&amp;', '&')
                    print self.infoFormat \
                          % (key[1], val)

            # Update the player status.
            self.playerStatus = self.player.getPlayingStatus()
            if self.playerStatus == 'stopped':
                break

            if not loop:
                break

            time.sleep(3)

    def stop(self):
        """
        Stop the player.  Probably would be used on exit.
        """
        self.player.stop()
        
#
# Main:  This is run when started from the command line.
#

if __name__ == '__main__':

    # Parse arguments with optparse included since Python-2.3.
    # ( http://docs.python.org/lib/module-optparse.html )
    import optparse

    usage = '%prog [options] arg' \
            + '\n\n  Python template script.'
    parser = optparse.OptionParser(usage=usage)
    parser.add_option('-v', '--verbose',
                      action = 'store_true', dest = 'verbose',
                      help = 'Use verbose output mode.')
    parser.add_option('-u', '--user',
                      action = 'store', type = 'string', dest = 'user',
                      help = 'User ID for login.')
    parser.add_option('-p', '--password',
                      action = 'store', type = 'string', dest = 'password',
                      help = 'Password for login.')
    parser.add_option('-k', '--toss-password',
                      action = 'store_true', dest = 'tossPassword',
                      help = 'Do not store password.')
    parser.add_option('-s', '--speed',
                      action = 'store', type = 'string', dest = 'speed',
                      help = 'Speed for stream rate: "low" or "high".',
                      default = 'high')

    (options, args) = parser.parse_args()
    if len(args) > 1:
        parser.error('unknown argument(s): "%s"' % '", "'.join(args[1:]))
    if len(args) < 1:
        parser.error('not enough arguments')

    print 'user:', options.user
    print 'toss-password:', options.tossPassword

    channel = int(args[0])
    print 'Connecting to XM Radio channel %d' % channel

    if options.tossPassword:
        # Password is only stored when passed in to the constructor.
        xmRadioOnline = XmRadioOnline()
    else:
        xmRadioOnline = XmRadioOnline(user = options.user,
                                      pw = options.password)
        
    # Here the password isn't stored.
    xmRadioOnline.connect(user = options.user, pw = options.password,
                          channel = channel, speed = options.speed)
    xmRadioOnline.play()
    try:
        xmRadioOnline.getPlaying()
    except KeyboardInterrupt:
        xmRadioOnline.stop()
        raise
    
