#!/usr/bin/python

import os
import sys
import time
import ConfigParser
import getopt

import lastfm.marshaller

def popen(argv, mode='r'):
    command = ' '.join(argv)
    return os.popen(command.encode(sys.getfilesystemencoding()), mode)

def quotemeta(s):
    for meta in ('\\', '$', '`', '"', '\n'):
        s = s.replace(meta, '\\' + meta)
    return '"%s"' % s

class Command:
    def __init__(self, user_opts):
        self.user_opts = user_opts

class CdParanoia(Command):
    def open(self, device, number):
        return popen(['cdparanoia', '-r', self.user_opts, '-d', device,
            '%d' % number, '-'])

class APlay(Command):
    def open(self):
        return popen(['aplay', '-q', '-t', 'raw', '-c', '2', '-r', '44100',
            '-f', 'S16_LE', self.user_opts], 'w')

class Bfp(Command):
    def open(self):
        return popen(['bfp', self.user_opts], 'w')

class OggEnc(Command):
    def open(self, song, path):
        argv = ['oggenc', '-Q', '-r', self.user_opts]
        try: argv += ['-a', quotemeta(song['artist'])]
        except KeyError: pass
        try: argv += ['-t', quotemeta(song['title'])]
        except KeyError: pass
        try: argv += ['-l', quotemeta(song['album'])]
        except KeyError: pass
        try: argv += ['-l', '%d' % song['number']] # XXX
        except KeyError: pass
        try: argv += ['-c', quotemeta('musicbrainz_trackid=%s' % song['mbid'])]
        except KeyError: pass
        argv += ['-o', quotemeta('%s.ogg' % path), '-']
        return popen(argv, 'w')

class Lame(Command):
    def open(self, song, path):
        argv = ['lame', '--quiet', '-rx', self.user_opts]
        try: argv += ['--ta', quotemeta(song['artist'])]
        except KeyError: pass
        try: argv += ['--tt', quotemeta(song['title'])]
        except KeyError: pass
        try: argv += ['--tl', quotemeta(song['album'])]
        except KeyError: pass
        try: argv += ['--tn', '%d' % song['number']] # XXX
        except KeyError: pass
        argv += ['-', quotemeta('%s.mp3' % path)]
        return popen(argv, 'w')

rippers = {'cdparanoia': CdParanoia}
players = {'aplay': APlay, 'bfp': Bfp}
encoders = {'oggenc': OggEnc, 'lame': Lame}

class SaneConfParser(ConfigParser.RawConfigParser):
    def get(self, section, option, default):
        try:
            return ConfigParser.RawConfigParser.get(self, section, option)
        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
            return default

def_path = '%(artist)s/%(album)s/%(number)02d - %(title)s'
sox = 'sox "track%02d.cdda.wav" -t raw -r 44100 -s -w -c 2 -'

if __name__ == '__main__':
    shortopts = 'd:qc'
    longopts = ['device=', 'quiet', 'continue']

    try:
        opts, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
    except getopt.GetoptError, e:
        print >>sys.stderr, 'peel: %s' % e
        sys.exit(1)

    device = '/dev/cdrom'
    quiet = False

    for opt, arg in opts:
        if opt in ('--device', '-d'):
            device = arg
        elif opt in ('--quiet', '-q'):
            quiet = True
        elif opt in ('--continue', '-c'):
            device = None

    cp = SaneConfParser()
    cp.read(os.path.expanduser('~/.peelrc'))

    rip_cmd = cp.get('commands', 'rip', 'cdparanoia')
    play_cmd = cp.get('commands', 'play', 'aplay')
    enc_cmd = cp.get('commands', 'encode', 'oggenc')

    rip_opts = cp.get('options', rip_cmd, '')
    play_opts = cp.get('options', play_cmd, '')
    enc_opts = cp.get('options', enc_cmd, '')

    path_tmpl = cp.get('output', 'path', def_path)

    try:
        ripper = rippers[rip_cmd](rip_opts)
        player = players[play_cmd](play_opts)
        encoder = encoders[enc_cmd](enc_opts)
    except KeyError, e:
        print >>sys.stderr, 'unknown command: %s' % e.args[0]

    if not quiet:
        play = player.open()
        log = lastfm.logger('peel')

    for song in lastfm.marshaller.load_documents(sys.stdin):
        print "Track %(number)s: %(title)s..." % song

        if device:
            rip = ripper.open(device, song['number'])
        else:
            rip = popen(sox % song['number'])

        safe_song = {}
        for k, v in song.iteritems():
            try:
                safe_song[k] = v.replace('/', '_')
            except AttributeError:
                safe_song[k] = v

        path = path_tmpl % safe_song
        dir = os.path.dirname(path)
        if dir and not os.path.isdir(dir):
            os.makedirs(dir)

        enc = encoder.open(song, path)

        while True:
            buf = rip.read(4096)
            if buf:
                enc.write(buf)
                if not quiet:
                    play.write(buf)
            else:
                break

        if not quiet:
            if song['length'] >= 30:
                song['time'] = time.gmtime()
                lastfm.submit([song])
                log.info('Sent %s to daemon' % lastfm.repr(song))
