#!/usr/bin/env python

from optparse import OptionParser
from twisted.internet import reactor, defer
import uuid
import random
import os.path
import socket
import datetime
import time
import sys
import urllib


import opennsa.topology
import opennsa.setup
import opennsa.nsa
import opennsa.cli.commands
import opennsa.cli.options

defaults_file = os.path.join( os.path.expanduser('~'), '.opennsa-cli' )
# Set some defaults
WSDL_DIR = "/usr/local/share/nsi/wsdl"
TOPOLOGY_FILE = "AutoGOLE-TOPO.owl"
PORT = 9081
# If the file exists, and the values exist, they overwrite the defaults
if os.path.exists(defaults_file):
    defaults = opennsa.cli.options.readDefaults( open(defaults_file) )
    TOPOLOGY_FILE = defaults.get('topology') or TOPOLOGY_FILE
    WSDL_DIR = defaults.get('wsdl') or WSDL_DIR
    PORT = int(defaults.get('port')) or PORT

def getSTP(topo,stp_id):
    tokens = stp_id.replace(opennsa.nsa.STP_PREFIX, '').split(':', 2)
    return topo.getEndpoint(tokens[0],tokens[1])

def doInfo(info_id):
    topo = opennsa.topology.gole.parseTopology([open(TOPOLOGY_FILE)])[0]
    if ":stp:" in info_id:
        stp = getSTP(topo,info_id)
        if stp:
            print stp.urn()
            print "  network: %s" % topo.getNetwork(stp.network).name
            dest = stp.dest_stp
            if dest:
                print "  connectedTo: %s" % stp.dest_stp.urn()
    else:
        net = topo.getNetwork(info_id)
        print net.location
        for ep in net.getEndpoints():
            print "nsa.STP('%s','%s')" % (net.name,ep)
        print 80*"-"


def getNSA(name):
    topo = opennsa.topology.gole.parseTopology([open(TOPOLOGY_FILE)])[0]
    for net in topo.networks:
        if net.name == name:
            return net.nsa
        elif net.nsa.identity == name:
            return net.nsa
        else:
            pass
    raise Exception("Could not find %s in the topology." % name)

@defer.inlineCallbacks
def doTerminate(terminateArgs):
    network = terminateArgs[0]
    conId = terminateArgs[1]
    client,client_nsa = createClient()
    _ = yield client.terminate(client_nsa, nsa, None, conId)
    print "Reservation terminated"

def parseStpList(stpList):
    topo = opennsa.topology.gole.parseTopology([open(TOPOLOGY_FILE)])[0]
    store = {}
    for stp in stpList:
        store[stp._order] = stp.stpId
    keys = store.keys()
    keys.sort()
    result = []
    for k in keys:
        # stp = getSTP(topo,store[k])
        stp = store[k]
        result.append(stp)
    return result

@defer.inlineCallbacks
def doQuery(queryArgs):
    nsa = queryArgs.query
    if not "http" in nsa:
        nsa = getNSA(nsa)
    client,client_nsa = createClient()
    if queryArgs.cid:
        print "Querying %s for %s" % (nsa,queryArgs.cid)
        qr = yield client.query(client_nsa, nsa, None, "Summary", connection_ids = [ queryArgs.cid ] )
    elif queryArgs.gid:
        print "Querying %s for %s" % (nsa,queryArgs.gid)
        qr = yield client.query(client_nsa, nsa, None, "Summary", global_reservation_ids = [ queryArgs.gid ] )
    else:
        print "Querying %s for %s" % (nsa,None)
        qr = yield client.query(client_nsa, nsa, None, "Summary", connection_ids = [] )
    if hasattr(qr,"reservationSummary"):
        for res in qr.reservationSummary:
            print "global-reservation-id: " + str(res.globalReservationId)
            if hasattr(res,"description"):
                print "description: " + str(res.description)
            print "schedule: %s -- %s" % (res.serviceParameters.schedule.startTime, res.serviceParameters.schedule.endTime)
            print "bandwidth: " + str(res.serviceParameters.bandwidth.desired)
            print "status: " + str(res.connectionState)
            if hasattr(res,"path"):
                if hasattr(res.path,"stpList"):
                    print "stplist: %s" % (res.path.stpList.stp)
                    print "parsedList: %s" % (parseStpList(res.path.stpList.stp))
                else:
                    print "stpList: " + str([res.path.sourceSTP.stpId,res.path.destSTP.stpId])
            print "Typeinfo: %s" % (type(res.serviceParameters.schedule.startTime))
    else:
        print qr


@defer.inlineCallbacks
def doReserve(options):
    topo = opennsa.topology.gole.parseTopology([open(TOPOLOGY_FILE)])[0]
    reservation = options.reserve
    srcNet = reservation[0]
    dstNet = reservation[2]
    # if not srcNet.endswith(".ets"):
    #     srcNet += ".ets"
    # if not dstNet.endswith(".ets"):
    #     dstNet += ".ets"
    srcNet = topo.getNetwork(srcNet)
    srcSTP = srcNet.getEndpoint(reservation[1])
    dstNet = topo.getNetwork(dstNet)
    dstSTP = dstNet.getEndpoint(reservation[3])
    if options.nsa:
        provider_nsa = getNSA(options.nsa)
    else:
        provider_nsa = srcNet.nsa
    print "Reserving (%s,%s) to (%s,%s) \n at %s (%s)" % (srcNet,srcSTP,dstNet,dstSTP, provider_nsa,provider_nsa.url())
    # Setting some defaults for now, to fill in later
    start_time=None
    end_time=None
    description='CLI Test Connection'
    # Constructing the ServiceParamaters
    if not start_time:
        start_time = datetime.datetime.utcfromtimestamp(time.time() + 120 ) # two minutes from now
    if options.length:
        end_time   = start_time + datetime.timedelta(minutes=options.length) # two minutes later
    else:
        end_time   = start_time + datetime.timedelta(minutes=3) # two minutes later
    if options.gid:
        global_reservation_id = options.gid
    else:
        global_reservation_id = 'opennsa-cli:gid-%s' % random.randrange(1000,10000)
    connection_id = "urn:uuid:%s" % uuid.uuid1()
    bwp = opennsa.nsa.BandwidthParameters(200)
    service_params  = opennsa.nsa.ServiceParameters(start_time, end_time, srcSTP, dstSTP, bandwidth=bwp)
    client,client_nsa = createClient()
    # Send the reservation and wait for response
    r = yield client.reserve(client_nsa, provider_nsa, None, global_reservation_id, description, connection_id, service_params)
    print "Reservation created.\nReservation ID: %s\nConnection ID: %s" % (global_reservation_id,connection_id)
    if r and options.register:
        urllib.urlopen("http://rembrandt0.uva.netherlight.nl:8080/register",
            urllib.urlencode({"urn": global_reservation_id,"nsa": provider_nsa.urn()}))


@defer.inlineCallbacks
def doTerminate(options):
    nsa = options.terminate
    if not "http" in nsa:
        nsa = getNSA(nsa)
    client,client_nsa = createClient()
    print "Terminating %s at %s" % (options.cid, nsa)
    qr = yield client.terminate(client_nsa, nsa, None , connection_id =  options.cid )

@defer.inlineCallbacks
def doProvision(options):
    nsa = options.provision
    if not "http" in nsa:
        nsa = getNSA(nsa)
    client,client_nsa = createClient()
    print "Provisioning %s at %s" % (options.cid, nsa)
    qr = yield client.provision(client_nsa, nsa, None , connection_id =  options.cid )

def createClient():
    # Constructing the client NSA
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(("gmail.com",80))
    host = s.getsockname()[0]
    s.close()
    client, factory = opennsa.setup.createClient(host, PORT, WSDL_DIR)
    client_nsa = opennsa.nsa.NetworkServiceAgent('OpenNSA-commandline', 'http://%s:%s/NSI/services/ConnectionService' % (host,PORT))
    reactor.listenTCP(PORT, factory)
    return client,client_nsa

def runDefer(d):
    def handleError(x):
        x.printTraceback()
    d.addErrback(handleError)
    d.addCallback(lambda _: reactor.stop())
    reactor.run()

def doMain():
    parser = OptionParser()
    parser.add_option("-f", "--file", dest="filename",
                      help="write report to FILE", metavar="FILE")
    parser.add_option("-i","--info",
                        action="store",dest="info",
                        help="provide info about an id")
    parser.add_option("-r","--reserve",
                        action="store",dest="reserve",nargs=4,
                        help="make a reservation between two stps both defined by network and id")
    parser.add_option("-q","--query",
                        action="store",dest="query",
                        help="query a network about a reservation, requires network and query")
    parser.add_option("-g","--global-reservation-id",
                        action="store",dest="gid",default=None,
                        help="provide a global reservation ID for querying or reservation")
    parser.add_option("-l","--length",
                        action="store",dest="length",default=None,type="int",
                        help="Length of reservation in minutes")
    parser.add_option("-c","--connection-id",
                        action="store",dest="cid",default=None,
                        help="provide a connection ID for querying or termination")
    parser.add_option("-n","--provider-nsa",
                        action="store",dest="nsa",default=None,
                        help="provide a provider NSA to issue commands to")
    parser.add_option("-t","--terminate",
                        action="store",dest="terminate",
                        help="terminate a reservation at a network, requires network and conn-id")
    parser.add_option("-p","--provision",
                        action="store",dest="provision",
                        help="provision a reservation at a network, requires network and conn-id")
    parser.add_option("-w","--register",
                        action="store_true",dest="register",
                        help="on successful reservation register the reservation on the web monitor")
    (options, args) = parser.parse_args()

    cmd = None
    if options.gid and options.cid:
        sys.exit("Cannot use both reservation ID and connection ID")

    if options.info:
        doInfo(options.info)
    elif options.reserve:
        cmd = doReserve
        opts = options
    elif options.query:
        cmd = doQuery
        opts = options
    elif options.provision:
        cmd = doProvision
        opts = options
    elif options.terminate:
        if not options.cid:
            sys.exit("Need a connection ID to terminate connections.")
        cmd = doTerminate
        opts = options
    if cmd:
        d = defer.maybeDeferred(cmd,opts)
        runDefer(d)

if __name__ == '__main__':
    doMain()
