#!/usr/bin/env python2

# This program emulates a Dashboard server, and validates any
# cluepackets it gets.  It is intended to ease development of
# Dashboard frontends on platforms where the Dashboard doesn't yet
# run.

class CluepacketError(Exception):
    def __init__(self, message):
        self.args = message

import xml.sax
from xml.sax.handler import ContentHandler

# an xml parser class for the cluepacket
class SAXSimple(ContentHandler):
    """SAXSimple is a python port of the perl XML::Simple parser"""

    def __init__(self, keyattr=[], contentkey="content", keeproot=0):
        self.keyattr    = keyattr
        self.contentkey = contentkey
        self.keeproot   = keeproot
    
    def startDocument(self):
        self.lists   = []
        self.cur     = self.tree = {}

    def endDocument(self):
        if not self.keeproot:
            self.tree = self.tree.values()[0][0]
        del self.lists
        del self.cur

    def startElement(self, name, attrs):
        newattrs = {}
        newattrs.update(attrs)

        key = name

        for i in self.keyattr:
            if newattrs.has_key(i):
                key = newattrs[i]
                break

        self.lists.append(self.cur)
        self.cur.setdefault(key, []).append(newattrs)
        self.cur = newattrs

    def endElement(self, name):
        self.cur = self.lists.pop()

    def characters(self, content):
        try:
            self.cur[self.contentkey] = self.cur[self.contentkey] + content
        except KeyError:
            self.cur[self.contentkey] = content

from SocketServer import BaseRequestHandler,TCPServer
class DashboardServer(BaseRequestHandler):
    def handle(self):
        raw = self.request.recv(65535)
        print "---- begin ----"
        print "Received raw cluepacket: '%s'" % raw

        try:
            self._validate_cluepacket(raw)
        except CluepacketError, e:
            print "ERROR:", e

        print "----- end -----"

    def _validate_cluepacket(self, raw):
        """\
Try to validate the raw cluepacket.  Raises CluepacketError if something
is wrong with the cluepacket or its clues."""

        if raw[-1] != '\n':
            raise CluepacketError("Cluepacket must end with a newline.")

        try:
            tree = self._parse_cluepacket(raw)
        except xml.sax.SAXParseException, e:
            raise CluepacketError("Cluepacket is not valid XML: %s" % e)

        print "XML parse tree follows:"
        from pprint import pprint
        pprint(tree)

        # possible CluePacket tags
        tags = ['Clue', 'Context', 'Focused', 'Frontend', 'content']
        for key in tree.keys():
            if key not in tags:
                raise CluepacketError("Cluepacket has unrecognized tag: %s" % key)

        try:
            if not tree['content'].isspace():
                raise CluepacketError("Cluepacket has non-whitespace CDATA: '%s'" % tree['content'].strip())
        except KeyError:
            pass

        try:
            for required in ['Frontend', 'Focused']:
                if tree[required][0].get('content', '') == '':
                    raise CluepacketError("Cluepacket has an empty required tag: %s" % required)
        except KeyError, e:
            raise CluepacketError("Cluepacket is missing required tag: %s" % e)

        for clue in tree.get('Clue', []):
            self._validate_clue(clue)

        print "CluePacket appears valid."

    def _validate_clue(self, clue):
        tags = ['Type', 'Relevance', 'content']
        for key in clue.keys():
            if key not in tags:
                raise CluepacketError("Clue has unrecognized tag: %s" % key)

        if clue.has_key('Type'):
            types = ['date', 'full_name', 'email', 'aim_name', 'yahoo_name',
                     'msn_name', 'icq_name', 'jabber_name', 'org', 'phone',
                     'address', 'url', 'keyword', 'textblock',
                     'word_at_point', 'sentence_at_point', 'line_at_point']
            if clue['Type'] not in types:
                raise CluepacketError("Clue has unrecognized Type: %s" \
                                      % clue['Type'])

        if clue.has_key('Relevance'):
            try:
                num = int(clue['Relevance'])
            except ValueError:
                raise CluepacketError("Clue relevance is non-integer: %s" \
                                      % clue['Relevance'])

            if num < 1 or num > 10:
                raise CluepacketError("Clue relevance is outside the range [1..10]: %s" % clue['Relevance'])

    def _parse_cluepacket(self, raw):
        simple = SAXSimple()

        parser = xml.sax.make_parser()
        parser.setContentHandler(simple)

        from xml.sax.xmlreader import InputSource
        import StringIO
        inpsrc = InputSource()
        inpsrc.setByteStream(StringIO.StringIO(raw))

        parser.parse(inpsrc)
        return simple.tree
        
import socket

try:
    server = TCPServer(('', 5913), DashboardServer)
    server.serve_forever()
except KeyboardInterrupt:
    print
except socket.error, e:
    print "Socket error: %s" % e[1]
