#!/usr/bin/env python

#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#

import getopt
import sys
import socket
import os
from qpid import qmfconsole

def Usage ():
    print "Usage:  qpid-route [OPTIONS] link add  <dest-broker> <src-broker>"
    print "        qpid-route [OPTIONS] link del  <dest-broker> <src-broker>"
    print "        qpid-route [OPTIONS] link list [<dest-broker>]"
    print
    print "        qpid-route [OPTIONS] route add   <dest-broker> <src-broker> <exchange> <routing-key> [tag] [exclude-list]"
    print "        qpid-route [OPTIONS] route del   <dest-broker> <src-broker> <exchange> <routing-key>"
    print "        qpid-route [OPTIONS] route list  [<dest-broker>]"
    print "        qpid-route [OPTIONS] route flush [<dest-broker>]"
    print "        qpid-route [OPTIONS] dynamic add <dest-broker> <src-broker> <exchange> [tag] [exclude-list]"
    print "        qpid-route [OPTIONS] dynamic del <dest-broker> <src-broker> <exchange>"
    print
    print "Options:"
    print "    -v [ --verbose ]         Verbose output"
    print "    -q [ --quiet ]           Quiet output, don't print duplicate warnings"
    print "    -d [ --durable ]         Added configuration shall be durable"
    print "    -e [ --del-empty-link ]  Delete link after deleting last route on the link"
    print "    -t <transport> [ --transport <transport>]"
    print "                             Specify transport to use for links, defaults to tcp"
    print
    print "  dest-broker and src-broker are in the form:  [username/password@] hostname | ip-address [:<port>]"
    print "  ex:  localhost, 10.1.1.7:10000, broker-host:10000, guest/guest@localhost"
    print
    sys.exit (1)

_verbose  = False
_quiet    = False
_durable  = False
_dellink  = False
_transport = "tcp"

class RouteManager:
    def __init__ (self, destBroker):
        self.dest = qmfconsole.BrokerURL(destBroker)
        self.src  = None
        self.qmf = qmfconsole.Session()
        self.broker = self.qmf.addBroker(destBroker)

    def Disconnect (self):
        self.qmf.delBroker(self.broker)

    def getLink (self):
        links = self.qmf.getObjects(_class="link")
        for link in links:
            if self.src.match(link.host, link.port):
                return link
        return None

    def AddLink (self, srcBroker):
        self.src = qmfconsole.BrokerURL(srcBroker)
        if self.dest.match(self.src.host, self.src.port):
            print "Linking broker to itself is not permitted"
            sys.exit(1)

        brokers = self.qmf.getObjects(_class="broker")
        broker = brokers[0]
        link = self.getLink()
        if link != None:
            raise Exception("Link already exists")

        if self.src.authName == "anonymous":
            mech = "ANONYMOUS"
        else:
            mech = "PLAIN"
        res = broker.connect(self.src.host, self.src.port, _durable,
                             mech, self.src.authName, self.src.authPass,
                             _transport)
        if _verbose:
            print "Connect method returned:", res.status, res.text
        link = self.getLink()

    def DelLink (self, srcBroker):
        self.src = qmfconsole.BrokerURL(srcBroker)
        brokers = self.qmf.getObjects(_class="broker")
        broker = brokers[0]
        link = self.getLink()
        if link == None:
            raise Exception("Link not found")

        res = link.close()
        if _verbose:
            print "Close method returned:", res.status, res.text

    def ListLinks (self):
        links = self.qmf.getObjects(_class="link")
        if len(links) == 0:
            print "No Links Found"
        else:
            print
            print "Host            Port    Durable  State             Last Error"
            print "==================================================================="
            for link in links:
                print "%-16s%-8d   %c     %-18s%s" % \
                (link.host, link.port, YN(link.durable), link.state, link.lastError)

    def AddRoute (self, srcBroker, exchange, routingKey, tag, excludes, dynamic=False):
        self.src = qmfconsole.BrokerURL(srcBroker)
        if self.dest.match(self.src.host, self.src.port):
            raise Exception("Linking broker to itself is not permitted")

        brokers = self.qmf.getObjects(_class="broker")
        broker = brokers[0]

        link = self.getLink()
        if link == None:
            if _verbose:
                print "Inter-broker link not found, creating..."

            if self.src.authName == "anonymous":
                mech = "ANONYMOUS"
            else:
                mech = "PLAIN"
            res = broker.connect(self.src.host, self.src.port, _durable,
                                 mech, self.src.authName, self.src.authPass,
                                 _transport)
            if _verbose:
                print "Connect method returned:", res.status, res.text
            link = self.getLink()

        if link == None:
            raise Exception("Protocol Error - Missing link ID")

        bridges = self.qmf.getObjects(_class="bridge")
        for bridge in bridges:
            if bridge.linkRef == link.getObjectId() and \
                    bridge.dest == exchange and bridge.key == routingKey:
                if not _quiet:
                    raise Exception("Duplicate Route - ignoring: %s(%s)" % (exchange, routingKey))
                sys.exit (0)

        if _verbose:
            print "Creating inter-broker binding..."
        res = link.bridge(_durable, exchange, exchange, routingKey, tag, excludes, False, False, dynamic)
        if res.status != 0:
            raise Exception(res.text)
        if _verbose:
            print "Bridge method returned:", res.status, res.text

    def DelRoute (self, srcBroker, exchange, routingKey, dynamic=False):
        self.src = qmfconsole.BrokerURL(srcBroker)
        link = self.getLink()
        if link == None:
            if not _quiet:
                raise Exception("No link found from %s to %s" % (self.src.name(), self.dest.name()))
            sys.exit (0)

        bridges = self.qmf.getObjects(_class="bridge")
        for bridge in bridges:
            if bridge.linkRef == link.getObjectId() and bridge.dest == exchange and bridge.key == routingKey \
                    and bridge.dynamic == dynamic:
                if _verbose:
                    print "Closing bridge..."
                res = bridge.close()
                if res.status != 0:
                    raise Exception("Error closing bridge: %d - %s" % (res.status, res.text))
                if len (bridges) == 1 and _dellink:
                    link = self.getLink ()
                    if link == None:
                        sys.exit (0)
                    if _verbose:
                        print "Last bridge on link, closing link..."
                    res = link.close()
                    if res.status != 0:
                        raise Exception("Error closing link: %d - %s" % (res.status, res.text))
                sys.exit (0)
        if not _quiet:
            raise Exception("Route not found")

    def ListRoutes (self):
        links   = self.qmf.getObjects(_class="link")
        bridges = self.qmf.getObjects(_class="bridge")

        for bridge in bridges:
            myLink = None
            for link in links:
                if bridge.linkRef == link.getObjectId():
                    myLink = link
                    break
            if myLink != None:
                if bridge.dynamic:
                    keyText = "<dynamic>"
                else:
                    keyText = bridge.key
                print "%s %s:%d %s %s" % (self.dest.name(), myLink.host, myLink.port, bridge.dest, keyText)

    def ClearAllRoutes (self):
        links   = self.qmf.getObjects(_class="link")
        bridges = self.qmf.getObjects(_class="bridge")

        for bridge in bridges:
            if _verbose:
                myLink = None
                for link in links:
                    if bridge.linkRef == link.getObjectId():
                        myLink = link
                        break
                if myLink != None:
                    print "Deleting Bridge: %s:%d %s %s... " % (myLink.host, myLink.port, bridge.dest, bridge.key),
            res = bridge.close()
            if res.status != 0:
                print "Error: %d - %s" % (res.status, res.text)
            elif _verbose:
                print "Ok"

        if _dellink:
            links = self.qmf.getObjects(_class="link")
            for link in links:
                if _verbose:
                    print "Deleting Link: %s:%d... " % (link.host, link.port),
                res = link.close()
                if res.status != 0:
                    print "Error: %d - %s" % (res.status, res.text)
                elif _verbose:
                    print "Ok"

def YN(val):
    if val == 1:
        return 'Y'
    return 'N'

##
## Main Program
##

try:
    longOpts = ("verbose", "quiet", "durable", "del-empty-link", "transport=")
    (optlist, cargs) = getopt.gnu_getopt (sys.argv[1:], "vqdet:", longOpts)
except:
    Usage ()

for opt in optlist:
    if opt[0] == "-v" or opt[0] == "--verbose":
        _verbose = True
    if opt[0] == "-q" or opt[0] == "--quiet":
        _quiet = True
    if opt[0] == "-d" or opt[0] == "--durable":
        _durable = True
    if opt[0] == "-e" or opt[0] == "--del-empty-link":
        _dellink = True
    if opt[0] == "-t" or opt[0] == "--transport":
        _transport = opt[1]

nargs = len (cargs)
if nargs < 2:
    Usage ()
if nargs == 2:
    destBroker = "localhost"
else:
    destBroker = cargs[2]

group = cargs[0]
cmd   = cargs[1]

try:
    rm = RouteManager (destBroker)
    if group == "link":
        if cmd == "add":
            if nargs != 4:
                Usage()
            rm.AddLink (cargs[3])
        elif cmd == "del":
            if nargs != 4:
                Usage()
            rm.DelLink (cargs[3])
        elif cmd == "list":
            rm.ListLinks ()

    elif group == "dynamic":
        if cmd == "add":
            if nargs < 5 or nargs > 7:
                Usage ()

            tag = ""
            excludes = ""
            if nargs > 5: tag = cargs[5]     
            if nargs > 6: excludes = cargs[6]     
            rm.AddRoute (cargs[3], cargs[4], "", tag, excludes, dynamic=True)
        elif cmd == "del":
            if nargs != 5:
                Usage ()
            else:
                rm.DelRoute (cargs[3], cargs[4], "", dynamic=True)

    elif group == "route":
        if cmd == "add":
            if nargs < 6 or nargs > 8:
                Usage ()

            tag = ""
            excludes = ""
            if nargs > 6: tag = cargs[6]     
            if nargs > 7: excludes = cargs[7]     
            rm.AddRoute (cargs[3], cargs[4], cargs[5], tag, excludes, dynamic=False)
        elif cmd == "del":
            if nargs != 6:
                Usage ()
            else:
                rm.DelRoute (cargs[3], cargs[4], cargs[5], dynamic=False)
        else:
            if   cmd == "list":
                rm.ListRoutes ()
            elif cmd == "flush":
                rm.ClearAllRoutes ()
            else:
                Usage ()
except Exception,e:
    print "Failed:", e.args[0]
    sys.exit(1)

rm.Disconnect ()
