#!/usr/bin/python
import sys
import datetime
import logging
import libxml2
from optparse import OptionParser
from M2Crypto.SSL import Context

# pubsub import must come first because it overloads part of the
# StanzaProcessor class
from ligo.lvalert import pubsub

from pyxmpp.all import JID,TLSSettings
from pyxmpp.jabber.all import Client

"""
A tool to send an event to a pubsub node 
"""

__version__ = "$Revision$"
__date__ = "$Date$"
__name__ = "lvalert_send"
__Id__ = "$Id$"
__title__ = "LIGO-Virgo Alert Sender"

#################################################################
# help message
usage = """\
%prog [options]
-----------------------------------------------------------------

  A tool to send to the pubsub service at lvalert.phys.uwm.edu.
  LSC-Virgo members can activate their accounts on this server by
  completing the form at
  
    https://www.lsc-group.phys.uwm.edu/cgi-bin/jabber-acct.cgi 

  and setting a password.  Before using this program to send to a
  node, the node should be created with lvalert_admin and/or
  configured to allow you to send to it. 

  You can send information containted in the file stuff.xml by
  doing:

  %prog --username albert.einstein --password secret --node small_steps --file stuff.xml --max_attempts N

  When an stuff.xml is sent to a node by a publisher, it will be sent
  to all subscribers who are logged in.

  The owner (person who creates the node by default) can delete and
  publish information to the node. The owner can also add other
  publishers to the node. Configuration and management of nodes is
  handled with lvalert_admin. 

  If the server is busy, the connection is allowed to timeout N
  times before failing.
  
  Others can subscribe any existing node. Run 

  lvalert_admin --help

  to find out how to manage your subscriptions. 

"""

#################################################################
def parse_command_line():
  """
  Parser function dedicated
  """
  parser = OptionParser( usage=usage, \
      version= "%prog CVS\n" +
      "$Id$\n" +
      "$Name$\n")

  #username and password
  parser.add_option("-a","--username",action="store",type="string",\
      default="", help="the username of the publisher or listener" )
  parser.add_option("-b","--password",action="store",type="string",\
      default="", help="the password of the publisher or listener" )
  parser.add_option("-s","--server",action="store",type="string",\
      default="lvalert.phys.uwm.edu", help="the pubsub server" )
  parser.add_option("-r","--resource",action="store",type="string",\
      default="sender", help="resource to use in JID" )

  # access information about root nodes
  parser.add_option("-n","--node",action="store",type="string",\
      default=None, help="name of the node on the pubsub server" )
  parser.add_option("-p","--file",action="store",type="string",\
      default=None, help="name of the file with the event" )

  # debugging options
  parser.add_option("-v","--verbose",action="store_true",\
      default=False, help="be verbose as you process the request" )
  parser.add_option("-g","--debug",action="store_true",\
      default=False, help="should  print out lots of information" )

  # timeout attempts
  parser.add_option("-m","--max_attempts",action="store_true",\
      default=10, help="max number of timeouts allowed" )

  (options,args) = parser.parse_args()

  if not options.username:
    raise ValueError, "--username is required"

  if not options.node:
    raise ValueError, "--node is required"

  if not options.file:
    raise ValueError, "--file is required"

  return options, sys.argv[1:]
  
# ============================================================================
# -- get command line arguments
opts, args = parse_command_line()

# add a logger so that we can see what's going
logger=logging.getLogger(__name__)
logger.addHandler(logging.StreamHandler())
if opts.debug:
    logger.setLevel(logging.DEBUG)
elif opts.verbose:
    logger.setLevel(logging.INFO)
else:
    logger.setLevel(logging.ERROR)

def onStart(client):
    node = opts.node

    if opts.file == '-':
        eventfile = sys.stdin
    else:
        eventfile = open(opts.file)
    voevent = eventfile.read()
    eventfile.close()

    recpt=JID("pubsub."+opts.server)

    client.sendMessage(node, voevent, recpt)

class MyClient(Client):
    def __init__(self,jid,password,max_attempts,onStart):
        self.jid = jid
        self.max_attempts = max_attempts
        self.onStart = onStart

        # we require a TLS connection
        #  Specify sslv3 to get around Sun Java SSL bug handling session ticket
        #  https://rt.phys.uwm.edu/Ticket/Display.html?id=1825
        #  http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6728126
        t=TLSSettings(require=True,verify_peer=False, ctx=Context('sslv3'))

        # setup client with provided connection information
        # and identity data
        Client.__init__(self, self.jid, password, \
            auth_methods=["sasl:GSSAPI","sasl:PLAIN"], tls_settings=t)

        # A counter for the idle function, below.
        self.counter = 0

    def stream_state_changed(self,state,arg):
        """This one is called when the state of stream connecting the component
        to a server changes. This will usually be used to let the user
        know what is going on."""
        logger.info("*** State changed: %s %r ***" % (state,arg))

    def session_started(self):
        logger.info(datetime.datetime.now().ctime())
        self.onStart(self)

    def sendMessage(self,node,msg,recpt):
        ps = pubsub.PubSub(from_jid = self.jid, to_jid = recpt, stream = self.stream,
                           stanza_type = "get")
        ps.publish(msg,node)
        self.stream.set_response_handlers(ps, 
                self.onSuccess, 
                self.onError,
                lambda stanza: self.onTimeout(stanza,node,msg,recpt))
        self.stream.send(ps)
       
    def onSuccess(self,stanza):
        logger.info("operation successful.")
        self.disconnect()
        return True

    def onError(self,stanza):
        errorNode = stanza.get_error()
        logger.error("error type = %s" % errorNode.get_type())
        logger.error("error message = %s" % errorNode.get_message())
        logger.info("disconnecting")
        self.disconnect()
        sys.exit(1)
        return True

    def onTimeout(self,stanza,node,msg,recpt):
        logger.info("operation timed out.  Trying again...")
        if self.counter < self.max_attempts:
            self.counter = self.counter + 1
            self.sendMessage(node,msg,recpt)
        else:
            print "Reached max_attempts. Disconnecting..."
            self.disconnect()
            sys.exit(1)
        return True

# Check for file
if not opts.file:
   print "You must supply a file to send"
   sys.exit(1)

# Debug the memory
libxml2.debugMemory(1)

# Instantiate the client
jid=JID(opts.username+"@"+opts.server+"/"+opts.resource)
# if bare JID is provided add a resource -- it is required
if not jid.resource:
    jid=JID(jid.node, jid.domain, "sender")

client = MyClient(jid,opts.password,opts.max_attempts,onStart)

# Connect
logger.info("connecting...")
client.connect()

try:
    client.loop(1)
except KeyboardInterrupt:
    print u"disconnecting..."
    client.disconnect()

# vi: sts=4 et sw=4
