text: import json
  import time
  
  from oic.oauth2 import Message
  
  __author__ = 'roland'
  
  # Message direction
  INCOMING = 1
  OUTGOING = 2
  
  # standard event labels
  EV_ASSERTION = 'assertion'
  EV_CONDITION = 'condition'
  EV_EXCEPTION = 'exception'
  EV_END = 'end'
  EV_EVENT = 'event'
  EV_FAULT = 'fault'
  EV_FUNCTION = 'function'
  EV_HANDLER_RESPONSE = 'handler response'
  EV_HTML_SRC = 'html src'
  EV_HTTP_ARGS = 'http args'
  EV_HTTP_INFO = 'http info'
  EV_HTTP_REQUEST = 'http request'
  EV_HTTP_RESPONSE = 'http response'
  EV_HTTP_RESPONSE_HEADER = 'http response header'
  EV_NOOP = 'not expected to do'
  EV_OPERATION = 'phase'
  EV_OP_ARGS = 'operation args'
  EV_PROTOCOL_RESPONSE = 'protocol response'
  EV_PROTOCOL_REQUEST = 'protocol request'
  EV_REDIRECT_URL = 'redirect url'
  EV_REPLY = 'reply'
  EV_REQUEST = 'request'
  EV_REQUEST_ARGS = 'request args'
  EV_RESPONSE = 'response'
  EV_RESPONSE_ARGS = 'response args'
  EV_RUN = 'run'
  EV_SEND = 'send'
  EV_URL = 'url'
  EV_JWS_HEADER = 'JWS header'
  EV_JWE_HEADER = 'JWE header'
  EV_ENDPOINT = 'endpoint'
  EV_OBJ = 'object'
  EV_OBJ_ARGS = 'object args'
  
  class NoSuchEvent(Exception):
      pass
  
  
  class Base(object):
      def __init__(self, **kwargs):
          self.op_args = kwargs
  
      def gather_args(self):
          return self.op_args
  
      def to_str(self):
          _res = self.gather_args()
          return json.dumps(_res, sort_keys=True, indent=4,
                            separators=(',', ': '))
  
  
  class Operation(Base):
      def __init__(self, name, typ='', **kwargs):
          Base.__init__(self, **kwargs)
          self.name = name
          self.type = typ
  
      def gather_args(self):
          _res = {'name': self.name}
          if self.type:
              _res['type'] = self.type
          _res.update(Base.gather_args(self))
          return _res
  
  
  class FailedOperation(Operation):
      def __init__(self, name, error, **kwargs):
          Operation.__init__(self, name, **kwargs)
          self.error = error
  
      def gather_args(self):
          _res = {'name': self.name, 'error': self.error}
          if self.type:
              _res['type'] = self.type
          _res.update(self.op_args)
          return _res
  
  
  class HTTPRequest(Base):
      def __init__(self, endpoint, method):
          Base.__init__(self)
          self.endpoint = endpoint
          self.method = method
          self.authz = None
  
      def gather_args(self):
          _res = {}
          for k in ['endpoint', 'method','authz']:
              v = getattr(self, k)
              if v:
                  _res[k] = v
          return _res
  
  
  class HTTPResponse(object):
      def __init__(self, response):
          try:
              self.status_code = response.status_code
          except AttributeError:
              self.status_code = int(response.status.split(' ')[0])
          try:
              self.url = response.url
          except AttributeError:
              self.url = ''
  
          self.headers = response.headers
  
          # Should perhaps be more intelligent about this
          if self.status_code >= 400:
              try:
                  self.text = response.text
              except AttributeError:
                  self.text = response.message
          else:
              self.text = ''
  
  
  class Event(object):
      def __init__(self, timestamp=0, typ='', data=None, ref='', sub='',
                   sender='', direction=0, **kwargs):
          self.timestamp = timestamp or time.time()
          self.typ = typ
          self.data = data
          try:
              self.ref = ref.lower()
          except AttributeError:
              self.ref = ref
          self.sub = sub
          self.sender = sender
          self.direction = direction
          self.kwargs = kwargs
  
      def __str__(self):
          return '{}:{}:{}'.format(self.timestamp, self.typ, self.data)
  
      def __eq__(self, other):
          if isinstance(other, Event):
              for param in ['timestamp', 'typ', 'data', 'ref', 'sub', 'sender']:
                  if getattr(self, param) != getattr(other, param):
                      return False
          return True
  
      def older(self, other):
          if other.timestamp >= self.timestamp:
              return True
          return False
  
      def what_when(self):
          return '{}:{}'.format(self.timestamp, self.typ)
  
  
  class Events(object):
      def __init__(self):
          self.events = []
  
      def store(self, typ, data, ref='', sub='', sender='', direction=0,
                **kwargs):
          index = time.time()
  
          typ = typ.lower()
          if typ == EV_HTTP_RESPONSE:  # only store part of the instance
              data = HTTPResponse(data)
  
          self.events.append(
              Event(int(index), typ, data, ref, sub, sender, direction, **kwargs))
          return index
  
      def by_index(self, index):
          l = [e for e in self.events if e.timestamp == index]
          if l:
              return l[0]
          else:
              raise KeyError(index)
  
      def by_ref(self, ref):
          try:
              lr = ref.lower()
          except AttributeError:
              lr = ref
          return [e for e in self.events if e.ref == lr]
  
      def by_direction(self, direction):
          return [e for e in self.events if e.direction == direction]
  
      def get(self, typ):
          ltyp = typ.lower()
          return [ev for ev in self.events if ev.typ == ltyp]
  
      def get_data(self, typ, sender=''):
          if sender:
              return [d.data for d in self.get(typ) if d.sender == sender]
          else:
              return [d.data for d in self.get(typ)]
  
      def get_messages(self, typ, msg_cls):
          res = []
          for m in self.get(typ):
              if m.data.__class__ == msg_cls:
                  res.append(m.data)
          # return [m.data for m in self.get(typ) if isinstance(m.data, msg_cls)]
          return res
  
      def last(self, typ):
          res = self.get(typ)
          if len(res):
              return res[-1]
          else:
              return None
  
      def get_message(self, typ, msg_cls):
          l = self.get_messages(typ, msg_cls)
          if l:
              return l[-1]
  
          raise NoSuchEvent('{}:{}'.format(typ, msg_cls))
  
      def last_item(self, typ):
          l = self.get_data(typ)
          if l:
              return l[-1]
  
          raise NoSuchEvent(typ)
  
      def __len__(self):
          return len(self.events)
  
      def __getitem__(self, item):
          return self.get_data(item)
  
      def __setitem__(self, key, value):
          self.store(key, value)
  
      def append(self, event):
          assert isinstance(event, Event)
          self.events.append(event)
  
      def extend(self, events):
          for event in events:
              self.append(event)
  
      def __iter__(self):
          return self.events.__iter__()
  
      def last_of(self, types):
          l = self.events[:]
          l.reverse()
          for ev in l:
              if ev.typ in types:
                  return ev.data
  
          return None
  
      def __contains__(self, event):
          ts = event.timestamp
          for ev in self.events:
              if event.timestamp == ev.timestamp:
                  if event == ev:
                      return True
  
          return False
  
      def sort(self):
          self.events.sort(key=lambda event: event.timestamp)
  
      def to_html(self, form='table'):
          if form == 'list':
              text = ['<ul>']
              for ev in self.events:
                  text.append('<li> {}'.format(ev))
              text.append('</ul>')
          else:
              text = ['<table border=1 width="600">']
              for ev in self.events:
                  if isinstance(ev.data, Message):
                      _data = json.dumps(ev.data.to_dict(), sort_keys=True,
                                         indent=4, separators=(',', ': '))
                  else:
                      _data = ev.data
                  text.append(
                      '<tr><td>{time}</td><td>{typ}</td>'
                      '<td>{data}}</td></tr>'.format(
                          time=ev.timestamp, typ=ev.typ, data=_data))
              text.append('</table>')
  
          return '\n'.join(text)
  
      def __str__(self):
          return '\n'.join(['{}'.format(ev) for ev in self.events])
  
      def reset(self):
          self.events = []
  
      def when(self, typ, msg):
          res = []
          for m in self.get(typ):
              if m.data.__class__ == msg:
                  res.append(m.timestamp)
  
          return res
  
      def last_event_type(self):
          return self.events[-1].typ
  
      def timeline(self):
          start = self.events[0].timestamp
          return [(ev.timestamp - start, ev.typ, ev.data) for ev in self.events]
  
      def digest(self):
          res = []
          for event in self.events:
              res.append(event.what_when())
          return res
  
  
  def funtion_to_str(event):
      _dat = event.data
      res = [_dat['name']]
      if 'args' in _dat and _dat['args']:
          res.append('args:{}'.format(_dat['args']))
      if 'kwargs' in _dat and _dat['kwargs']:
          res.append('kwargs:{}'.format(_dat['kwargs']))
      return res
  
  
  def http_response_to_str(event):
      _dat = event.data
      res = [event.typ]
      if _dat.url:
          res.append('url:{}'.format(_dat.url))
      if _dat.status_code:
          res.append('status_code:{}'.format(_dat.status_code))
      if _dat.text:
          res.append('message:{}'.format(_dat.text))
      return res
  
  
  def exception_to_str(event):
      res = [event.typ]
      try:
          res.append('{} {}'.format(event.kwargs['note'], event.data))
      except KeyError:
          res.append('{}'.format(event.data))
      return res
  
  
  def message_to_str(event):
      return [event.data.__class__.__name__,
              json.dumps(event.data.to_dict(), sort_keys=True, indent=4,
                         separators=(',', ': '))]
  
  
  TO_STR = {
      EV_FUNCTION: funtion_to_str,
      EV_HTTP_RESPONSE: http_response_to_str,
      EV_EXCEPTION: exception_to_str,
      EV_PROTOCOL_REQUEST: message_to_str,
      EV_PROTOCOL_RESPONSE: message_to_str
  }
  
  
  def layout(start, event):
      elem = ['{}'.format(round(event.timestamp - start, 3))]
      if event.direction:
          if event.direction == OUTGOING:
              elem.append('-->')
          else:
              elem.append('<--')
  
      try:
          elem.extend(TO_STR[event.typ](event))
      except KeyError:
          elem.append(event.typ)
          if isinstance(event.data, Base):
              elem.append(event.data.to_str())
          else:
              elem.append(str(event.data))
  
      return ' '.join(elem)
  
  
  def row(start, event):
      if event.typ == EV_OPERATION:
          _row = ['<tr class="info"><td>{}</td>'.format(
              round(event.timestamp - start, 3))]
      else:
          _row = ['<tr><td>{}</td>'.format(
              round(event.timestamp - start, 3))]
  
      try:
          p = TO_STR[event.typ](event)
          _row.append(
              '<td>{}</td><td><pre><code>{}</pre></code></td>'.format(
                  p[0],' '.join(p[1:])))
      except KeyError:
          _row.append('<td style="max-width: 500px; word-wrap: break-word;">{}</td>'.format(event.typ))
          if isinstance(event.data, Base):
              _row.append('<td style="max-width: 500px; word-wrap: break-word;">{}</td>'.format(event.data.to_str()))
          else:
              _row.append('<td style="max-width: 500px; word-wrap: break-word;">{}</td>'.format(str(event.data)))
      _row.append("</tr>")
      return "".join(_row)