# Class interface to the CD module.

import cd, CD

class Error(Exception):
    pass
class _Stop(Exception):
    pass

def _doatime(self, cb_type, data):
    if ((data[0] * 60) + data[1]) * 75 + data[2] > self.end:
##              print 'done with list entry', repr(self.listindex)
        raise _Stop
    func, arg = self.callbacks[cb_type]
    if func:
        func(arg, cb_type, data)

def _dopnum(self, cb_type, data):
    if data > self.end:
##              print 'done with list entry', repr(self.listindex)
        raise _Stop
    func, arg = self.callbacks[cb_type]
    if func:
        func(arg, cb_type, data)

class Readcd:
    def __init__(self, *arg):
        if len(arg) == 0:
            self.player = cd.open()
        elif len(arg) == 1:
            self.player = cd.open(arg[0])
        elif len(arg) == 2:
            self.player = cd.open(arg[0], arg[1])
        else:
            raise Error, 'bad __init__ call'
        self.list = []
        self.callbacks = [(None, None)] * 8
        self.parser = cd.createparser()
        self.playing = 0
        self.end = 0
        self.status = None
        self.trackinfo = None

    def eject(self):
        self.player.eject()
        self.list = []
        self.end = 0
        self.listindex = 0
        self.status = None
        self.trackinfo = None
        if self.playing:
##                      print 'stop playing from eject'
            raise _Stop

    def pmsf2msf(self, track, min, sec, frame):
        if not self.status:
            self.cachestatus()
        if track < self.status[5] or track > self.status[6]:
            raise Error, 'track number out of range'
        if not self.trackinfo:
            self.cacheinfo()
        start, total = self.trackinfo[track]
        start = ((start[0] * 60) + start[1]) * 75 + start[2]
        total = ((total[0] * 60) + total[1]) * 75 + total[2]
        block = ((min * 60) + sec) * 75 + frame
        if block > total:
            raise Error, 'out of range'
        block = start + block
        min, block = divmod(block, 75*60)
        sec, frame = divmod(block, 75)
        return min, sec, frame

    def reset(self):
        self.list = []

    def appendtrack(self, track):
        self.appendstretch(track, track)

    def appendstretch(self, start, end):
        if not self.status:
            self.cachestatus()
        if not start:
            start = 1
        if not end:
            end = self.status[6]
        if type(end) == type(0):
            if end < self.status[5] or end > self.status[6]:
                raise Error, 'range error'
        else:
            l = len(end)
            if l == 4:
                prog, min, sec, frame = end
                if prog < self.status[5] or prog > self.status[6]:
                    raise Error, 'range error'
                end = self.pmsf2msf(prog, min, sec, frame)
            elif l != 3:
                raise Error, 'syntax error'
        if type(start) == type(0):
            if start < self.status[5] or start > self.status[6]:
                raise Error, 'range error'
            if len(self.list) > 0:
                s, e = self.list[-1]
                if type(e) == type(0):
                    if start == e+1:
                        start = s
                        del self.list[-1]
        else:
            l = len(start)
            if l == 4:
                prog, min, sec, frame = start
                if prog < self.status[5] or prog > self.status[6]:
                    raise Error, 'range error'
                start = self.pmsf2msf(prog, min, sec, frame)
            elif l != 3:
                raise Error, 'syntax error'
        self.list.append((start, end))

    def settracks(self, list):
        self.list = []
        for track in list:
            self.appendtrack(track)

    def setcallback(self, cb_type, func, arg):
        if cb_type < 0 or cb_type >= 8:
            raise Error, 'type out of range'
        self.callbacks[cb_type] = (func, arg)
        if self.playing:
            start, end = self.list[self.listindex]
            if type(end) == type(0):
                if cb_type != CD.PNUM:
                    self.parser.setcallback(cb_type, func, arg)
            else:
                if cb_type != CD.ATIME:
                    self.parser.setcallback(cb_type, func, arg)

    def removecallback(self, cb_type):
        if cb_type < 0 or cb_type >= 8:
            raise Error, 'type out of range'
        self.callbacks[cb_type] = (None, None)
        if self.playing:
            start, end = self.list[self.listindex]
            if type(end) == type(0):
                if cb_type != CD.PNUM:
                    self.parser.removecallback(cb_type)
            else:
                if cb_type != CD.ATIME:
                    self.parser.removecallback(cb_type)

    def gettrackinfo(self, *arg):
        if not self.status:
            self.cachestatus()
        if not self.trackinfo:
            self.cacheinfo()
        if len(arg) == 0:
            return self.trackinfo[self.status[5]:self.status[6]+1]
        result = []
        for i in arg:
            if i < self.status[5] or i > self.status[6]:
                raise Error, 'range error'
            result.append(self.trackinfo[i])
        return result

    def cacheinfo(self):
        if not self.status:
            self.cachestatus()
        self.trackinfo = []
        for i in range(self.status[5]):
            self.trackinfo.append(None)
        for i in range(self.status[5], self.status[6]+1):
            self.trackinfo.append(self.player.gettrackinfo(i))

    def cachestatus(self):
        self.status = self.player.getstatus()
        if self.status[0] == CD.NODISC:
            self.status = None
            raise Error, 'no disc in player'

    def getstatus(self):
        return self.player.getstatus()

    def play(self):
        if not self.status:
            self.cachestatus()
        size = self.player.bestreadsize()
        self.listindex = 0
        self.playing = 0
        for i in range(8):
            func, arg = self.callbacks[i]
            if func:
                self.parser.setcallback(i, func, arg)
            else:
                self.parser.removecallback(i)
        if len(self.list) == 0:
            for i in range(self.status[5], self.status[6]+1):
                self.appendtrack(i)
        try:
            while 1:
                if not self.playing:
                    if self.listindex >= len(self.list):
                        return
                    start, end = self.list[self.listindex]
                    if type(start) == type(0):
                        dummy = self.player.seektrack(
                                start)
                    else:
                        min, sec, frame = start
                        dummy = self.player.seek(
                                min, sec, frame)
                    if type(end) == type(0):
                        self.parser.setcallback(
                                CD.PNUM, _dopnum, self)
                        self.end = end
                        func, arg = \
                              self.callbacks[CD.ATIME]
                        if func:
                            self.parser.setcallback(CD.ATIME, func, arg)
                        else:
                            self.parser.removecallback(CD.ATIME)
                    else:
                        min, sec, frame = end
                        self.parser.setcallback(
                                CD.ATIME, _doatime,
                                self)
                        self.end = (min * 60 + sec) * \
                                   75 + frame
                        func, arg = \
                              self.callbacks[CD.PNUM]
                        if func:
                            self.parser.setcallback(CD.PNUM, func, arg)
                        else:
                            self.parser.removecallback(CD.PNUM)
                    self.playing = 1
                data = self.player.readda(size)
                if data == '':
                    self.playing = 0
                    self.listindex = self.listindex + 1
                    continue
                try:
                    self.parser.parseframe(data)
                except _Stop:
                    self.playing = 0
                    self.listindex = self.listindex + 1
        finally:
            self.playing = 0
