# Copyright (C) Patrick Brady, Brian Moe, Branson Stephens (2015)
#
# This file is part of lvalert
#
# lvalert is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# It is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with lvalert.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function
import sys
import os
import datetime
import logging
import libxml2
import getpass
import uuid
from optparse import OptionParser

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

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

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

__name__ = "lvalert_send"

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

  A tool to send to the pubsub service at lvalert.cgca.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 --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. 

"""

#################################################################
parser = OptionParser(usage=usage)

#username and password
parser.add_option("-a","--username",action="store",type="string",\
  help="the username of the publisher or listener" )
parser.add_option("-s","--server",action="store",type="string",\
  default="lvalert.cgca.uwm.edu", help="the pubsub server" )
parser.add_option("-r","--resource",action="store",type="string",\
  default=uuid.uuid4().hex, help="resource to use in JID [default: random]" )
parser.add_option("--netrc", "-N",
  help="Load username and password from this file",
  default=os.environ.get('NETRC', '~/.netrc'))

# 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" )

# version
parser.add_option("-w", "--version", action="store_true",
  default=False, help="display version information")

# ============================================================================
# -- get command line arguments
opts, args = parser.parse_args()

if opts.version:
    import pkg_resources
    version = pkg_resources.require("ligo-lvalert")[0].version
    print("LVAlert v. %s" % version)
    exit(0)

try:
    default_username, _, default_password = safe_netrc(os.path.expanduser(opts.netrc)).authenticators(opts.server)
except IOError:
    default_username, default_password = None, None

myusername = opts.username or default_username
if not myusername:
    parser.error('--username is required')
if myusername == default_username:
    mypassword = default_password
else:
    mypassword = getpass.getpass('password for ' + myusername + ': ')

if not opts.node:
    raise ValueError("--node is required")

if not opts.file:
    raise ValueError("--file is required")

# 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
        t=TLSSettings(require=True,verify_peer=False)

        # 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
# append UUID to resource to guarantee that it is non-empty and unique
jid=JID(myusername+"@"+opts.server+"/"+opts.resource)
client = MyClient(jid,mypassword,opts.max_attempts,onStart)

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

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

# vi: sts=4 et sw=4
