
'''
Provide classes to parse and handle requests.

Copyright 2005 Dan Pascu
'''

__all__ = ['Request', 'RequestInfo', 'InvalidRequestError']


class InvalidRequestError(Exception): pass


class Structure(dict):
    '''
    A dict like class, which in addition to accessing the data like with any normal dictionary,
    allows one to access a value in the dictionary by accessing an attribute of the instance
    with the same name as the dictionary key that points to that value. This is valid for
    both reading and writing the value. In addition to a dict, a value may be accessed by
    using a composed key like this obj['k1.k2.k3'] which is the same as obj['k1']['k2']['k3']
    and the same with obj.k1.k2.k3 
    '''
    def __init__(self):
        dict.__init__(self)
    def __getitem__(self, key):
        elements = key.split('.')
        obj = self ## start with ourselves
        for e in elements:
            if not isinstance(obj, dict):
                raise TypeError, 'unsubscriptable object'
            obj = dict.__getitem__(obj, e)
        return obj
    def __setitem__(self, key, value):
        self.__dict__[key] = value
        dict.__setitem__(self, key, value)
    def __delitem__(self, key):
        dict.__delitem__(self, key)
        del self.__dict__[key]
    __setattr__ = __setitem__
    def __delattr__(self, name):
        try:
            del self.__dict__[name]
        except KeyError:
            raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, name)
        else:
            dict.__delitem__(self, name)
    def update(self, other):
        dict.update(self, other)
        for key, value in other.items():
            self.__dict__[key] = value

class GhostedStructure(Structure):
    '''
    Like Structure, but allows ghosted members. A ghosted member may be missing and won't
    show up in the list of structure members, but will return a default value if one
    specifically asks for that missing ghosted structure member.
    '''
    _ghosts = {}
    def __init__(self):
        Structure.__init__(self)
    def __getitem__(self, key):
        try:
            return Structure.__getitem__(self, key)
        except KeyError:
            if key in self._ghosts:
                return self._ghosts[key]
            raise
    def __getattr__(self, name):
        try:
            return self.__dict__.get(name, self._ghosts[name])
        except KeyError:
            raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, name)
    def __delattr__(self, name):
        try:
            Structure.__delattr__(self, name)
        except AttributeError:
            if name not in self._ghosts:
                raise

class RequestInfo(GhostedStructure):
    _ghosts = {'from': '', 'to': '', 'fromtag': '', 'totag': '', 'dispatcher': None}
    def __init__(self, data):
        Structure.__init__(self)
        self.__dict__['flags'] = GhostedStructure()
        self.flags.__dict__['_ghosts'] = {'asymmetric': False}
        try:
            info = data.split('=', 1)[1]
            if info:
                entries = info.split(',')
            else:
                entries = []
        except IndexError:
            entries = []
        for entry in entries:
            try:
                key, value = entry.split(':', 1)
                self[key] = value
            except ValueError:
                self.flags[entry] = True
    def __str__(self):
        return 'info=' + ','.join(['%s:%s' % pair for pair in self.items()] + list(self.flags))
    def __repr__(self):
        return "'%s'" % self

class Request(Structure):
    '''A request parsed into a structure based on request type'''
    __commands = {'request': ('cmd', 'sessionId', 'streams', 'signalingIP', 'callerDomain', 'callerType',
                              'calledDomain', 'calledType', 'userAgent', 'info'),
                  'lookup':  ('cmd', 'sessionId', 'streams', 'signalingIP', 'callerDomain', 'callerType',
                              'calledDomain', 'calledType', 'userAgent', 'info'),
                  'delete':  ('cmd', 'sessionId', 'info'),
                  'timeout': ('cmd', 'sessionId', 'stopRecord'),
                  'status':  ('cmd',),
                  'status2': ('cmd',),
                  'summary': ('cmd',),
                  'version': ('cmd',)}

    def __init__(self, request):
        Structure.__init__(self)
        args = request.split()
        if not args:
            raise InvalidRequestError, "received empty request"
        cmd = args[0].lower()
        if cmd not in self.__commands.keys():
            raise InvalidRequestError, "received unknown command `%s'" % cmd
        if len(args) < len(self.__commands[cmd]):
            raise InvalidRequestError, "insufficient arguments for command `%s'" % cmd
        self.update(dict(zip(self.__commands[cmd], args)))
        if hasattr(self, 'info'):
            self.info = RequestInfo(self.info)
        if cmd in ('request', 'lookup') and not self.info['from']:
            # remove at a later time -Dan
            print("warning: SER is using an old mediaproxy.so module. "
                  "Please upgrade it, else accounting won't work.")
    
    # TODO: find a way to only define these properties for the requests that have the callerType attribute -Dan
    localCaller = property(lambda x: x.callerType=='local')
    localCalled = property(lambda x: x.calledType=='local')

    def __str__(self):
        if self.cmd in ('request', 'lookup'):
            return "%(cmd)s %(sessionId)s %(streams)s %(signalingIP)s %(callerDomain)s %(callerType)s %(calledDomain)s %(calledType)s %(userAgent)s %(info)s\n" % self
        elif self.cmd == 'delete':
            return "%(cmd)s %(sessionId)s %(info)s\n" % self
        elif self.cmd == 'timeout':
            return "%(cmd)s %(sessionId)s %(stopRecord)s\n" % self
        else:
            return "%(cmd)s\n" % self
    def __repr__(self):
        return "'%s'" % self

