# -*- coding: utf-8 -*-
from pyparsing import *
import sys
import ACLMessage
import AID
from BasicFipaDateTime import *
 
import xml.sax
from xml.sax import handler
 
 
class ACLParser:
    """
    Parser for the ACL language
    """
 
    def __init__(self):
 
        lpar = Literal("(").suppress()
        rpar = Literal(")").suppress()
        digit = oneOf("0 1 2 3 4 5 6 7 8 9")
        sign = oneOf("+ -")
        dot = Literal(".")
        Exponent = oneOf("e E")
 
        _bslash = "\\"
        _escapables = 'tnrfbacdeghijklmopqsuvwxyz"' + _bslash
        _octDigits = "01234567"
        _escapedChar = (Word(_bslash, _escapables, exact=2) | Word(_bslash, _octDigits, min=2, max=4))
 
        _sglQuote = Literal("'")
        _dblQuote = Literal('"')
        StringLiteral = Combine(_dblQuote.suppress() + ZeroOrMore(CharsNotIn('\\"\r') | _escapedChar) + _dblQuote.suppress()).streamline()
 
        #Word = [~ "\0x00" – "\0x20", "(", ")", "#", "0" – "9", "-", "@"]
        #       [~ "\0x00" – "\0x20", "(", ")"]*
        ACLWord = Word(alphanums + "#@.-")
        #StringLiteral = "\"" ([ ~ "\"" ] | "\\\"")* "\""
        #StringLiteral = Combine(Literal('"')+OneOrMore(Word(alphanums+"#@ .:-_[]()+?'¿¡!$%&=*,,;<>/\\"))+Literal('"'))
 
        #ByteLengthEncodedString = "#" Digit+ "\"" <byte sequence>
        ByteLengthEncodedString = Literal("TODO")
        String = (StringLiteral | ByteLengthEncodedString)
 
        Year = Combine(digit + digit + digit + digit)
        Month = Combine(digit + digit)
        Day = Combine(digit + digit)
        Hour = Combine(digit + digit)
        Minute = Combine(digit + digit)
        Second = Combine(digit + digit)
        MilliSecond = Combine(digit + digit + digit)
        DateTime = Combine(Optional(sign) + Year + Month + Day + Literal("T") + Hour + Minute + Second + MilliSecond)
 
        FloatExponent = Combine(Exponent + Optional(sign) + OneOrMore(digit))
        FloatMantissa = Combine((OneOrMore(digit) + dot + ZeroOrMore(digit)) | (ZeroOrMore(digit) + dot + OneOrMore(digit)))
        Float = Combine((Optional(sign) + FloatMantissa + Optional(FloatExponent)) | (Optional(sign) + OneOrMore(digit) + FloatExponent))
 
        Integer = Combine(Optional(sign) + OneOrMore(digit))
        Number = Group(Integer | Float)
 
        """
        #bnf de una URL
        void=empty
            digits = nums
        hex = nums + "abcdefABCDEF"
        safe =  "$-_@.&+-"
        extra = "!*\"'(),"
        escape =  "%" + hex + hex
        xalphas = Word(alphanums+safe+extra+escape)
        ialpha = (Word(alphas) + Optional(xalphas))
        alphanum2 = alphanums+"-"+"_"+"."+"+"
        password = Forward()
        password << (alphanum2+Optional(password))
        user=Forward()
        user << (alphanum2+Optional(user))
        search = Forward()
        search << (xalphas + Optional(Literal("+")+search))
        segment = xalphas
        path = Forward()
        path << (void | (segment + Optional(Literal("/")+path)))
        port = digits
        hostname = Forward()
        hostnumber = (digits + dot + digits + dot + digits + dot + digits)
        hostname << ialpha + Optional(dot + hostname)
        formcode = (Literal("N")|Literal("T")|Literal("C"))
        ftptype = ((Literal("A")+formcode)|(Literal("E")+formcode)|Literal("I")|(Literal("L")+digits))
        host = (hostname|hostnumber)
        hostport = (host + Optional(Literal(":")+port))
        login = (Optional(user + Optional(Literal(":")+password) + Literal("@")) + hostport )
        mailtoaddress = Combine(Literal("mailto:")+ xalphas + Literal("@") + hostname)
        ftpaddress = Combine(Literal("ftp://")+login+Literal("/")+path+Optional(Literal(";")+ftptype))
        httpaddress = Combine(Literal("http://")+hostport+Optional(Literal("/")+path) +Optional(Literal("?")+search))
        URL = (httpaddress|ftpaddress|mailtoaddress)
        """
 
        #URL = Word(alphanums+":/#@.")
        URL = Word(alphanums + ":/#@.-")
 
        URLSequence = (lpar + Literal("sequence").suppress() + OneOrMore(URL) + rpar)  # .setResultsName("URLseq")
 
        AgentIdentifier = Forward()
 
        AgentIdentifierSequence = Group(lpar + Literal("sequence").suppress() + OneOrMore(AgentIdentifier) + rpar)  # .setResultsName("AIDseq")
 
        #AddressSequence  = Group(lpar + Literal("sequence").suppress() + OneOrMore(URL)+rpar)   #Word(alphanums+"/.:+?")) + rpar)
 
        AgentIdentifier << Group(
            lpar + Literal("agent-identifier").suppress() +
            #Literal(":name").suppress() + ACLWord.setResultsName("name") +
            Literal(":name").suppress() + URL.setResultsName("name") +
            Optional(Literal(":addresses").suppress() + URLSequence.setResultsName("addresses")) +
            Optional(Literal(":resolvers").suppress() + AgentIdentifierSequence.setResultsName("resolvers")) +
            # This one for the X-tras (thanks Jade)
            # Make it more general-case oriented
            Optional(Literal(":X-JADE-agent-classname").suppress() + URL.suppress()) +
            rpar)  # .setResultsName("AID")
 
        #AgentIdentifier << Group(lpar + Literal("agent-identifier").suppress() + Literal(":name").suppress() + Word(alphanums+"@.").setResultsName("name") + Optional(Literal(":addresses").suppress() + URLSequence.setResultsName("addresses")) + Optional(Literal(":resolvers").suppress() + AgentIdentifierSequence.setResultsName("resolvers")) + rpar)#.setResultsName("AID")
 
        AgentIdentifierSet = Group(lpar + Literal("set").suppress() + OneOrMore(AgentIdentifier) + rpar)
 
        Expression = Forward()
        Expression << (ACLWord | String | Number | DateTime | (lpar + Expression + rpar))
        #Expression << (Word(alphanums).setResultsName("word") | String.setResultsName("string") | Number.setResultsName("number") | DateTime.setResultsName("datetime") | Combine(lpar + Expression + rpar).setResultsName("expression"))
 
        MessageParameter = (
            Literal(":sender").suppress() + AgentIdentifier.setResultsName("sender") |
            Literal(":receiver").suppress() + AgentIdentifierSet.setResultsName("receiver") |
            Literal(":content").suppress() + String.setResultsName("content") |
            #Literal(":reply-with").suppress() + Expression.setResultsName("reply-with") |
            Literal(":reply-with").suppress() + URL.setResultsName("reply-with") |
            Literal(":reply-by").suppress() + DateTime.setResultsName("reply-by") |
            Literal(":in-reply-to").suppress() + Expression.setResultsName("in-reply-to") |
            Literal(":reply-to").suppress() + AgentIdentifierSet.setResultsName("reply-to") |
            Literal(":language").suppress() + Expression.setResultsName("language") |
            Literal(":encoding").suppress() + Expression.setResultsName("encoding") |
            Literal(":ontology").suppress() + Expression.setResultsName("ontology") |
            Literal(":protocol").suppress() + ACLWord.setResultsName("protocol") |
            #Literal(":conversation-id").suppress() + Expression.setResultsName("conversation-id")
            Literal(":conversation-id").suppress() + URL.setResultsName("conversation-id")
        )
 
        MessageType = (
            Literal("accept-proposal") |
            Literal("agree") |
            Literal("cancel") |
            Literal("cfp") |
            Literal("confirm") |
            Literal("disconfirm") |
            Literal("failure") |
            Literal("inform") |
            Literal("inform-if") |
            Literal("inform-ref") |
            Literal("not-understood") |
            Literal("propagate") |
            Literal("propose") |
            Literal("proxy") |
            Literal("query-if") |
            Literal("query-ref") |
            Literal("refuse") |
            Literal("reject-proposal") |
            Literal("request") |
            Literal("request-when") |
            Literal("request-whenever") |
            Literal("subscribe") |
            # I'm looking at you, Jade!
            Literal("ACCEPT-PROPOSAL") |
            Literal("AGREE") |
            Literal("CANCEL") |
            Literal("CFP") |
            Literal("CONFIRM") |
            Literal("DISCONFIRM") |
            Literal("FAILURE") |
            Literal("INFORM") |
            Literal("INFORM-IF") |
            Literal("INFORM-REF") |
            Literal("NOT-UNDERSTOOD") |
            Literal("PROPAGATE") |
            Literal("PROPOSE") |
            Literal("PROXY") |
            Literal("QUERY-IF") |
            Literal("QUERY-REF") |
            Literal("REFUSE") |
            Literal("REJECT-PROPOSAL") |
            Literal("REQUEST") |
            Literal("REQUEST-WHEN") |
            Literal("REQUEST-WHENEVER") |
            Literal("SUBSCRIBE")
        )
 
        Message = (lpar + MessageType.setResultsName("msgtype") + OneOrMore(MessageParameter.setResultsName("msgParameter")) + rpar)  # .setResultsName("message")
 
        ACLCommunicativeAct = Message
 
        self.bnf = ACLCommunicativeAct
 
        #bnf = OneOrMore(line).setResultsName("program")
 
        #bnf.ignore(comment)
        #bnf.ignore(directive)
 
        try:
            self.bnf.validate()
 
        except Exception, err:
            print err
            sys.exit(-1)
 
        #bnf.setDebug()
    def processAID(self, _aid):
        """
        parses an AID.
        returns a pyparsing.ParseResult class
        """
 
        aid = AID.aid()
 
        if 'name' in _aid:
            aid.setName(_aid['name'])
 
        if 'addresses' in _aid:
            addr = _aid['addresses']
            for i in addr:
                aid.addAddress(i)
 
        if 'resolvers' in _aid:
            res = _aid['resolvers']
            for i in res:
                aid.addResolvers(self.processAID(i))
 
        return aid
 
    def parse(self, string):
        """
        parses a string
        returns a pyparsing.ParseResult class
        """
 
        try:
            m = self.bnf.parseString(string)
        except ParseException, err:
            print err.line
            print " " * (err.column - 1) + "^"
            print err
            sys.exit(-1)
        except Exception, err:
            print "Unkwonw Exception"
            print err
            sys.exit(-1)
 
        return self.buildACL(m)
 
    def parseFile(self, file):
        """
        parses a file
        returns an ACLMessage
        """
 
        try:
            m = self.bnf.parseFile(file)
        except ParseException, err:
            print err.line
            print " " * (err.column - 1) + "^"
            print err
            sys.exit(-1)
        except Exception, err:
            print "Unkwonw Exception"
            print err
            sys.exit(-1)
 
        return self.buildACL(m)
 
    def buildACL(self, m):
        """
        returns an ACLMessage object from a pyparsing.ParseResults object
        """
 
        #print repr(m)
        #print m.asXML()
        #print m.asList()
 
        msg = ACLMessage.ACLMessage()
 
        if 'msgtype' in m:
            msg.setPerformative(m['msgtype'])
 
        if 'sender' in m:
            msg.setSender(self.processAID(m['sender']))
 
        if 'receiver' in m:
            recv = m['receiver']
            for i in recv:
                msg.addReceiver(self.processAID(i))
 
        if 'content' in m:
            msg.setContent(m['content'])
 
        if 'reply-with' in m:
            msg.setReplyWith(m['reply-with'])
 
        if 'reply-by' in m:
            msg.setReplyBy(BasicFipaDateTime(m['reply-by']))
 
        if 'in-reply-to' in m:
            msg.setInReplyTo(m['in-reply-to'])
 
        if 'reply-to' in m:
            r = m['reply-to']
            for i in r:
                msg.AddReplyTo(self.processAID(i))
 
        if 'language' in m:
            msg.setLanguage(m['language'])
 
        if 'encoding' in m:
            msg.setEncoding(m['encoding'])
 
        if 'ontology' in m:
            msg.setOntology(m['ontology'])
 
        if 'protocol' in m:
            msg.setProtocol(m['protocol'])
 
        if 'conversation-id' in m:
            msg.setConversationId(m['conversation-id'])
 
        return msg
 
 
class ACLxmlParser(handler.ContentHandler):
 
    def __init__(self):
 
        #constants
        self.FIPA_MESSAGE_TAG = "fipa-message"
        self.ACT_TAG = "act"
        self.CONVERSATION_ID_TAG = "conversation-id"
        self.SENDER_TAG = "sender"
        self.RECEIVER_TAG = "receiver"
        self.CONTENT_TAG = "content"
        self.LANGUAGE_TAG = "language"
        self.ENCODING_TAG = "encoding"
        self.ONTOLOGY_TAG = "ontology"
        self.PROTOCOL_TAG = "protocol"
        self.REPLY_WITH_TAG = "reply-with"
        self.IN_REPLY_TO_TAG = "in-reply-to"
        self.REPLY_BY_TAG = "reply-by"
        self.REPLY_TO_TAG = "reply-to"
        self.CONVERSATION_ID_TAG = "conversation-id"
        self.AGENT_ID_TAG = "agent-identifier"
        self.NAME_TAG = "name"
        self.ADDRESSES_TAG = "addresses"
        self.URL_TAG = "url"
        self.RESOLVERS_TAG = "resolvers"
        self.USER_DEFINED_TAG = "user-defined"
        self.TIME_TAG = "time"
        self.ID_TAG = "id"
        self.HREF_TAG = "href"
        self.OT = "<"
        self.ET = "</"
        self.CT = ">"
        self.NULL = ""
 
    """
    ***************************************************
    *               Decoding methods                  *
    ***************************************************
    """
 
    def startDocument(self):
        self.msg = ACLMessage.ACLMessage()
 
    def endDocument(self):
        pass
 
    #This method is called when exist characters in the elements
    def characters(self, buff):
        self.accumulator = self.accumulator + buff
 
    def startElement(self, localName, attributes):
 
        self.accumulator = ""
 
        if self.FIPA_MESSAGE_TAG == localName.lower():
            self.msg.setPerformative(attributes.getValue(self.ACT_TAG))
            try:
                self.msg.setConversationId(attributes.getValue(self.CONVERSATION_ID_TAG))
 
            except:
                pass
 
        if self.SENDER_TAG == localName.lower():
            self.aid = AID.aid()
            self.aidTag = self.SENDER_TAG
 
        if self.RECEIVER_TAG == localName.lower():
            self.aid = AID.aid()
            self.aidTag = self.RECEIVER_TAG
 
        if self.REPLY_TO_TAG == localName.lower():
            self.aid = AID.aid()
            self.aidTag = self.REPLY_TO_TAG
 
        if self.RESOLVERS_TAG == localName.lower():
            self.aid = AID.aid()
            self.aidTag = self.RESOLVERS_TAG
 
        if self.REPLY_BY_TAG == localName.lower():
            self.msg.setReplyBy(BasicFipaDateTime(attributes.getValue(self.TIME_TAG)))
 
        if self.NAME_TAG == localName.lower():
            self.aid.setName(attributes.getValue(self.ID_TAG))
 
        if self.URL_TAG == localName.lower():
            self.aid.addAddress(attributes.getValue(self.HREF_TAG))
 
    def endElement(self, localName):
 
        if self.CONTENT_TAG == localName.lower():
            self.msg.setContent(self.accumulator)
 
        if self.LANGUAGE_TAG == localName.lower():
            self.msg.setLanguage(self.accumulator)
 
        if self.ENCODING_TAG == localName.lower():
            self.msg.setEncoding(self.accumulator)
 
        if self.ONTOLOGY_TAG == localName.lower():
            self.msg.setOntology(self.accumulator)
 
        if self.PROTOCOL_TAG == localName.lower():
            self.msg.setProtocol(self.accumulator)
 
        if self.REPLY_WITH_TAG == localName.lower():
            self.msg.setReplyWith(self.accumulator)
 
        if self.IN_REPLY_TO_TAG == localName.lower():
            self.msg.setInReplyTo(self.accumulator)
 
        if self.REPLY_TO_TAG == localName.lower() or \
            self.SENDER_TAG == localName.lower() or \
            self.RECEIVER_TAG == localName.lower() or \
                self.RESOLVERS_TAG == localName.lower():
            self.aidTag = ""
 
        if self.CONVERSATION_ID_TAG == localName.lower():
            self.msg.setConversationId(self.accumulator)
 
        if self.AGENT_ID_TAG == localName.lower():
            if self.aidTag == self.SENDER_TAG:
                self.msg.setSender(self.aid)
            elif self.aidTag == self.RECEIVER_TAG:
                self.msg.addReceiver(self.aid)
            elif self.aidTag == self.REPLY_TO_TAG:
                self.msg.addReplyTo(self.aid)
            elif self.aidTag == self.RESOLVERS_TAG:
                self.msg.addResolvers(self.aid)
 
    """
      This does the following:
      < tag >
         content
      </ tag >
    """
    def encodeTag(self, tag, content, proptag=None, propcontent=None):
        sb = self.OT + tag
        if proptag is not None:
            sb = sb + " " + proptag + '="' + str(propcontent) + '"'
 
        if content is None or content == "":
            sb = sb + "/" + self.CT
            return sb
        sb = sb + self.CT
        sb = sb + content
        sb = sb + self.ET + tag + self.CT
 
        return sb
 
    """ Encode the information of Agent, Tags To and From """
    def encodeAid(self, aid):
 
        sb = self.OT + self.AGENT_ID_TAG + self.CT
        sb = sb + self.encodeTag(self.NAME_TAG, None, self.ID_TAG, aid.getName())
 
        sb = sb + self.OT + self.ADDRESSES_TAG + self.CT
        addresses = aid.getAddresses()
        for addr in addresses:
            sb = sb + self.encodeTag(self.URL_TAG, "", self.HREF_TAG, addr)
        sb = sb + self.ET + self.ADDRESSES_TAG + self.CT
 
        resolvers = aid.getResolvers()
        if len(resolvers) > 0:
            sb = sb + self.OT + self.RESOLVERS_TAG + self.CT
            for res in resolvers:
                sb = sb + self.encodeAid(res)
            sb = sb + self.ET + self.RESOLVERS_TAG + self.CT
 
        sb = sb + self.ET + self.AGENT_ID_TAG + self.CT
 
        return sb
 
    def encodeXML(self, msg):
 
        sb = self.OT + self.FIPA_MESSAGE_TAG
        if msg.getPerformative():
            sb += " " + self.ACT_TAG + '="' + msg.getPerformative() + '"'
        sb += self.CT
 
        #sender
        if msg.getSender():
            sb += self.OT + self.SENDER_TAG + self.CT
            sb += self.encodeAid(msg.getSender())
            sb += self.ET + self.SENDER_TAG + self.CT
 
        #receivers
        if len(msg.getReceivers()) > 0:
            sb += self.OT + self.RECEIVER_TAG + self.CT
            for r in msg.getReceivers():
                sb += self.encodeAid(r)
            sb += self.ET + self.RECEIVER_TAG + self.CT
 
        if msg.getContent():
            sb += self.encodeTag(self.CONTENT_TAG, str(msg.getContent()))
 
        if msg.getLanguage():
            sb += self.encodeTag(self.LANGUAGE_TAG, msg.getLanguage())
 
        if msg.getEncoding():
            sb += self.encodeTag(self.ENCODING_TAG, msg.getEncoding())
 
        if msg.getOntology():
            sb += self.encodeTag(self.ONTOLOGY_TAG, msg.getOntology())
 
        if msg.getProtocol():
            sb += self.encodeTag(self.PROTOCOL_TAG, msg.getProtocol())
 
        if msg.getReplyWith():
            sb += self.encodeTag(self.REPLY_WITH_TAG, msg.getReplyWith())
 
        if msg.getInReplyTo():
            sb += self.encodeTag(self.IN_REPLY_TO_TAG, msg.getInReplyTo())
 
        if msg.getReplyBy():
            date = BasicFipaDateTime()
            date.fromString(str(msg.getReplyBy()))
            sb += self.encodeTag(self.REPLY_BY_TAG, str(date))
 
        if len(msg.getReplyTo()) > 0:
            sb += self.OT + self.REPLY_TO_TAG + self.CT
            for e in msg.getReplyTo():
                sb += self.encodeAid(e)
            sb += self.ET + self.REPLY_TO_TAG + self.CT
 
        if msg.getConversationId():
            sb += self.encodeTag(self.CONVERSATION_ID_TAG, msg.getConversationId())
 
        sb += self.ET + self.FIPA_MESSAGE_TAG + self.CT
 
        return sb
 
    def parse(self, _in):
        """
        parses the xml input
        """
        xml.sax.parseString(_in, self)
        return self.msg
 
    def parseFile(self, file):
        xml.sax.parse(file, self)
        return self.msg
 
 
#p = ACLParser()
#msg = p.parse("message3.acl")
#print msg
if __name__ == "__main__":
 
    p = ACLxmlParser()
    m = p.parseFile("m.xml")
    print m
    print p.encodeXML(m)
 
 
 
#Debug print
#print program.asXML("instruction")
#for line in program:
#	print line
#	for k in line.keys():
#		print k + ':' + str(line[k])
#	print
 
 
#exception control
#	except ParseException,err:
#		print err.line
#		print " "*(err.column-1)+"^"
#		print err
#
#	except RecursiveGrammarException, err:
#		print err.line
#		print err