#!/usr/bin/env python
# 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/>.

import netrc
import sys
import os
import time
import select
import logging
import libxml2
import getpass
import uuid
from optparse import *
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,Iq,Presence,Message,StreamError,TLSSettings
from pyxmpp.jabber.all import Client
from pyxmpp.jabber.dataforms import Form,DATAFORM_NS
from pyxmpp.interface import implements
from pyxmpp.interfaces import *
from pyxmpp.exceptions import ClientError, FatalClientError

"""
A tool to administer the pubsub service of openfire
"""

#################################################################
# help message
usage = """\
%prog [options]
------------------------------------------------------------------------------
  A tool to administer the pubsub service of openfire 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 typing your password. Then you can use this program to do the
  following things:

  1.  create a pubsub node called small_steps

  %prog --username albert.einstein --create --node small_steps

  2.  delete a pubsub node called small_steps

  %prog --username albert.einstein --delete --node small_steps

  4.  subscribe to a pubsub node called lvalert_test

  %prog --username albert.einstein --subscribe --node lvalert_test

  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 using the command
  
  %prog --username albert.einstein --add-publisher patrick.brady --node small_steps
  
  Others can subscribe. To see a list of available nodes:
  
  %prog --username albert.einstein --get-nodes 

  To see the nodes to which you are subscribed:

  %prog --username albert.einstein --subscriptions
  
  There are two programs called lvalert_listen and lvalert_send which
  provide ways to listen for content on the node and to publish
  information to the node respectively.

"""

#################################################################
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("-c","--server",action="store",type="string",\
  default="lvalert.cgca.uwm.edu", help="the pubsub server" )
parser.add_option("-r","--resource",action="store",type="string",\
  default="admin", help="resource to use in JID" )
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("-d","--create",action="store_true",\
  default=False, help="create a new pubsub node" )
parser.add_option("-e","--delete",action="store_true",\
  default=False, help="delete a new pubsub node" )
parser.add_option("-f","--subscribe",action="store_true",\
  default=False, help="subscribe to a new pubsub node" )
parser.add_option("-g","--unsubscribe",action="store_true",\
  default=False, help="unsubscribe from a new pubsub node" )
parser.add_option("-n","--unsubscribe-subid",action="store",type="string",\
  default=None, help="subid of subscription obtained by running subscriptions" )
parser.add_option("-i","--subscriptions",action="store_true",\
  default=False, help="return a list of subscriptions" )
parser.add_option("-j","--add-publisher",action="store",type="string",\
  default=None, help="jabber id of person allowed to publish to node" )
parser.add_option("-m","--get-nodes",action="store_true",\
  default=False, help="get the list of existing nodes" )
parser.add_option("-o","--affiliations",action="store",type="string",\
  default=None, help="get the list of affiliations to a node" )
parser.add_option("-p","--delete-publisher",action="store",type="string",\
  default=None, help="jid of publisher to delete from a node" )
parser.add_option("-q","--node",action="store",type="string",\
  default=None, help="name of node" )
# debugging options
parser.add_option("-l","--debug",action="store_true",\
  default=False, help="should  print out lots of information" )
parser.add_option("-v","--verbose",action="store_true",\
  default=False, help="be verbose as you process the request" )
# 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)

if (opts.add_publisher or opts.create or opts.delete or opts.subscribe or opts.unsubscribe or opts.delete_publisher) and not opts.node:
    parser.error("--node must be provided")

try:
    default_username, _, default_password = netrc.netrc(os.path.expanduser(opts.netrc)).authenticators(opts.server)
except:
    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 + ': ')

class MyClient(Client):
    def __init__(self, jid, password):
        # 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
        # NOTE: This is no longer necessary after Openfire 3.7.
        # (See update to RT ticket above.)
        #t=TLSSettings(require=True,verify_peer=False, ctx=Context('sslv3'))
        t=TLSSettings(require=True,verify_peer=False)

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


    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."""
        if opts.verbose:
            print "*** State changed: %s %r ***" % (state,arg)
        else:
            pass

    def session_started(self):
        self.stream.send(Presence())
        if opts.get_nodes:
            self.stream.set_response_handlers(pspl, \
                pspl.get_nodes_result,pspl.create_error,\
                pspl.create_timeout)
        if opts.create:
            self.stream.set_response_handlers(pspl, \
                pspl.generic_result,pspl.create_error,\
                pspl.create_timeout)
        if opts.delete:
            self.stream.set_response_handlers(pspl, \
                pspl.generic_result,pspl.generic_error,\
                pspl.generic_timeout)
        if opts.subscribe:
            self.stream.set_response_handlers(pspl, \
                pspl.subscribe_result,pspl.subscribe_error,\
                pspl.subscribe_timeout)
        if opts.affiliations:
            self.stream.set_response_handlers(pspl, \
                pspl.affiliations_result,pspl.generic_error,\
                pspl.generic_timeout)
        if opts.add_publisher:
            self.stream.set_response_handlers(pspl, \
                pspl.generic_result,pspl.generic_error,\
                pspl.generic_timeout)
        if opts.delete_publisher:
            self.stream.set_response_handlers(pspl, \
                pspl.generic_result,pspl.generic_error,\
                pspl.generic_timeout)
        if opts.unsubscribe:
            self.stream.set_response_handlers(pspl, \
                pspl.generic_result,pspl.generic_error,\
                pspl.generic_timeout)
        if opts.subscriptions:
            self.stream.set_response_handlers(pspl, \
                pspl.subscriptions_result,pspl.subscribe_error,\
                pspl.subscribe_timeout)
        self.stream.send(pspl)

    def idle(self):
        if self.stream and self.session_established:
            if opts.verbose:
                print "Disconnecting"
            self.disconnect()
        if opts.verbose:
            print "idle"
        time.sleep(4)

    def post_disconnect(self):
        print "Disconnected"
        raise Disconnected


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

# debug the memore
libxml2.debugMemory(1)

# set up the stream
# append UUID to resource to guarantee that it is non-empty and unique
myjid=JID(myusername+"@"+opts.server+"/"+opts.resource+uuid.uuid4().hex)
s=MyClient(jid=myjid,password=mypassword)

if opts.verbose:
    print "connecting..."
s.connect()

if opts.verbose:
    print "build pubsub stanza..."
recpt=JID("pubsub."+opts.server)
pspl=pubsub.PubSub(from_jid = myjid, to_jid = recpt, stream = s,\
stanza_type="get")
if opts.get_nodes:
    print "Getting nodes"
    pspl.get_nodes()
elif opts.create:
    print "Creating node " + opts.node
    pspl.create_node(opts.node)
elif opts.delete:
    pspl.delete_node(opts.node)
elif opts.subscribe:
    pspl.subscribe(myjid,opts.node)
elif opts.unsubscribe:
    pspl.unsubscribe(myjid,opts.node,opts.unsubscribe_subid)
elif opts.subscriptions:
    pspl.subscriptions(myjid)
elif opts.affiliations:
    pspl.affiliations(myjid,opts.affiliations)
elif opts.add_publisher:
    publisher_jid = opts.add_publisher.split('@')[0]
    publisher_jid=JID(publisher_jid+"@"+opts.server+"/Home")
    pspl.publisher(publisher_jid,opts.node,"publisher")
elif opts.delete_publisher:
    publisher_jid = opts.delete_publisher.split('@')[0]
    publisher_jid=JID(publisher_jid+"@"+opts.server+"/Home")
    pspl.publisher(publisher_jid,opts.node,"none")
else:
    pspl.get_nodes()

if opts.verbose:
    print "sending message..."

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

# vi: sts=4 et sw=4
