while response.json()["state"] == "IN_PROGRESS" or response.json()["state"] == "down":
        if timeout == 0:
            return 1
        time.sleep(1)
        response = requests.get(sessionUrl)
        state = response.json()["state"]
        print "\t\tState:", state
        timeout = timeout - 1

# By Hubert Gee

#    - Load a saved config file.
#    - Reassign Ports:  Exclude calling assignPorts if it's unecessary.
#    - Verify port states.
#    - Start all protocols.
#    - Verify all protocol sessions.
#    - Apply Traffic
#    - Regenerate Traffic
#    - Start Traffic
#    - Get Stats

import sys
import zipfile
import requests
import json
import os
import time

class TestFailedError(Exception): pass
class Py: pass

py = Py()
portList = [['10.219.117.101', '1', '1'], ['10.219.117.101', '1', '2']]
py.ixTclServer  =  "10.219.117.103"
py.ixRestPort   =  '11009'

py.ixTclServer  =  "192.168.70.127"
py.ixRestPort   =  '11009'

def waitForComplete(response, sessionUrl, timeout=90):
    # response: Provide the POST action response.  Generally, after an /operations action.
    # Returns 0 if the state is good
    # Returns 1 if the  state remains down or IN_PROGRESS after timeout.

    if response.json().has_key("errors"):
        print response.json()["errors"][0]
        return 1
    print "\n", sessionUrl
    print "\t\tState:",response.json()["state"]

    # Falling down here means success
    return 0

def assignPorts(sessionUrl, portList):
    # Use this API to assign or reassign ports.
    # GET RESPONSE:  SUCCESS

    response = requests.get(sessionUrl+'/vport')
    if response.status_code != 200:
        return 1

    vportList = ["%s/vport/%s" % (sessionUrl, str(i["id"])) for i in response.json()]
    if len(vportList) != len(portList):
        print '\nassignPorts error: The amount of configured virtual ports is not equal to the portList amount'
        return 1

    data = {"arg1": [], "arg2": [], "arg3": vportList, "arg4": "true"}
    [data["arg1"].append({"arg1":str(chassis), "arg2":str(card), "arg3":str(port)}) for chassis,card,port in portList]
    response = requests.post(sessionUrl+'/operations/assignports',
                             data=json.dumps(data),
                             headers={'content-type': 'application/json'})
    if waitForComplete(response, sessionUrl+'/operations/assignports/'+response.json()['id']) == 1:
        return 1
    else:
        return 0

def verifyPortState(sessionUrl):
    # Returns 0 if all ports are up.

    response = requests.get(sessionUrl+'/vport')
    vportList = ["%s/vport/%s" % (sessionUrl, str(i["id"])) for i in response.json()]
    for eachVport in vportList:
        for counter in range(0,61):
            response = requests.get(eachVport)
            print '\n', eachVport
            print '\t\tVerify Port State:', response.json()['state']
            if counter < 60 and response.json()['state'] == 'down':
                time.sleep(1)
                continue
            if counter < 60 and response.json()['state'] == 'up':
                return 0
            if counter == 60 and response.json()['state'] == 'down':
                # Failed
                return 1

def startAllProtocols(sessionUrl):

    print '\nstartAllProtocols'
    response = requests.post(sessionUrl+'/operations/startallprotocols', data={}, headers={})
    if response.status_code == 202:
        return 0
    else:
        return 1

def stopAllProtocols(session):

    print '\nstopAllProtocols'
    response = requests.post(sessionUrl+'/operations/stopallprotocols', data={}, headers={})
    if response.status_code == 202:
        return 0
    else:
        return 1

def verifyProtocolSessions(protocolObjList, timeout=90):
    # Verify the user specified protocol list to verify for session UP.
    # timeout: Total wait time for all the protocols in the provided list to come up.

    timerStop = timeout

    for eachProtocol in protocolObjList:
        print '\nVerifyProtocolSessions: %s\n' % eachProtocol
        for timer in range(1,timerStop+1):
            response = requests.get(eachProtocol)
            if response.status_code != 200:
                print 'Failed to get response'
                continue

            protocolSessionStatus = response.json()['sessionStatus']
            print '\tStatus: Down : Wait %s/%s seconds' % (timer, timerStop)
            if timer < timerStop+1:
                if 'down' in protocolSessionStatus or 'notStarted' in protocolSessionStatus:
                    time.sleep(1)
            if timer < timerStop+1:
                if 'down' not in protocolSessionStatus and 'notStarted' not in protocolSessionStatus:
                    print '\n\tStatus: All UP'
                    break
            if timer == timerStop:
                if 'down' in protocolSessionStatus or 'notStarted' in protocolSessionStatus:
                    print '\nverifyProtocolSessions Failed'
                    return 1
    return 0

def verifyAllProtocolSessionsNgpf(sessionUrl, timeout=120):
    # This API will loop through each created Topology Group and verify
    # all the created protocols for session up for up to 120 seconds total.
    # Will verify IPv4 and IPv6.
    # Returns 0 if all sessions are UP.
    # Returns 1 if any session remains DOWN after 120 seconds.

    protocolList = ['ancp',
                    'bfdv4Interface',
                    'bgpIpv4Peer',
                    'bgpIpv6Peer',
                    'dhcpv4relayAgent',
                    'dhcpv6relayAgent',
                    'dhcpv4server',
                    'dhcpv6server',
                    'geneve',
                    'greoipv4',
                    'greoipv6',
                    'igmpHost',
                    'igmpQuerier',
                    'lac',
                    'ldpBasicRouter',
                    'ldpBasicRouterV6',
                    'ldpConnectedInterface',
                    'ldpv6ConnectedInterface',
                    'ldpTargetedRouter',
                    'ldpTargetedRouterV6',
                    'lns',
                    'mldHost',
                    'mldQuerier',
                    'ptp',
                    'ipv6sr',
                    'openFlowController',
                    'openFlowSwitch',
                    'ospfv2',
                    'ospfv3',
                    'ovsdbcontroller',
                    'ovsdbserver',
                    'pcc',
                    'pce',
                    'pcepBackupPCEs',
                    'pimV4Interface',
                    'pimV6Interface',
                    'ptp',
                    'rsvpteIf',
                    'rsvpteLsps',
                    'tag',
                    'vxlan'
                ]

    sessionDownList = ['down', 'notStarted']
    startCounter = 1
    import time

    response = requests.get(sessionUrl+'/topology')
    topologyList = ['%s/%s/%s' % (sessionUrl, 'topology', str(i["id"])) for i in response.json()]
    for topology in topologyList:
        response = requests.get(topology+'/deviceGroup')
        deviceGroupList = ['%s/%s/%s' % (topology, 'deviceGroup', str(i["id"])) for i in response.json()]
        for deviceGroup in deviceGroupList:
            response = requests.get(deviceGroup+'/ethernet')
            ethernetList = ['%s/%s/%s' % (deviceGroup, 'ethernet', str(i["id"])) for i in response.json()]
            for ethernet in ethernetList:
                response = requests.get(ethernet+'/ipv4')
                ipv4List = ['%s/%s/%s' % (ethernet, 'ipv4', str(i["id"])) for i in response.json()]
                response = requests.get(ethernet+'/ipv6')
                ipv6List = ['%s/%s/%s' % (ethernet, 'ipv6', str(i["id"])) for i in response.json()]
                for ipv4 in ipv4List+ipv6List:
                    for protocol in protocolList:
                        response = requests.get(ipv4+'/'+protocol)
                        if response.json() == [] or 'errors' in response.json():
                            continue

                        currentProtocolList = ['%s/%s/%s' % (ipv4, protocol, str(i["id"])) for i in response.json()]
                        for currentProtocol in currentProtocolList:
                            for timer in range(startCounter, timeout+1):
                                response = requests.get(currentProtocol)
                                currentStatus = response.json()['sessionStatus']
                                print '\n%s' % currentProtocol
                                print '\tTotal sessions: %d' % len(currentStatus)
                                totalDownSessions = 0
                                for eachStatus in currentStatus:
                                    if eachStatus != 'up':
                                        totalDownSessions += 1
                                print '\tTotal sessions Down: %d' % totalDownSessions

                                if timer < timeout and [element for element in sessionDownList if element in currentStatus] == []:
                                    print '\tProtocol sessions are all up'
                                    startCounter = timer
                                    break
                                if timer < timeout and [element for element in sessionDownList if element in currentStatus] != []:
                                    print '\tWait %d/%d seconds' % (timer, timeout)
                                    time.sleep(1)
                                    continue
                                if timer == timeout and [element for element in sessionDownList if element in currentStatus] != []:
                                    print '\tProtocol session failed to come up:'
                                    return 1

    # All active protocols are up. Return 0
    return 0

def applyTraffic(sessionUrl):

    print '\napplyTraffic:', sessionUrl
    response = requests.post(sessionUrl+'/traffic/operations/apply',
                             data=json.dumps({'arg1': sessionUrl+'/traffic'}),
                             headers={'content-type': 'application/json'})
    if response.status_code == 202:
        if waitForComplete(response, sessionUrl+'/traffic/operations/apply/'+response.json()['id']) == 1:
            print 'applyTraffic: waitForComplete failed'
            return 1
        else:
            return 0
    else:
        return 1

def regenerateAllTrafficItems(sessionUrl):
    # Performs regenerate on all enabled Traffic Items.
    # This API requires the waitForComplete API also.

    serverUrl = sessionUrl.split('/api')[0]
    response = requests.get(sessionUrl + "/traffic/trafficItem")
    trafficItemList = ["%s%s" % (serverUrl, str(i["links"][0]["href"])) for i in response.json()]
    for trafficItem in trafficItemList:
        response = requests.get(trafficItem)
        if response.status_code != 200:
            return 1

        if response.json()['enabled'] == True:
            print '\nRegenerating:', trafficItem
            response = requests.post(trafficItem+"/operations/generate",
                                     data=json.dumps({"arg1": trafficItem}),
                                     headers={"content-type": "application/json"})
            if response.status_code != 202:
                return 1

            if waitForComplete(response, trafficItem+"/operations/generate/"+response.json()['id']) == 1:
                return 1
            else:
                return 0

def startTraffic(sessionUrl):

    print '\nstartTraffic:', sessionUrl+'/traffic/operations/start'
    response = requests.post(sessionUrl+'/traffic/operations/start',
                             data=json.dumps({'arg1': sessionUrl+'/traffic'}),
                             headers={'content-type': 'application/json'})
    print response.json()
    if response.status_code == 202:
        return 0
    else:
        return 1

def stopTraffic(sessionUrl):

    print '\nstopTraffic:', sessionUrl+'/traffic/operations/stop'
    response = requests.post(sessionUrl+'/traffic/operations/stop',
                             data=json.dumps({'arg1': sessionUrl+'/traffic'}),
                             headers={'content-type': 'application/json'})
    print response.json()
    if response.status_code == 202:
        return 0
    else:
        return 1

def getStats(sessionUrl, csvFile=None, csvEnableFileTimestamp=False, viewName='Flow Statistics'):
    #           None will not create a CSV file.
    #           Provide a <filename>.csv to record all stats to a CSV file.
    # csvEnableFileTimestamp = True or False. If True, timestamp will be appended to the filename.
    # viewName options (Not case sensitive):
    #    NOTE: Not all statistics are listed here.
    #          You could get the statistic viewName directly from the IxNetwork GUI in the statistics.
    #    'Tx-Rx Frame Rate Statistics'
    #    'Port CPU Statistics'
    #    'Global Protocol Statistics'
    #    'OSPFv2-RTR Drill Down'
    #    'OSPFv2-RTR Per Port'
    #    'IPv4 Drill Down'
    #    'L2-L3 Test Summary Statistics'
    #    'Traffic Item Statistics'
    #    'IGMP Host Drill Down'
    #    'IGMP Host Per Port'
    #    'IPv6 Drill Down'
    #    'MLD Host Drill Down'
    #    'MLD Host Per Port'
    #    'PIMv6 IF Drill Down'
    #    'PIMv6 IF Per Port'

    # Note: Not all of the viewNames are listed here. You have to get the exact names from
    #       the IxNetwork GUI in statistics based on your protocol(s).
    # Return you a dictionary of all the stats: statDict[rowNumber][columnName] == statValue
    #   Get stats on row 2 for 'Tx Frames' = statDict[2]['Tx Frames']

    urlHeadersJson = {'content-type': 'application/json'}
    viewList = requests.get('%s/%s/%s' % (sessionUrl, 'statistics', 'view'), headers=urlHeadersJson)
    views = ['%s/%s/%s/%s' % (sessionUrl, 'statistics', 'view', str(i['id'])) for i in viewList.json()]

    for view in views:
        # GetAttribute
        response = requests.get('%s' % view, headers=urlHeadersJson)
        if response.status_code != 200:
            print '\ngetStats: Failed:', response.text
            return 1

        import re
        captionMatch = re.match(viewName, response.json()['caption'], re.I)
        if captionMatch:
            viewObj = view
            break

    try:
        response = requests.patch(viewObj, data=json.dumps({'enabled': 'true'}), headers=urlHeadersJson)
    except:
        print '\ngetStats error: No stats available'
        return 0

    response = requests.get(viewObj+'/page')
    if response.status_code != 200:
        print '\ngetStats: Failed to get total pages'
        return 1

    for counter in range(0,31):
        totalPages = response.json()['totalPages']
        if totalPages == 'null':
            print '\nGetting total pages is not ready yet. Waiting %d/30 seconds' % counter
            time.sleep(1)
        if totalPages != 'null':
            break
        if totalPages == 'null' and counter == 30:
            print '\ngetStats failed: Getting total pages'
            return 1

    response = requests.get(viewObj+'/page', '-columnCaptions')
    if response.status_code != 200:
        print '\ngetStats: Failed to get statistic column names'
        return 1

    if csvFile != None:
        import csv
        csvFileName = csvFile.replace(' ', '_')
        if csvEnableFileTimestamp:
            import datetime
            timestamp = datetime.datetime.now().strftime('%H%M%S')
            if '.' in csvFileName:
                csvFileNameTemp = csvFileName.split('.')[0]
                csvFileNameExtension = csvFileName.split('.')[1]
                csvFileName = csvFileNameTemp+'_'+timestamp+'.'+csvFileNameExtension
            else:
                csvFileName = csvFileName+'_'+timestamp

        csvFile = open(csvFileName, 'w')
        csvWriteObj = csv.writer(csvFile)

    # Get the stat column names
    columnList = response.json()['columnCaptions']
    if csvFile != None:
        csvWriteObj.writerow(columnList)

    # Get the stat values
    response = requests.get(viewObj+'/page')
    statValueList = response.json()['pageValues']

    statDict = {}
    flowNumber = 1
    for statValue in statValueList:
        if csvFile != None:
            csvWriteObj.writerow(statValue[0])

        print '\nRow: %d' % flowNumber
        statDict[flowNumber] = {}
        index = 0
        for statValue in statValue[0]:
            statName = columnList[index]
            statDict[flowNumber].update({statName: statValue})
            print '\t%s: %s' % (statName, statValue)
            index += 1
        flowNumber += 1

    if csvFile != None:
        csvFile.close()
    return statDict

    # Flow Statistics dictionary output example
    '''
    Flow: 50
        Tx Port: Ethernet - 002
        Rx Port: Ethernet - 001
        Traffic Item: OSPF T1 to T2
        Source/Dest Value Pair: 2.0.21.1-1.0.21.1
        Flow Group: OSPF T1 to T2-FlowGroup-1 - Flow Group 0002
        Tx Frames: 35873
        Rx Frames: 35873
        Frames Delta: 0
        Loss %: 0
        Tx Frame Rate: 3643.5
        Rx Frame Rate: 3643.5
        Tx L1 Rate (bps): 4313904
        Rx L1 Rate (bps): 4313904
        Rx Bytes: 4591744
        Tx Rate (Bps): 466368
        Rx Rate (Bps): 466368
        Tx Rate (bps): 3730944
        Rx Rate (bps): 3730944
        Tx Rate (Kbps): 3730.944
        Rx Rate (Kbps): 3730.944
        Tx Rate (Mbps): 3.731
        Rx Rate (Mbps): 3.731
        Store-Forward Avg Latency (ns): 0
        Store-Forward Min Latency (ns): 0
        Store-Forward Max Latency (ns): 0
        First TimeStamp: 00:00:00.722
        Last TimeStamp: 00:00:10.568
    '''
    return

def printDict(obj, nested_level=0, output=sys.stdout):
    """
    Print each dict key with indentions for readability.
    """

    spacing = '   '
    if type(obj) == dict:
        print >> output, '%s' % ((nested_level) * spacing)
        for k, v in obj.items():
            if hasattr(v, '__iter__'):
                print >> output, '%s%s:' % ((nested_level + 1) * spacing, k)
                printDict(v, nested_level + 1, output)
            else:
                print >> output, '%s%s: %s' % ((nested_level + 1) * spacing, k, v)

        print >> output, '%s' % (nested_level * spacing)

    elif type(obj) == list:
        print >> output, '%s[' % ((nested_level) * spacing)
        for v in obj:
            if hasattr(v, '__iter__'):
                printDict(v, nested_level + 1, output)
            else:
                print >> output, '%s%s' % ((nested_level + 1) * spacing, v)
        print >> output, '%s]' % ((nested_level) * spacing)

    else:
        print >> output, '%s%s' % (nested_level * spacing, obj)

def enableDisableBgpFlapNgpf(sessionUrl, action='true', upTimeInSec=0, downTimeInSec=0):
    # This API will enable or disable flapping on all the BGP interfaces.
    # sessionUrl = The BGP object handle.
    # action     = string format.  Not boolean.
    #              'true'  = enable BGP flap.
    #              'false' = disable BGP flap.
    # upTimeInSecs   = Up Time In Seconds.  Provide an integer.
    # downTimeInSecs = Down Time In Seconds. Provide an integer.

    urlHeadersJson = {'content-type': 'application/json'}
    httpHeader = sessionUrl.split('/api')[0]
    print '\nenableBgpRouteFlapNgpf: Please wait a moment while I query for datas...'
    response = requests.get(sessionUrl)
    if response.status_code != 200:
        return 1

    # NOTE:  This will take a moment if the config is large
    flapMultivalue = response.json()['flap']

    upTimeInSecsMultivalue = response.json()['uptimeInSec']
    downTimeInSecsMultivalue = response.json()['downtimeInSec']

    print '\nenableDisableBgpFlapNgpf:', action
    response = requests.patch(httpHeader+flapMultivalue+'/singleValue',
                              data=json.dumps({'value': action}),
                              headers=urlHeadersJson)
    if response.status_code != 200:
        return 1

    print 'enableDisableBgpFlapNgpf upTimeInSec:', upTimeInSec
    response = requests.patch(httpHeader+upTimeInSecsMultivalue+'/singleValue',
                              data=json.dumps({'value': str(upTimeInSec)}),
                              headers=urlHeadersJson)
    if response.status_code != 200:
        return 1

    print 'enableDisableBgpFlapNgpf downTimeInSec:', downTimeInSec
    response = requests.patch(httpHeader+downTimeInSecsMultivalue+'/singleValue',
                              data=json.dumps({'value': str(downTimeInSec)}),
                              headers=urlHeadersJson)
    if response.status_code != 200:
        return 1

def startStopIgmpHostNgpf(protocolSessionUrl, action='start'):
    # action: start or stop

    sessionUrl = protocolSessionUrl.split('/topology')[0]
    url = sessionUrl+'/topology/deviceGroup/ethernet/ipv4/igmpHost/operations/%s' % action
    dataUrl = protocolSessionUrl.split('api')[1]
    data = {'arg1': [dataUrl]}
    urlHeadersJson = {'content-type': 'application/json'}
    print '\nstartStopIgmpHostNgpf: %s: %s' % (action, protocolSessionUrl)
    response = requests.post(url, data=json.dumps(data), headers=urlHeadersJson)
    if response.status_code != 202:
        return 1
    if waitForComplete(response, url+response.json()['id']) == 1:
        return 1
    else:
        return 0

def startStopPimV4InterfaceNgpf(protocolSessionUrl, action='start'):
    # action: start or stop

    sessionUrl = protocolSessionUrl.split('/topology')[0]
    url = sessionUrl+'/topology/deviceGroup/ethernet/ipv4/pimV4Interface/operations/%s' % action
    dataUrl = protocolSessionUrl.split('api')[1]
    data = {'arg1': [dataUrl]}
    urlHeadersJson = {'content-type': 'application/json'}
    print '\nstartStopPimV4InterfaceNgpf: %s: %s' % (action, protocolSessionUrl)
    response = requests.post(url, data=json.dumps(data), headers=urlHeadersJson)
    if response.status_code != 202:
        return 1
    if waitForComplete(response, url+response.json()['id']) == 1:
        return 1
    else:
        return 0

def startStopMldHostNgpf(protocolSessionUrl, action='start'):
    # For IPv6 only
    # action: start or stop
    sessionUrl = protocolSessionUrl.split('/topology')[0]
    url = sessionUrl+'/topology/deviceGroup/ethernet/ipv6/mldHost/operations/%s' % action
    dataUrl = protocolSessionUrl.split('api')[1]
    data = {'arg1': [dataUrl]}
    urlHeadersJson = {'content-type': 'application/json'}
    print '\nstartStopMldHostNgpf: %s: %s' % (action, protocolSessionUrl)
    response = requests.post(url, data=json.dumps(data), headers=urlHeadersJson)
    if response.status_code != 202:
        return 1
    if waitForComplete(response, url+response.json()['id']) == 1:
        return 1
    else:
        return 0

def enableDisableIgmpGroupNgpf(protocolSessionUrl, groupRangeList, action='disable'):
    # groupRangeList: A list of multicast group range addresses to disable.
    # action: disable or enable
    #   To enable or disable specific multicast group range IP addresses by using
    # 1> Get a list of all the Multicast group range IP addresses.
    # 2> Get the multivalue list of ACTIVE STATE group ranges.
    # 3> Loop through the user list "groupRangeList" and look
    #    for the index position of the specified group range IP address.
    # 4> Using overlay to enable|disable the index value.
    # Note: If an overlay is not created, then create one by:
    #       - And add an Overlay.

    if action == 'disable':
        enableDisable = 'false'
    else:
        enableDisable = 'true'

    httpHeader = protocolSessionUrl.split('/api')[0]
    urlHeadersJson = {'content-type': 'application/json'}

    url = protocolSessionUrl+'/igmpMcastIPv4GroupList'
    response = requests.get(url)
    if response.status_code != 200:
        return 1

    # Get startMcastAddr multivalue to get a list of all the configured Group Range IP addresses.
    groupRangeAddressMultivalue = response.json()['startMcastAddr']
    # Get the active multivalue to do the overlay on top of.
    activeMultivalue = response.json()['active']

    # Getting the list of Group Range IP addresses.
    response = requests.get(httpHeader+groupRangeAddressMultivalue)
    if response.status_code != 200:
        return 1

    # groupRangeValues are multicast group ranges:
    groupRangeValues = response.json()['values']
    print '\nConfigured groupRangeValues:', groupRangeValues

    listOfIndexesToDisable = []
    # Loop through user list of specified group ranges to disable.
    for groupRangeIp in groupRangeList:
        index = groupRangeValues.index(groupRangeIp)
        listOfIndexesToDisable.append(index)

    if listOfIndexesToDisable == []:
        print '\ndisableIgmpGroupNgpf Error: No multicast group range ip address found on your list'
        return 1

    for index in listOfIndexesToDisable:
        currentOverlayUrl = httpHeader+activeMultivalue+'/overlay'
        # NOTE:  Index IS NOT zero based.
        print 'enableDisableIgmpGroupNgpf: %s: %s' % (action, groupRangeValues[index])
        response = requests.post(currentOverlayUrl,
                                 data=json.dumps({'index': index+1, 'value': enableDisable}),
                                 headers=urlHeadersJson)
        if response.status_code != 201:
            return 1

    return 0

def enableDisableMldGroupNgpf(protocolSessionUrl, groupRangeList, action='disable'):
    # For IPv6 only
    # groupRangeList: A list of multicast group range addresses to disable.
    # action: disable or enable
    #   To enable or disable specific multicast group range IP addresses by using
    # 1> Get a list of all the Multicast group range IP addresses.
    # 2> Get the multivalue list of ACTIVE STATE group ranges.
    # 3> Loop through the user list "groupRangeList" and look
    #    for the index position of the specified group range IP address.
    # 4> Using overlay to enable|disable the index value.
    # Note: If an overlay is not created, then create one by:
    #       - And add an Overlay.

    if action == 'disable':
        enableDisable = 'false'
    else:
        enableDisable = 'true'

    httpHeader = protocolSessionUrl.split('/api')[0]
    urlHeadersJson = {'content-type': 'application/json'}

    url = protocolSessionUrl+'/mldMcastIPv6GroupList'
    response = requests.get(url)
    if response.status_code != 200:
        return 1

    # Get startMcastAddr multivalue to get a list of all the configured Group Range IP addresses.
    groupRangeAddressMultivalue = response.json()['startMcastAddr']
    # Get the active multivalue to do the overlay on top of.
    activeMultivalue = response.json()['active']

    # Getting the list of Group Range IP addresses.
    response = requests.get(httpHeader+groupRangeAddressMultivalue)
    if response.status_code != 200:
        return 1

    # groupRangeValues are multicast group ranges:
    groupRangeValues = response.json()['values']
    print '\nConfigured groupRangeValues:', groupRangeValues

    listOfIndexesToDisable = []
    # Loop through user list of specified group ranges to disable.
    for groupRangeIp in groupRangeList:
        index = groupRangeValues.index(groupRangeIp)
        listOfIndexesToDisable.append(index)

    if listOfIndexesToDisable == []:
        print '\ndisableMldGroupNgpf Error: No multicast group range ip address found on your list'
        return 1

    for index in listOfIndexesToDisable:
        currentOverlayUrl = httpHeader+activeMultivalue+'/overlay'
        # NOTE:  Index IS NOT zero based.
        print 'enableDisableMldGroupNgpf: %s: %s' % (action, groupRangeValues[index])
        response = requests.post(currentOverlayUrl,
                                 data=json.dumps({'index': index+1, 'value': enableDisable}),
                                 headers=urlHeadersJson)
        if response.status_code != 201:
            return 1

    return 0

def sendIgmpJoinNgpf_backup(sessionUrl, multicastIpAddress):
    # This API will send IGMP join.
    # If multicastIpAddress is 'all', this will send IGMP join on all multicast addresses.
    # Else, provide a list of multicast IP addresses to send join.
    # multicastIpAddress: 'all' or a list of multicast IP addresses to send joins

    httpHeader = sessionUrl.split('/api')[0]
    jsonHeader = {'content-type': 'application/json'}

    # 1> Based on the list of multicastIpAddress, get all their indexes.
    response = requests.get(sessionUrl+'/igmpMcastIPv4GroupList')
    if response.status_code != 200:
        return 1
    startMcastAddrMultivalue = response.json()['startMcastAddr']

    response = requests.get(httpHeader+startMcastAddrMultivalue)
    if response.status_code != 200:
        return 1
    listOfConfiguredMcastIpAddresses = response.json()['values']
    print '\nsendIgmpJoinNgpf: List of configured Mcast IP addresses:', listOfConfiguredMcastIpAddresses
    if listOfConfiguredMcastIpAddresses == []:
        print 'sendIgmpJoinNgpf: No Mcast IP address configured'
        return 1

    if multicastIpAddress == 'all':
        listOfMcastAddresses = listOfConfiguredMcastIpAddresses
    else:
        listOfMcastAddresses = multicastIpAddress

    # Note: Index position is not zero based.
    indexListToSend = []
    for eachMcastAddress in listOfMcastAddresses:
        index = listOfConfiguredMcastIpAddresses.index(eachMcastAddress)
        indexListToSend.append(index+1)

    url = sessionUrl+'/igmpMcastIPv4GroupList/operations/igmpjoingroup'
    data = {'arg1': [sessionUrl+'/igmpMcastIPv4GroupList'], 'arg2': indexListToSend}

    print '\nsendIgmpJoinNgpf:', url
    print '\t', multicastIpAddress
    response = requests.post(url, data=json.dumps(data), headers=jsonHeader)
    if response.status_code != 202:
        return 1
    if waitForComplete(response, url+response.json()['id']) == 1:
        return 1
    else:
        return 0

def sendIgmpJoinOrLeaveNgpf(**kwargs):
    # This API will send IGMP joins or leaves.
    # If groupRangeList or sourceRangeList is 'all', this will send IGMP join
    # on all configured multicast addresses.
    # Else, provide a list of group range IP addresses to send join or leave.
    # MANDATORY PARAMETERS:
    #    protocolObject or sessionUrl
    #    groupRangeList or sourcRangeList
    # OPTIONAL PARAMETERS:
    #    portName
    #    hostIp
    # PARAMETER DEFINITIONS:
    #    action:          State join or leave.
    #    protocolObject:  The igmpHost object to send joins|leaves.
    #    sessionUrl:      This is the session ID: 'http://192.168.70.127:11009/api/v1/sessions/1/ixnetwork'.
    #                     If you want this API to dynamically figure out the igmp host
    #                     object based on hostIp or portName, then use sessionUrl
    #                     because this API needs to know your REST API server:port and the connected session ID.
    #    groupRangeList:  State 'all' or a list of mcast group range ip addresses to send joins|leaves.
    #    sourceRangeList: State 'all' or a list of mcast source group ip addresses to send joins|leaves.
    #    hostIp:          Dynamically get the igmpHost object based on the provided host IP address.
    #    portName:        Dynamically get the igmpHost object based on the provided host IP address.
    # USAGE: SELECT A METHOD
    #       (About #4: Include a portName to handle VPN configurations if the
    #        same IP address is on multiple ports.)
    #    Provide a known IGMP host object:
    # All examples below will search for the corresponding igmpHost
    # object based on hostIp or portName.
    #    This is similar to the above, with an addition of providing a portName for filtering.
    #    In a scenario such as VPN where multiple ports have the same IP, this API uses the portName
    #    as a filter to distinguish which multicast IP address to send joins or leaves.

    if 'protocolObject' not in kwargs and 'sessionUrl' not in kwargs:
        print '\nError: You must provide one of the followings: protocolObject or sessionUrl'
        print 'Please read the API description for usage.'
        return 1

    if 'protocolObject' in kwargs:
        httpHeader = kwargs['protocolObject'].split('/api')[0]

    if 'sessionUrl' in kwargs:
        import re
        # Do some error checking and grab the serverIp:socket
        serverUrlMatch = re.match('(http://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+)/api/v1/sessions/[0-9]+/ixnetwork$', kwargs['sessionUrl'])
        if serverUrlMatch:
            httpHeader = serverUrlMatch.group(1)
        else:
            print '\nError: sessionUrl parameter should be: http://<serverIp>:socketPort/api/v1/sessions/<sessionId>/ixnetwork'
            print 'You entered:', kwargs['sessionUrl']
            return 1

    if 'action' not in kwargs:
        print '\nError: You must include the "action" parameter and state join or leave'
        return 1

    if kwargs['action'] == 'join':
        sendAction = 'igmpjoingroup'
    if kwargs['action'] == 'leave':
        sendAction = 'igmpleavegroup'

    if 'groupRangeList' in kwargs and 'sourceRangeList' in kwargs:
        print '\nError: Please select either groupRangeList or sourceRangeList. Not both.'
        return 1

    if 'groupRangeList' not in kwargs and 'sourceRangeList' not in kwargs:
        print '\nError: It is mandatory to provide either groupRangeList or sourceRangeList.'
        return 1

    jsonHeader = {'content-type': 'application/json'}
    igmpHostSessionUrlList = []

    #     Get a list of Topologies
    #     Get a list of vports
    #     Match portName with vport's name
    #     If found, look for matching mcastGroupRangeList:
    #        Use the topology object and:
    #        Get a list of ipv4
    #        Get a list of igmpHost objects (This becomes the current sessionUrl).

    if 'portName' in kwargs or 'hostIp' in kwargs:
        response = requests.get(kwargs['sessionUrl']+'/topology')
        topologyList = ['%s/%s/%s' % (kwargs['sessionUrl'], 'topology', str(i["id"])) for i in response.json()]
        for topology in topologyList:
            response = requests.get(topology)
            if response.status_code != 200:
                return 1
            vportList = response.json()['vports']
            if vportList == []:
                print 'Error: No vport is created'
                return 1
            for vport in vportList:
                response = requests.get(httpHeader+vport)
                if response.status_code != 200:
                    print 'Error: Get vport status code:', response.status_code
                    return 1
                currentVportName = response.json()['name']
                response = requests.get(topology+'/deviceGroup')
                deviceGroupList = ['%s/%s/%s' % (topology, 'deviceGroup', str(i["id"])) for i in response.json()]
                for deviceGroup in deviceGroupList:
                    response = requests.get(deviceGroup+'/ethernet')
                    ethernetList = ['%s/%s/%s' % (deviceGroup, 'ethernet', str(i["id"])) for i in response.json()]
                    for ethernet in ethernetList:
                        response = requests.get(ethernet+'/ipv4')
                        configuredIpv4ObjList = ['%s/%s/%s' % (ethernet, 'ipv4', str(i["id"])) for i in response.json()]
                        response = requests.get(ethernet+'/ipv6')
                        configuredIpv6ObjList = ['%s/%s/%s' % (ethernet, 'ipv6', str(i["id"])) for i in response.json()]

                        for layer3IpObj in (configuredIpv4ObjList + configuredIpv6ObjList):
                            response = requests.get(layer3IpObj)
                            if response.status_code != 200:
                                print 'Error: GET:', layer3IpObj
                                print 'Error: Status code failed', response.status_code
                                return 1
                            currentIpAddrMultivalue = response.json()['address']
                            response = requests.get(httpHeader + currentIpAddrMultivalue)
                            if response.status_code != 200:
                                print 'Error: GET:', layer3IpObj
                                print 'Error: Status code failed:', response.status_code
                                return 1
                            configuredIpAddresses = response.json()['values']
                            if 'hostIp' in kwargs and kwargs['hostIp'] not in configuredIpAddresses:
                                continue
                            response = requests.get(layer3IpObj+'/igmpHost')
                            if response.status_code != 200:
                                # Ignore status code failures here because it is expected that
                                # not all layer3 objects have igmpHost configured.
                                continue
                            foreachHostObj = response.json()[0]['links'][0]['href']
                            if 'igmpHost' in foreachHostObj:
                                # Scenario #1:  Based on portName only
                                if ('portName' in kwargs and
                                    'hostIp' not in kwargs and
                                    kwargs['portName'] == currentVportName):
                                    igmpHostSessionUrlList.append(httpHeader + foreachHostObj)

                                # Scenario #2:  Based on hostIp only
                                if ('hostIp' in kwargs and
                                    'portName' not in kwargs
                                    and kwargs['hostIp'] in configuredIpAddresses):
                                    igmpHostSessionUrlList.append(httpHeader + foreachHostObj)

                                # Scenario #3:  Based on hostIp & portName (VPN handlings)
                                if ('hostIp' in kwargs and
                                    'portName' in kwargs and
                                    kwargs['portName'] == currentVportName and
                                    kwargs['hostIp'] in configuredIpAddresses):
                                    igmpHostSessionUrlList.append(httpHeader + foreachHostObj)

        if igmpHostSessionUrlList == []:
            print 'Error: No IGMP host object found.'
            return 1
        protocolHostObj = igmpHostSessionUrlList[0]

    if 'protocolObject' in kwargs:
        protocolHostObj = kwargs['protocolObject']

    #     - Get igmpHost object.
    #     - Get its list of configured mcast:
    #          - groupRange IP addresses.
    #     - Match user defined groupRangeList with listOfConfiguredMcastIpAddresses
    #       to get all the indexes.
    #     - Send join|leave using igmpHost object with list of indexes.

    response = requests.get(protocolHostObj)
    if response.status_code != 200:
        return 1
    if response.json()['sessionStatus'][0] == 'notStarted':
        print 'Error: IGMP protocol is not started. Cannot send joins|leaves'
        return 1

    # 1> Based on the list of groupRangeList, get all their indexes.
    if 'groupRangeList' in kwargs:
        response = requests.get(protocolHostObj+'/igmpMcastIPv4GroupList')
        if response.status_code != 200:
            return 1
        startAddrMultivalue = response.json()['startMcastAddr']
        incrAddrMultivalue = response.json()['mcastAddrIncr']
        addrCntMultivalue = response.json()['mcastAddrCnt']

    if 'sourceRangeList' in kwargs:
        response = requests.get(protocolHostObj+'/igmpMcastIPv4GroupList/igmpUcastIPv4SourceList')
        if response.status_code != 200:
            return 1
        startAddrMultivalue = response.json()['startUcastAddr']
        incrAddrMultivalue = response.json()['ucastAddrIncr']
        addrCntMultivalue = response.json()['ucastSrcAddrCnt']

    response = requests.get(httpHeader + startAddrMultivalue)
    if response.status_code != 200:
        return 1

    listOfConfiguredMcastIpAddresses = response.json()['values']
    #print '\nsendIgmpJoinNgpf: List of configured Mcast IP addresses:', listOfConfiguredMcastIpAddresses
    if listOfConfiguredMcastIpAddresses == []:
        print 'sendIgmpJoinNgpf: No Mcast IP address configured'
        return 1

    try:
        if 'groupRangeList' in kwargs and kwargs['groupRangeList'] == 'all':
            listOfMcastAddresses = listOfConfiguredMcastIpAddresses
        else:
            listOfMcastAddresses = kwargs['groupRangeList']
    except:
        pass

    try:
        if 'sourceRangeList' in kwargs and kwargs['sourceRangeList'] == 'all':
            listOfMcastAddresses = listOfConfiguredMcastIpAddresses
        else:
            listOfMcastAddresses = kwargs['sourceRangeList']
    except:
        pass

    indexListToSend = []
    for eachMcastAddress in listOfMcastAddresses:
        try:
            index = listOfConfiguredMcastIpAddresses.index(eachMcastAddress)
            # Note: Index position is not zero based.
            indexListToSend.append(index+1)
        except:
            print '\nsendIgmpJoinNgpf: Error: The multicast IP address that you provided is not a configured address:', eachMcastAddress
            return 1

    # Get the multicast group increment
    response = requests.get(httpHeader + incrAddrMultivalue)
    if response.status_code != 200:
        return 1
    mcastIncrValueList = response.json()['values']

    # Get the multicast group total count
    response = requests.get(httpHeader + addrCntMultivalue)
    if response.status_code != 200:
        return 1
    mcastGroupCountValueList = response.json()['values']

    if sendAction == 'igmpjoingroup':
        igmpSend = 'join'
    else:
        igmpSend = 'leave'

    print '\nSending IGMP %s on:' % igmpSend
    for index in indexListToSend:
        groupIp = listOfConfiguredMcastIpAddresses[index-1]
        incrStep    = mcastIncrValueList[index-1]
        # The total number of mcast IP address count
        groupIpAddrCount  = mcastGroupCountValueList[index-1]

        # Write some logic to get the last group IP address for display
        convertIncrStepToList  = incrStep.split('.')

        try:
            # Look for the index position of '1' in '0.0.0.1'
            incrStepIndex = convertIncrStepToList.index('1')
            convertGroupIpToList = groupIp.split('.')
            ipOctetToIncrement = convertGroupIpToList[incrStepIndex]

            lastOctetGroupIpAddress = int(ipOctetToIncrement) + int(groupIpAddrCount)-1
            convertGroupIpToList[incrStepIndex] = str(lastOctetGroupIpAddress)
            lastGroupIpAddress = '.'.join(convertGroupIpToList)
        except:
            lastGroupIpAddress = groupIp

        print '\tmcastGroupAddress={groupIp}  step={incrStep}  groupAddressCount={groupIpAddrCount}  lastgroupIp={lastGroupIp}'.format(
            groupIp=groupIp,
            incrStep=incrStep,
            groupIpAddrCount=groupIpAddrCount,
            lastGroupIp=lastGroupIpAddress
        )

    url = protocolHostObj+'/igmpMcastIPv4GroupList/operations/%s' % sendAction
    if indexListToSend != []:
        data = {'arg1': [protocolHostObj+'/igmpMcastIPv4GroupList'], 'arg2': indexListToSend}
        print '\nURL:', url
        print '\nDATA:', data
        response = requests.post(url, data=json.dumps(data), headers=jsonHeader)
        if response.status_code != 202:
            return 1
        if waitForComplete(response, url+response.json()['id']) == 1:
            return 1
        else:
            return 0

def sendIgmpJoinNgpf_backup(sessionUrl, multicastIpAddress):
    # This API will send IGMP join.
    # If multicastIpAddress is 'all', this will send IGMP join on all multicast addresses.
    # Else, provide a list of multicast IP addresses to send join.
    # multicastIpAddress: 'all' or a list of multicast IP addresses to send joins

    httpHeader = sessionUrl.split('/api')[0]
    jsonHeader = {'content-type': 'application/json'}

    # 1> Based on the list of multicastIpAddress, get all their indexes.
    response = requests.get(sessionUrl+'/igmpMcastIPv4GroupList')
    if response.status_code != 200:
        return 1
    startMcastAddrMultivalue = response.json()['startMcastAddr']

    response = requests.get(httpHeader+startMcastAddrMultivalue)
    if response.status_code != 200:
        return 1
    listOfConfiguredMcastIpAddresses = response.json()['values']
    print '\nsendIgmpJoinNgpf: List of configured Mcast IP addresses:', listOfConfiguredMcastIpAddresses
    if listOfConfiguredMcastIpAddresses == []:
        print 'sendIgmpJoinNgpf: No Mcast IP address configured'
        return 1

    if multicastIpAddress == 'all':
        listOfMcastAddresses = listOfConfiguredMcastIpAddresses
    else:
        listOfMcastAddresses = multicastIpAddress

    # Note: Index position is not zero based.
    indexListToSend = []
    for eachMcastAddress in listOfMcastAddresses:
        index = listOfConfiguredMcastIpAddresses.index(eachMcastAddress)
        indexListToSend.append(index+1)

    url = sessionUrl+'/igmpMcastIPv4GroupList/operations/igmpjoingroup'
    data = {'arg1': [sessionUrl+'/igmpMcastIPv4GroupList'], 'arg2': indexListToSend}

    print '\nsendIgmpJoinNgpf:', url
    print '\t', multicastIpAddress
    response = requests.post(url, data=json.dumps(data), headers=jsonHeader)
    if response.status_code != 202:
        return 1
    if waitForComplete(response, url+response.json()['id']) == 1:
        return 1
    else:
        return 0

def sendIgmpLeaveNgpf(sessionUrl, multicastIpAddress):
    # This API will send IGMP leaves.
    # If multicastIpAddress is 'all', this will send IGMP leaves on all multicast addresses.
    # Else, provide a list of multicast IP addresses to send leaves.
    # multicastIpAddress: 'all' or a list of multicast IP addresses to send leaves

    httpHeader = sessionUrl.split('/api')[0]
    jsonHeader = {'content-type': 'application/json'}

    # 1> Based on the list of multicastIpAddress, get all their indexes.
    response = requests.get(sessionUrl+'/igmpMcastIPv4GroupList')
    if response.status_code != 200:
        return 1
    startMcastAddrMultivalue = response.json()['startMcastAddr']

    response = requests.get(httpHeader+startMcastAddrMultivalue)
    if response.status_code != 200:
        return 1
    listOfConfiguredMcastIpAddresses = response.json()['values']
    print '\nsendIgmpLeaveNgpf: List of configured Mcast IP addresses:', listOfConfiguredMcastIpAddresses
    if listOfConfiguredMcastIpAddresses == []:
        print 'sendIgmpLeaveNgpf: No Mcast IP address configured'
        return 1

    if multicastIpAddress == 'all':
        listOfMcastAddresses = listOfConfiguredMcastIpAddresses
    else:
        listOfMcastAddresses = multicastIpAddress

    # Note: Index position is not zero based.
    indexListToSend = []
    for eachMcastAddress in listOfMcastAddresses:
        index = listOfConfiguredMcastIpAddresses.index(eachMcastAddress)
        indexListToSend.append(index+1)

    url = sessionUrl+'/igmpMcastIPv4GroupList/operations/igmpleavegroup'
    data = {'arg1': [sessionUrl+'/igmpMcastIPv4GroupList'], 'arg2': indexListToSend}

    print '\nsendIgmpLeaveNgpf:', url
    print '\t', multicastIpAddress
    response = requests.post(url, data=json.dumps(data), headers=jsonHeader)
    if response.status_code != 202:
        return 1
    if waitForComplete(response, url+response.json()['id']) == 1:
        return 1
    else:
        return 0

def sendPimV4JoinNgpf(sessionUrl):
    # sessionUrl: The pimV4Interace object.

    url = sessionUrl+'/pimV4JoinPruneList/operations/join'

    # data = {'arg1': [A list of /api/v1/.../topology/1/.../pimV4Interface/<number>/pimV4JoinPruneList]}
    pimV4InterfacePath = sessionUrl.split('/api')[1]
    data = {'arg1': [pimV4InterfacePath+'/pimV4JoinPruneList']}
    urlHeadersJson = {'content-type': 'application/json'}

    print '\nsendPimV4JoinNgpf:', url
    response = requests.post(url, data=json.dumps(data), headers=urlHeadersJson)
    if response.status_code != 202:
        return 1
    if waitForComplete(response, url+response.json()['id']) == 1:
        return 1
    else:
        return 0

def sendPimV4LeaveNgpf(sessionUrl):
    # sessionUrl: The pimV4Interace object.

    url = sessionUrl+'/pimV4JoinPruneList/operations/leave'

    # data = {'arg1': [A list of /api/v1/.../topology/1/.../pimV4Interface/<number>/pimV4JoinPruneList]}
    pimV4InterfacePath = sessionUrl.split('/api')[1]
    data = {'arg1': [pimV4InterfacePath+'/pimV4JoinPruneList']}
    urlHeadersJson = {'content-type': 'application/json'}

    print '\nsendPimV4LeaveNgpf:', url
    response = requests.post(url, data=json.dumps(data), headers=urlHeadersJson)
    if response.status_code != 202:
        return 1
    if waitForComplete(response, url+response.json()['id']) == 1:
        return 1
    else:
        return 0

def sendMldJoinNgpf(sessionUrl, ipv6AddressList):
    # For IPv6 only
    # This API will take the mld sessionUrl object and loop through all the configured ports
    # looking for the specified ipv6Address to send a join.
    # ipv6AddressList: 'all' or a list of IPv6 addresses that must be EXACTLY how it is configured on the GUI.

    httpHeader = sessionUrl.split('/api')[0]
    jsonHeader = {'content-type': 'application/json'}

    # Loop all port objects to get user specified IPv6 address to send the join.
    portObjectList = sessionUrl+'/mldMcastIPv6GroupList/port'
    response = requests.get(portObjectList)
    if response.status_code != 200:
        return

    for eachPortIdDetails in response.json():
        currentPortId = eachPortIdDetails['id']
        # For each ID, get the 'startMcastAddr' multivalue
        startMcastAddrMultivalue = eachPortIdDetails['startMcastAddr']

        # Go to the multivalue and get the 'values'
        response = requests.get(httpHeader+startMcastAddrMultivalue)
        if response.status_code != 200:
            return 1

        listOfConfiguredGroupIpAddresses = response.json()['values']
        if ipv6AddressList == 'all':
            listOfGroupAddresses = listOfConfiguredGroupIpAddresses
        else:
            listOfGroupAddresses = ipv6AddressList

        for eachSpecifiedIpv6Addr in listOfGroupAddresses:
            if eachSpecifiedIpv6Addr in listOfConfiguredGroupIpAddresses:
                # if 'values' match ipv4Address, do a join on:
                url = sessionUrl+'/mldMcastIPv6GroupList/port/%s/operations/mldjoingroup' % currentPortId
                portIdObj = sessionUrl+'/mldMcastIPv6GroupList/port/%s' % currentPortId
                response = requests.post(url, data=json.dumps({'arg1': [portIdObj]}), headers=jsonHeader)
                if response.status_code != 202:
                    return 1
                if waitForComplete(response, url+response.json()['id']) == 1:
                    return 1
                else:
                    return 0

    return 0

def sendMldLeaveNgpf(sessionUrl, ipv6AddressList):
    # For IPv6 only
    # This API will take the mld sessionUrl object and loop through all the configured ports
    # looking for the specified ipv6Address to send a leave.
    # ipv6AddressList: 'all' or a list of IPv6 addresses that must be EXACTLY how it is configured on the GUI.

    httpHeader = sessionUrl.split('/api')[0]
    jsonHeader = {'content-type': 'application/json'}

    # Loop all port objects to get user specified IPv6 address to send the leave.
    portObjectList = sessionUrl+'/mldMcastIPv6GroupList/port'
    response = requests.get(portObjectList)
    if response.status_code != 200:
        return

    for eachPortIdDetails in response.json():
        currentPortId = eachPortIdDetails['id']
        # For each ID, get the 'startMcastAddr' multivalue
        startMcastAddrMultivalue = eachPortIdDetails['startMcastAddr']

        # Go to the multivalue and get the 'values'
        response = requests.get(httpHeader+startMcastAddrMultivalue)
        if response.status_code != 200:
            return 1

        listOfConfiguredGroupIpAddresses = response.json()['values']
        if ipv6AddressList == 'all':
            listOfGroupAddresses = listOfConfiguredGroupIpAddresses
        else:
            listOfGroupAddresses = ipv6AddressList

        for eachSpecifiedIpv6Addr in listOfGroupAddresses:
            if eachSpecifiedIpv6Addr in listOfConfiguredGroupIpAddresses:
                # if 'values' match ipv4Address, do a join on:
                url = sessionUrl+'/mldMcastIPv6GroupList/port/%s/operations/mldleavegroup' % currentPortId
                portIdObj = sessionUrl+'/mldMcastIPv6GroupList/port/%s' % currentPortId
                response = requests.post(url, data=json.dumps({'arg1': [portIdObj]}), headers=jsonHeader)
                if response.status_code != 202:
                    return 1
                if waitForComplete(response, url+response.json()['id']) == 1:
                    return 1
                else:
                    return 0

    return 0

def prettyPrintAllOperations(sessionUrl, searchPath='/operations'):
    # Dispaly all the operation commands and its description:

    response = requests.get(sessionUrl+searchPath)
    for item in response.json():
        if 'operation' in item.keys():
            print '\n', item['operation']
            print '\t%s' % item['description']
            if 'args' in item.keys():
                for nestedKey,nestedValue in item['args'][0].items():
                    print '\t\t%s: %s' % (nestedKey, nestedValue)

ixNetUrl = "http://"+py.ixTclServer+":"+str(py.ixRestPort)+"/api/v1"
sessionUrl = ixNetUrl+'/sessions/1/ixnetwork'
serverUrl = 'http://'+py.ixTclServer+':'+str(py.ixRestPort)

# Uncomment this if you want to reassign ports

'''
sendIgmpJoinOrLeaveNgpf(protocolObject='http://192.168.70.127:11009/api/v1/sessions/1/ixnetwork/topology/1/deviceGroup/1/ethernet/1/ipv4/1/igmpHost/1',
                        groupRangeList=['225.0.0.2', '225.0.0.4'],
                        action='leave')

sys.exit()
'''

'''
sendIgmpJoinOrLeaveNgpf(sessionUrl=sessionUrl,
                        portName='my port1',
                        groupRangeList='all',
                        action='join')
sys.exit()
'''

'''
sendIgmpJoinOrLeaveNgpf(sessionUrl=sessionUrl,
                        hostIp='1.1.1.1',
                        groupRangeList=['225.0.0.2', '225.0.0.4'],
                        action='leave')
'''

'''
sendIgmpJoinOrLeaveNgpf(sessionUrl=sessionUrl,
                        hostIp='1.1.1.1',
                        portName='my port1',
                        groupRangeList='all',
                        action='join')
sys.exit()
'''

sendIgmpJoinOrLeaveNgpf(sessionUrl=sessionUrl,
                        portName='my port1',
                        sourceRangeList='all',
                        action='join')

#x enableDisableIMld
#x Join/leave mld: simple join/leave for mld with only group specified
#     Source Group specific join/leave: igmp, mld, and pim
#     2> By host IP address
#           Scenario #2: Handle same host IP on multiple ports (VPN). Must filter by port parameter.
#     3> Display the Group Range, Increment=#, and Last Group IP Address.
#     4> Source Group join|leave
#  Pim: join, prune
#x  Igmp stats
#x  Mld stats
#x  Pim stats

sys.exit()

if verifyPortState(sessionUrl) == 1:
    sys.exit()

if startAllProtocols(sessionUrl) == 1:
    sys.exit()

if verifyAllProtocolSessionsNgpf(sessionUrl, timeout=120) == 1:
    sys.exit()

if regenerateAllTrafficItems(sessionUrl) == 1:
    sys.exit()

if applyTraffic(sessionUrl) == 1:
    sys.exit()

if startTraffic(sessionUrl) == 1:
    sys.exit()

time.sleep(10)

stats = getStats(sessionUrl, viewName='Flow Statistics')
print printDict(stats)

print '--- tx frames:', stats[16]['Tx Frames']


    regex = attr_name + r"\s+=\s+['\"](.+)['\"]"
    if sys.version_info.major == 2:
        with open('pefile.py', 'r') as f:
            match = re.search(regex, f.read())
    else:
        with open('pefile.py', 'r', encoding='utf-8') as f:
            match = re.search(regex, f.read())
    # Second item in the group is the value of attribute.
    return match.group(1)

import ast
import os
import re
import sys

if sys.version_info.major == 3:
    from io import open

try:
    from setuptools import setup, Command
except ImportError as excp:
    from distutils.core import setup, Command

from unittest import TestLoader, TextTestRunner

os.environ['COPY_EXTENDED_ATTRIBUTES_DISABLE'] = 'true'
os.environ['COPYFILE_DISABLE'] = 'true'

def _read_doc():
    """
    Parse docstring from file 'pefile.py' and avoid importing
    this module directly.
    """
    if sys.version_info.major == 2:
        with open('pefile.py', 'r') as f:
            tree = ast.parse(f.read())
    else:
        with open('pefile.py', 'r', encoding='utf-8') as f:
            tree = ast.parse(f.read())
    return ast.get_docstring(tree)

def _read_attr(attr_name):
    """
    Parse attribute from file 'pefile.py' and avoid importing
    this module directly.

    __version__, __author__, __contact__,
    """

class TestCommand(Command):
    """Run tests."""
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        test_suite = TestLoader().discover('./tests', pattern='*_test.py')
        test_results = TextTestRunner(verbosity=2).run(test_suite)

setup(name = 'pefile',
    version = _read_attr('__version__'),
    description = 'Python PE parsing module',
    author = _read_attr('__author__'),
    author_email = _read_attr('__contact__'),
    url = 'http://example.com',
    download_url='http://example.com',
    keywords = ['pe', 'exe', 'dll', 'pefile', 'pecoff'],
    classifiers = [
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: Developers',
        'Intended Audience :: Science/Research',
        'Natural Language :: English',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Topic :: Software Development :: Libraries :: Python Modules'],
    long_description = "\n".join(_read_doc().split('\n')),
    cmdclass={"test": TestCommand},
    py_modules = ['pefile', 'peutils'],
    python_requires=">=3.6.0",
    packages = ['ordlookup'],
    install_requires=[
          'future',
    ],
    license="MIT",
)


return float(self.overlap) / float(len(self.proximalSegment.synapses))

'''
Created on Jan 16, 2011

@author: Barry Maturkanich
'''

from HTM.Cell import Cell
from HTM.Segment import Segment
from HTM.Synapse import Synapse

EMA_ALPHA = 0.005     #Exponential Moving Average alpha value

class Column(object):
  """
  Represents a single column of cells within an HTM Region.
  """

  def __init__(self, region, srcPos, pos):
    """
    Construct a new Column for the given parent region at source row/column
    position srcPos and column grid position pos.
    @param region the parent Region this Column belongs to.
    @param srcPos a tuple (srcX,srcY) of this Column's 'center' position
                 in terms of the proximal-synapse input space.
    @param pos a tuple(x,y) of this Column's position within the
               Region's column grid.
    """
    self.region = region  #parent region
    self.cells = [Cell(self, i) for i in xrange(region.cellsPerCol)] #Sequence cells
    self.isActive = False #whether or not this Column is currently active.
    #The list of potential synapses and their permanence values.
    self.proximalSegment = Segment(region.segActiveThreshold)
    #The boost value for column c as computed during learning.
    #  used to increase the overlap value for inactive columns.
    self.boost = 1.0
    #A sliding average representing how often column c has been active
    #  after inhibition (e.g. over the last 1000 iterations).
    self.activeDutyCycle = 1.0
    #A sliding average representing how often column c has had
    #  significant overlap (i.e. greater than minOverlap) with its inputs
    #  (e.g. over the last 1000 iterations).
    self.overlapDutyCycle = 1.0
    self.overlap = 0 #the last computed input overlap for the Column.
    self.ix = srcPos[0] #'input' row and col
    self.iy = srcPos[1]
    self.cx = pos[0] #'column grid' row and col
    self.cy = pos[1]

  def setActive(self, isActive):
    """
    Toggle whether or not this Column is currently active.
    """
    self.isActive = isActive

  def getOverlapPercentage(self):
    """ Return the (last computed) input overlap for this Column in terms of the
    percentage of active synapses out of total existing synapses.  """

  def getConnectedSynapses(self):
    """ Return the list of all currently connected proximal synapses for
    this Column. """
    return self.proximalSegment.getConnectedSynapses()

  def getBestMatchingCell(self, isSequence, previous=False):
    """
    For this column, return the cell with the best matching segment (at time t-1 if
    prevous=True else at time t). Only consider sequence segments if isSequence
    is True, otherwise only consider non-sequence segments. If no cell has a
    matching segment, then return the cell with the fewest number of segments.
    @return a list containing the best cell and its best segment (may be None).
    """
    bestCell = None
    bestSeg = None
    bestCount = 0
    for cell in self.cells:
      seg = cell.getBestMatchingSegment(isSequence, previous)
      if seg:
        if previous:
          synCount = len(seg.getPrevActiveSynapses(connectedOnly=False))
        else:
          synCount = len(seg.getActiveSynapses(connectedOnly=False))
        if synCount > bestCount:
          bestCell = cell
          bestSeg = seg
          bestCount = synCount

    if not bestCell:
      bestCell = self.cells[0]
      fewestCount = len(bestCell.segments)
      for cell in self.cells[1:]:
        if len(cell.segments) < fewestCount:
          fewestCount = len(cell.segments)
          bestCell = cell

    return bestCell, bestSeg

  def computeOverlap(self):
    """
    The spatial pooler overlap of this column with a particular input pattern.
    The overlap for each column is simply the number of connected synapses with active
    inputs, multiplied by its boost. If this value is below minOverlap, we set the
    overlap score to zero.
    """
    overlap = len(self.proximalSegment.getActiveSynapses())

    if overlap < self.region.minOverlap:
      overlap = 0
    else:
      overlap *= self.boost
    self.overlap = overlap

  def updatePermanences(self):
    """
    Update the permanence value of every synapse in this column based on whether active.
    This is the main learning rule (for the column's proximal dentrite).
    For winning columns, if a synapse is active, its permanence value is incremented,
    otherwise it is decremented. Permanence values are constrained to be between 0 and 1.
    """
    for syn in self.proximalSegment.synapses:
      if syn.isActive():
        syn.increasePermanence()
      else:
        syn.decreasePermanence()

  def performBoosting(self):
    """
    There are two separate boosting mechanisms
    in place to help a column learn connections. If a column does not win often
    enough (as measured by activeDutyCycle), its overall boost value is
    increased (line 30-32). Alternatively, if a column's connected synapses
    do not overlap well with any inputs often enough (as measured by
    overlapDutyCycle), its permanence values are boosted (line 34-36).
    Note: once learning is turned off, boost(c) is frozen.
    """
    #minDutyCycle(c) A variable representing the minimum desired firing rate for a cell.
    #  If a cell's firing rate falls below this value, it will be boosted. This value is
    #  calculated as 1% of the maximum firing rate of its neighbors.
    minDutyCycle = 0.01 * self.maxDutyCycle(self.region.neighbors(self))
    self.updateActiveDuteCycle()
    self.boost = self.boostFunction(minDutyCycle)

    self.updateOverlapDutyCycle()
    if self.overlapDutyCycle < minDutyCycle:
      self.increasePermanences(0.1*Synapse.CONNECTED_PERM)

  def maxDutyCycle(self, cols):
    """
    Returns the maximum active duty cycle of the columns in the given list
    of columns.
    """
    return max((col.activeDutyCycle for col in cols))

  def increasePermanences(self, scale):
    """
    Increase the permanence value of every synapse in this column by a scale factor.
    """
    for syn in self.proximalSegment.synapses:
      syn.increasePermanence(scale)

  def updateActiveDuteCycle(self):
    """
    Computes a moving average of how often this column has been active
    after inhibition.
    """
    newCycle = (1.0 - EMA_ALPHA) * self.activeDutyCycle
    if self.isActive:
      newCycle += EMA_ALPHA
    self.activeDutyCycle = newCycle

  def updateOverlapDutyCycle(self):
    """
    Computes a moving average of how often this column has overlap greater
    than minOverlap.
    Exponential moving average (EMA):
    St = a * Yt + (1-a)*St-1
    """
    newCycle = (1.0 - EMA_ALPHA) * self.overlapDutyCycle
    if self.overlap > self.region.minOverlap:
      newCycle += EMA_ALPHA
    self.overlapDutyCycle = newCycle

  def boostFunction(self, minDutyCycle):
    """
    Returns the boost value of this column. The boost value is a scalar >= 1.
    If activeDutyCyle(c) is above minDutyCycle(c), the boost value is 1.
    The boost increases linearly once the column's activeDutyCycle starts
    falling below its minDutyCycle.
    """
    if self.activeDutyCycle > minDutyCycle:
      return 1.0
    elif self.activeDutyCycle == 0.0:
      return self.boost * 1.05 #if 0 activeDuty, fix at +5%
    return minDutyCycle / self.activeDutyCycle


    s = list(iterable)
    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))

# Modified by Microsoft Corporation.

# misc useful functions

import imp
import os

def dataset_walker(dataset=None, dataroot=None, labels=None):
    # we assume that the dataset_walker class in dataroot/../scripts
    # is the one to use
    scripts_folder = os.path.join(dataroot, '../..', "scripts")
    _dw = imp.load_source('dataset_walker', os.path.join(scripts_folder, "dataset_walker.py"))
    return _dw.dataset_walker(dataset, dataroot=dataroot, labels=labels)

def import_class(cl):
    d = cl.rfind(".")
    classname = cl[d+1:len(cl)]
    m = __import__(cl[0:d], globals(), locals(), [classname])
    return getattr(m, classname)

from itertools import chain, combinations

def powerset(iterable):
    "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"

def svm_to_libsvm(model, labels=None) :
    # convert an sklearn model object into a file in the format of LIBSVM's sparse SVMs
    # (actually return the files lines in an array)
    lines = []
    n_classes = model.coef_.shape[0]+1
    total_n_SV, n_feats= model.support_vectors_.shape
    n_SV = model.n_support_

    SV = model.support_vectors_

    dual_coef = model.dual_coef_.todense()
    b = model.intercept_

    probA = model.probA_
    probB = model.probB_

    lines.append("svm_type")
    lines.append("nr_class %i" % n_classes)
    lines.append("total_sv %i" % total_n_SV)

    lines.append("rho "+" ".join(["%.12f" % -c for c in b]))

    if labels == None:
        labels = map(str, range(n_classes))
    lines.append("label " +  " ".join(labels))

    lines.append("probA "+" ".join(["%.12f" % v for v in probA]))
    lines.append("probB "+" ".join(["%.12f" % v for v in probB]))

    lines.append("nr_sv "+" ".join(["%i" % v for v in n_SV]))

    lines.append("SV")
    SV = SV.tocsc()

    for i in range( total_n_SV) :
            # coefs are in the jth column of coef
        this_line = ""
        for c in dual_coef[:,i] :
            this_line += ("%.12f " % c)
        sv = SV[i,:].tocoo()

        for j,v in zip(sv.col, sv.data) :
            this_line += ("%i:%.12f " % (j,v))
        lines.append(this_line)
    return lines


        schools = HttpResponse(content_type='text/csv')
        schools['Content-Disposition'] = 'attachment; filename="schools.csv"'
        writer = csv.writer(schools)
        for row in self.get_rows():
            writer.writerow(row)

        return schools

import csv

from django.conf import settings
from django.conf.urls import url
from django.contrib import admin
from django.urls import reverse
from django.http import HttpResponse, HttpResponseRedirect

from googleapiclient.discovery import build
from google.oauth2 import service_account

from huxley.core.models import School

class SchoolAdmin(admin.ModelAdmin):

    search_fields = ('name', )

    def get_rows(self):
        rows = []
        rows.append([
            "ID",
            "Name",
            "Address",
            "City",
            "State",
            "Zip Code",
            "Country",
            "Primary Name",
            "Primary Gender",
            "Primary Email",
            "Primary Phone",
            "Primary Type",
            "Secondary Name",
            "Secondary Gender",
            "Secondary Email",
            "Secondary Phone",
            "Secondary Type",
            "Program Type",
            "Times Attended",
            "International",
        ])

        for school in School.objects.all().order_by('name'):
            rows.append([
                str(field) for field in [
                    school.id,
                    school.name,
                    school.address,
                    school.city,
                    school.state,
                    school.zip_code,
                    school.country,
                    school.primary_name,
                    school.primary_gender,
                    school.primary_email,
                    school.primary_phone,
                    school.primary_type,
                    school.secondary_name,
                    school.secondary_gender,
                    school.secondary_email,
                    school.secondary_phone,
                    school.secondary_type,
                    school.program_type,
                    school.times_attended,
                    school.international,
                ]
            ])
        return rows

    def info(self, request):
        ''' Returns a CSV file containing the current set of
            Schools in our database with all of its fields. '''

    def sheets(self, request):
        if settings.SHEET_ID:
            SHEET_RANGE = 'Schools!A1:T'
            # Store credentials
            creds = service_account.Credentials.from_service_account_file(
                settings.SERVICE_ACCOUNT_FILE, scopes=settings.SCOPES)

            data = self.get_rows()

            body = {
                'values': data,
            }

            service = build('sheets', 'v4', credentials=creds)
            response = service.spreadsheets().values().clear(
                spreadsheetId=settings.SHEET_ID,
                range=SHEET_RANGE,
            ).execute()
            response = service.spreadsheets().values().update(
                spreadsheetId=settings.SHEET_ID,
                range=SHEET_RANGE,
                valueInputOption='USER_ENTERED',
                body=body).execute()

        return HttpResponseRedirect(reverse('admin:core_school_changelist'))

    def get_urls(self):
        return super(SchoolAdmin, self).get_urls() + [
            url(
                r'info',
                self.admin_site.admin_view(self.info),
                name='core_school_info',
            ),
            url(
                r'sheets',
                self.admin_site.admin_view(self.sheets),
                name='core_school_sheets',
            ),
        ]


while current != None and not stop:
        if current.getData() > item:
            stop = True
        else:
            previous = current
            current = current.getNext()

def add(self,item):
    current = self.head
    previous = None
    stop = False

    temp = Node(item)
    if previous == None:
        temp.setNext(self.head)
        self.head = temp
    else:
        temp.setNext(current)
        previous.setNext(temp)


"""Helper class for streaming resumable uploads."""

import collections
import os

from gslib.exception import CommandException
from gslib.util import GetJsonResumableChunkSize

class ResumableStreamingJsonUploadWrapper(object):
  """Wraps an input stream in a buffer for resumable uploads.

  This class takes a non-seekable input stream, buffers it, and exposes it
  as a stream with limited seek capabilities such that it can be used in a
  resumable JSON API upload.

  max_buffer_size bytes of buffering is supported.
  """

  def __init__(self, stream, max_buffer_size, test_small_buffer=False):
    """Initializes the wrapper.

    Args:
      stream: Input stream.
      max_buffer_size: Maximum size of internal buffer; should be >= the chunk
          size of the resumable upload API to ensure that at least one full
          chunk write can be replayed in the event of a server error.
      test_small_buffer: Skip check for buffer size vs. chunk size, for testing.
    """
    self._orig_fp = stream

    if not test_small_buffer and max_buffer_size < GetJsonResumableChunkSize():
      raise CommandException('Resumable streaming upload created with buffer '
                             'size %s, JSON resumable upload chunk size %s. '
                             'Buffer size must be >= JSON resumable upload '
                             'chunk size to ensure that uploads can be '
                             'resumed.' % (max_buffer_size,
                                           GetJsonResumableChunkSize()))

    self._max_buffer_size = max_buffer_size
    self._buffer = collections.deque()
    self._buffer_start = 0
    self._buffer_end = 0
    self._position = 0

  def read(self, size=-1):  # pylint: disable=invalid-name
    """"Reads from the wrapped stream.

    Args:
      size: The amount of bytes to read. If omitted or negative, the entire
          contents of the stream will be read and returned.

    Returns:
      Bytes from the wrapped stream.
    """
    read_all_bytes = size is None or size < 0
    if read_all_bytes:
      bytes_remaining = self._max_buffer_size
    else:
      bytes_remaining = size
    data = None
    buffered_data = []
    if self._position < self._buffer_end:
      # There was a backwards seek, so read from the buffer first.

      # TODO: Performance test to validate if it is worth re-aligning
      # the buffers in this case.  Also, seeking through the buffer for
      # each read on a long catch-up is probably not performant, but we'd
      # need a more complex data structure than a deque to get around this.
      pos_in_buffer = self._buffer_start
      buffer_index = 0
      # First, find the start position in the buffer.
      while pos_in_buffer + len(self._buffer[buffer_index]) < self._position:
        # When this loop exits, buffer_index will refer to a buffer that
        # has at least some overlap with self._position, and
        # pos_in_buffer will be >= self._position
        pos_in_buffer += len(self._buffer[buffer_index])
        buffer_index += 1

      # Read until we've read enough or we're out of buffer.
      while pos_in_buffer < self._buffer_end and bytes_remaining > 0:
        buffer_len = len(self._buffer[buffer_index])
        # This describes how far into the current buffer self._position is.
        offset_from_position = self._position - pos_in_buffer
        bytes_available_this_buffer = buffer_len - offset_from_position
        read_size = min(bytes_available_this_buffer, bytes_remaining)
        buffered_data.append(
            self._buffer[buffer_index]
            [offset_from_position:offset_from_position + read_size])
        bytes_remaining -= read_size
        pos_in_buffer += buffer_len
        buffer_index += 1
        self._position += read_size

    # At this point we're guaranteed that self._position == self._buffer_end,
    # and can read from the wrapped stream if needed.
    if read_all_bytes:
      # TODO: The user is requesting reading until the end of an
      # arbitrary length stream, which is bad because they have basically
      # thrown away memory requirements since we'll need to return data
      # with no size limits. We could break this down into smaller reads and
      # buffer it as we go, but we're still left returning the data all at
      # once to the caller.  We could raise, but for now trust the caller to
      # be sane.
      new_data = self._orig_fp.read(size)
      data_len = len(new_data)
      if not buffered_data:
        data = new_data
      else:
        buffered_data.append(new_data)
        data = b''.join(buffered_data)
      self._position += data_len
    elif bytes_remaining:
      new_data = self._orig_fp.read(bytes_remaining)
      if not buffered_data:
        data = new_data
      else:
        buffered_data.append(new_data)
        data = b''.join(buffered_data)
      data_len = len(new_data)
      if data_len:
        self._position += data_len
        self._buffer.append(new_data)
        self._buffer_end += data_len
        oldest_data = None
        while self._buffer_end - self._buffer_start > self._max_buffer_size:
          oldest_data = self._buffer.popleft()
          self._buffer_start += len(oldest_data)
        if oldest_data:
          refill_amount = self._max_buffer_size - (self._buffer_end -
                                                   self._buffer_start)
          if refill_amount:
            self._buffer.append(oldest_data[-refill_amount:])
            self._buffer_start -= refill_amount
    else:
      data = b''.join(buffered_data) if buffered_data else None

    return data

  def tell(self):  # pylint: disable=invalid-name
    """Returns the current stream position."""
    return self._position

  def seekable(self):  # pylint: disable=invalid-name
    """Returns true since limited seek support exists."""

  def seek(self, offset, whence=os.SEEK_SET):  # pylint: disable=invalid-name
    """Seeks on the buffered stream.

    Args:
      offset: The offset to seek to; must be within the buffer bounds.
      whence: Must be os.SEEK_SET.

    Raises:
      CommandException if an unsupported seek mode or position is used.
    """
    if whence == os.SEEK_SET:
      if offset < self._buffer_start or offset > self._buffer_end:
        raise CommandException('Unable to resume upload because of limited '
                               'buffering available for streaming uploads. '
                               'Offset %s was requested, but only data from '
                               '%s to %s is buffered.' %
                               (offset, self._buffer_start, self._buffer_end))
      # Move to a position within the buffer.
      self._position = offset
    elif whence == os.SEEK_END:
      if offset > self._max_buffer_size:
        raise CommandException('Invalid SEEK_END offset %s on streaming '
                               'upload. Only %s can be buffered.' %
                               (offset, self._max_buffer_size))
      # Read to the end and rely on buffering to handle the offset.
      while self.read(self._max_buffer_size):
        pass
      # Now we're at the end.
      self._position -= offset
    else:
      raise CommandException('Invalid seek mode on streaming upload. '
                             '(mode %s, offset %s)' % (whence, offset))

  def close(self):  # pylint: disable=invalid-name
    return self._orig_fp.close()


while pos_in_buffer + len(self._buffer[buffer_index]) < self._position:
        # When this loop exits, buffer_index will refer to a buffer that
        # has at least some overlap with self._position, and
        # pos_in_buffer will be >= self._position
        pos_in_buffer += len(self._buffer[buffer_index])
        buffer_index += 1

"""Helper class for streaming resumable uploads."""

import collections
import os

from gslib.exception import CommandException
from gslib.util import GetJsonResumableChunkSize

class ResumableStreamingJsonUploadWrapper(object):
  """Wraps an input stream in a buffer for resumable uploads.

  This class takes a non-seekable input stream, buffers it, and exposes it
  as a stream with limited seek capabilities such that it can be used in a
  resumable JSON API upload.

  max_buffer_size bytes of buffering is supported.
  """

  def __init__(self, stream, max_buffer_size, test_small_buffer=False):
    """Initializes the wrapper.

    Args:
      stream: Input stream.
      max_buffer_size: Maximum size of internal buffer; should be >= the chunk
          size of the resumable upload API to ensure that at least one full
          chunk write can be replayed in the event of a server error.
      test_small_buffer: Skip check for buffer size vs. chunk size, for testing.
    """
    self._orig_fp = stream

    if not test_small_buffer and max_buffer_size < GetJsonResumableChunkSize():
      raise CommandException('Resumable streaming upload created with buffer '
                             'size %s, JSON resumable upload chunk size %s. '
                             'Buffer size must be >= JSON resumable upload '
                             'chunk size to ensure that uploads can be '
                             'resumed.' % (max_buffer_size,
                                           GetJsonResumableChunkSize()))

    self._max_buffer_size = max_buffer_size
    self._buffer = collections.deque()
    self._buffer_start = 0
    self._buffer_end = 0
    self._position = 0

  def read(self, size=-1):  # pylint: disable=invalid-name
    """"Reads from the wrapped stream.

    Args:
      size: The amount of bytes to read. If omitted or negative, the entire
          contents of the stream will be read and returned.

    Returns:
      Bytes from the wrapped stream.
    """
    read_all_bytes = size is None or size < 0
    if read_all_bytes:
      bytes_remaining = self._max_buffer_size
    else:
      bytes_remaining = size
    data = None
    buffered_data = []
    if self._position < self._buffer_end:
      # There was a backwards seek, so read from the buffer first.

      # TODO: Performance test to validate if it is worth re-aligning
      # the buffers in this case.  Also, seeking through the buffer for
      # each read on a long catch-up is probably not performant, but we'd
      # need a more complex data structure than a deque to get around this.
      pos_in_buffer = self._buffer_start
      buffer_index = 0
      # First, find the start position in the buffer.

      # Read until we've read enough or we're out of buffer.
      while pos_in_buffer < self._buffer_end and bytes_remaining > 0:
        buffer_len = len(self._buffer[buffer_index])
        # This describes how far into the current buffer self._position is.
        offset_from_position = self._position - pos_in_buffer
        bytes_available_this_buffer = buffer_len - offset_from_position
        read_size = min(bytes_available_this_buffer, bytes_remaining)
        buffered_data.append(
            self._buffer[buffer_index]
            [offset_from_position:offset_from_position + read_size])
        bytes_remaining -= read_size
        pos_in_buffer += buffer_len
        buffer_index += 1
        self._position += read_size

    # At this point we're guaranteed that self._position == self._buffer_end,
    # and can read from the wrapped stream if needed.
    if read_all_bytes:
      # TODO: The user is requesting reading until the end of an
      # arbitrary length stream, which is bad because they have basically
      # thrown away memory requirements since we'll need to return data
      # with no size limits. We could break this down into smaller reads and
      # buffer it as we go, but we're still left returning the data all at
      # once to the caller.  We could raise, but for now trust the caller to
      # be sane.
      new_data = self._orig_fp.read(size)
      data_len = len(new_data)
      if not buffered_data:
        data = new_data
      else:
        buffered_data.append(new_data)
        data = b''.join(buffered_data)
      self._position += data_len
    elif bytes_remaining:
      new_data = self._orig_fp.read(bytes_remaining)
      if not buffered_data:
        data = new_data
      else:
        buffered_data.append(new_data)
        data = b''.join(buffered_data)
      data_len = len(new_data)
      if data_len:
        self._position += data_len
        self._buffer.append(new_data)
        self._buffer_end += data_len
        oldest_data = None
        while self._buffer_end - self._buffer_start > self._max_buffer_size:
          oldest_data = self._buffer.popleft()
          self._buffer_start += len(oldest_data)
        if oldest_data:
          refill_amount = self._max_buffer_size - (self._buffer_end -
                                                   self._buffer_start)
          if refill_amount:
            self._buffer.append(oldest_data[-refill_amount:])
            self._buffer_start -= refill_amount
    else:
      data = b''.join(buffered_data) if buffered_data else None

    return data

  def tell(self):  # pylint: disable=invalid-name
    """Returns the current stream position."""
    return self._position

  def seekable(self):  # pylint: disable=invalid-name
    """Returns true since limited seek support exists."""
    return True

  def seek(self, offset, whence=os.SEEK_SET):  # pylint: disable=invalid-name
    """Seeks on the buffered stream.

    Args:
      offset: The offset to seek to; must be within the buffer bounds.
      whence: Must be os.SEEK_SET.

    Raises:
      CommandException if an unsupported seek mode or position is used.
    """
    if whence == os.SEEK_SET:
      if offset < self._buffer_start or offset > self._buffer_end:
        raise CommandException('Unable to resume upload because of limited '
                               'buffering available for streaming uploads. '
                               'Offset %s was requested, but only data from '
                               '%s to %s is buffered.' %
                               (offset, self._buffer_start, self._buffer_end))
      # Move to a position within the buffer.
      self._position = offset
    elif whence == os.SEEK_END:
      if offset > self._max_buffer_size:
        raise CommandException('Invalid SEEK_END offset %s on streaming '
                               'upload. Only %s can be buffered.' %
                               (offset, self._max_buffer_size))
      # Read to the end and rely on buffering to handle the offset.
      while self.read(self._max_buffer_size):
        pass
      # Now we're at the end.
      self._position -= offset
    else:
      raise CommandException('Invalid seek mode on streaming upload. '
                             '(mode %s, offset %s)' % (whence, offset))

  def close(self):  # pylint: disable=invalid-name
    return self._orig_fp.close()


    cache_name = kwargs.pop('cache_name', '')
    cache_only = kwargs.pop('cache_only', False)
    cache_refresh = kwargs.pop('cache_refresh', False)
    my_object = get_pickle(cache_name) if not cache_refresh else None
    if my_object:
        return my_object
    elif not cache_only:
        my_object = func(*args, **kwargs)
        return set_pickle(my_object, cache_name)

import re
import os
import json
import xbmcgui
import xbmcvfs
import unicodedata
from resources.lib.addon.timedate import get_timedelta, get_datetime_now
from resources.lib.addon.parser import try_int
from resources.lib.addon.plugin import ADDON, ADDONDATA, kodi_log
from resources.lib.addon.constants import ALPHANUM_CHARS, INVALID_FILECHARS
from resources.lib.addon.timedate import is_future_timestamp
try:
    import cPickle as _pickle
except ImportError:
    import pickle as _pickle  # Newer versions of Py3 just use pickle

def validify_filename(filename, alphanum=False):
    filename = unicodedata.normalize('NFD', filename)
    filename = u''.join([c for c in filename if (not alphanum or c in ALPHANUM_CHARS) and c not in INVALID_FILECHARS])
    return filename.strip('.')

def normalise_filesize(filesize):
    filesize = try_int(filesize)
    i_flt = 1024.0
    i_str = ['B', 'KB', 'MB', 'GB', 'TB']
    for i in i_str:
        if filesize < i_flt:
            return u'{:.2f} {}'.format(filesize, i)
        filesize = filesize / i_flt
    return u'{:.2f} {}'.format(filesize, 'PB')

def get_files_in_folder(folder, regex):
    return [x for x in xbmcvfs.listdir(folder)[1] if re.match(regex, x)]

def read_file(filepath):
    vfs_file = xbmcvfs.File(filepath)
    content = ''
    try:
        content = vfs_file.read()
    finally:
        vfs_file.close()
    return content

def write_to_file(filepath, content):
    f = xbmcvfs.File(filepath, 'w')
    f.write(content)
    f.close()

def get_tmdb_id_nfo(basedir, foldername, tmdb_type='tv'):
    try:
        folder = basedir + foldername + '/'

        # Get files ending with .nfo in folder
        nfo_list = get_files_in_folder(folder, regex=r".*\.nfo$")

        # Check our nfo files for TMDb ID
        for nfo in nfo_list:
            content = read_file(folder + nfo)  # Get contents of .nfo file
            tmdb_id = content.replace(u'https://www.themoviedb.org/{}/'.format(tmdb_type), '')  # Clean content to retrieve tmdb_id
            tmdb_id = tmdb_id.replace(u'&islocal=True', '')
            tmdb_id = try_int(tmdb_id)
            if tmdb_id:
                return u'{}'.format(tmdb_id)

    except Exception as exc:
        kodi_log(u'ERROR GETTING TMDBID FROM NFO:\n{}'.format(exc))

def get_file_path(folder, filename, join_addon_data=True):
    return os.path.join(get_write_path(folder, join_addon_data), filename)

def delete_file(folder, filename, join_addon_data=True):
    xbmcvfs.delete(get_file_path(folder, filename, join_addon_data))

def dumps_to_file(data, folder, filename, indent=2, join_addon_data=True):
    path = os.path.join(get_write_path(folder, join_addon_data), filename)
    with open(path, 'w') as file:
        json.dump(data, file, indent=indent)
    return path

def get_write_path(folder, join_addon_data=True):
    main_dir = os.path.join(xbmcvfs.translatePath(ADDONDATA), folder) if join_addon_data else xbmcvfs.translatePath(folder)
    if not os.path.exists(main_dir):
        try:  # Try makedir to avoid race conditions
            os.makedirs(main_dir)
        except FileExistsError:
            pass
    return main_dir

def _del_file(folder, filename):
    file = os.path.join(folder, filename)
    os.remove(file)

def del_old_files(folder, limit=1):
    folder = get_write_path(folder, True)
    for filename in sorted(os.listdir(folder))[:-limit]:
        _del_file(folder, filename)

def make_path(path, warn_dialog=False):
    if xbmcvfs.exists(path):
        return xbmcvfs.translatePath(path)
    if xbmcvfs.mkdirs(path):
        return xbmcvfs.translatePath(path)
    if ADDON.getSettingBool('ignore_folderchecking'):
        kodi_log(u'Ignored xbmcvfs folder check error\n{}'.format(path), 2)
        return xbmcvfs.translatePath(path)
    kodi_log(u'XBMCVFS unable to create path:\n{}'.format(path), 2)
    if not warn_dialog:
        return
    xbmcgui.Dialog().ok(
        'XBMCVFS', u'{} [B]{}[/B]\n{}'.format(
            ADDON.getLocalizedString(32122), path, ADDON.getLocalizedString(32123)))

def get_pickle_name(cache_name, alphanum=False):
    cache_name = cache_name or ''
    cache_name = cache_name.replace('\\', '_').replace('/', '_').replace('.', '_').replace('?', '_').replace('&', '_').replace('=', '_').replace('__', '_')
    return validify_filename(cache_name, alphanum=alphanum).rstrip('_')

def set_pickle(my_object, cache_name, cache_days=14, json_dump=False):
    if not my_object:
        return
    cache_name = get_pickle_name(cache_name)
    if not cache_name:
        return
    timestamp = get_datetime_now() + get_timedelta(days=cache_days)
    cache_obj = {'my_object': my_object, 'expires': timestamp.strftime("%Y-%m-%dT%H:%M:%S")}
    with open(os.path.join(get_write_path('pickle'), cache_name), 'w' if json_dump else 'wb') as file:
        json.dump(cache_obj, file, indent=4) if json_dump else _pickle.dump(cache_obj, file)
    return my_object

def get_pickle(cache_name, json_dump=False):
    cache_name = get_pickle_name(cache_name)
    if not cache_name:
        return
    try:
        with open(os.path.join(get_write_path('pickle'), cache_name), 'r' if json_dump else 'rb') as file:
            cache_obj = json.load(file) if json_dump else _pickle.load(file)
    except IOError:
        cache_obj = None
    if cache_obj and is_future_timestamp(cache_obj.get('expires', '')):
        return cache_obj.get('my_object')

def use_pickle(func, *args, **kwargs):
    """
    Simplecache takes func with args and kwargs
    Returns the cached item if it exists otherwise does the function
    """


        if isinstance(pattern, str):
            return pattern in (self.name, self.rule_id)
        return pattern.match(self.name) or pattern.match(self.rule_id)

"""
Every issue is reported as ``robocop.rules.Message`` object. It can be later printed or used by
post-run reports.

Output message format
---------------------

Output message of rules can be defined with ``-f`` / ``--format`` argument. Default value::

    "{source}:{line}:{col} [{severity}] {rule_id} {desc} ({name})"

Available formats:
  * ``source``:     path to the file where the issue occurred
  * ``source_rel``: path to the file where the issue occurred, relative to execution directory
  * ``line``:       line number where the issue starts
  * ``end_line``:   line number where the issue ends
  * ``col``:        column number where the issue starts
  * ``end_col``:    column number where the issue ends
  * ``severity``:   severity of the issue, value of ``robocop.rules.RuleSeverity`` enum
  * ``rule_id``:    rule id (e.g. 0501)
  * ``name``:       rule name (e.g. ``line-too-long`)
  * ``desc``:       description of the rule
"""
from enum import Enum
from functools import total_ordering
import robocop.exceptions

@total_ordering
class RuleSeverity(Enum):
    """
    Rule severity.
    It can be configured with ``--configure id_or_msg_name:severity:value``
    where value can be first letter of severity value or whole name, case-insensitive.
    For example ::

        -c line-too-long:severity:e

    will change `line-too-long` rule severity to error.

    You can filter out all rules below given severity value by using following option::

        -t/--threshold <severity value>

    Example::

        --threshold E

    will only report rules with severity E and above.
    """

    INFO = "I"
    WARNING = "W"
    ERROR = "E"

    def __lt__(self, other):
        look_up = [sev.value for sev in RuleSeverity]
        return look_up.index(self.value) < look_up.index(other.value)

class Rule:
    def __init__(self, rule_id, body):
        self.rule_id = rule_id
        self.name = ""
        self.desc = ""
        self.source = None
        self.enabled = True
        self.severity = RuleSeverity.INFO
        self.configurable = []
        self.parse_body(body)

    def __str__(self):
        return (
            f"Rule - {self.rule_id} [{self.severity.value}]: {self.name}: {self.desc} "
            f'({"enabled" if self.enabled else "disabled"})'
        )

    def change_severity(self, value):
        severity = {
            "error": "E",
            "e": "E",
            "warning": "W",
            "w": "W",
            "info": "I",
            "i": "I",
        }.get(str(value).lower(), None)
        if severity is None:
            raise robocop.exceptions.InvalidRuleSeverityError(self.name, value)
        self.severity = RuleSeverity(severity)

    def get_configurable(self, param):
        for configurable in self.configurable:
            if configurable[0] == param:
                return configurable
        return None

    @staticmethod
    def get_configurable_desc(conf, default=None):
        desc = f"{conf[0]} = {default}\n" f"        type: {conf[2].__name__}"
        if len(conf) == 4:
            desc += "\n" f"        info: {conf[3]}"
        return desc

    @staticmethod
    def get_default_value(param, checker):
        return None if checker is None else checker.__dict__.get(param, None)

    def available_configurables(self, include_severity=True, checker=None):
        configurables = ["severity"] if include_severity else []
        for conf in self.configurable:
            default = self.get_default_value(conf[1], checker)
            configurables.append(self.get_configurable_desc(conf, default))
        if not configurables:
            return ""
        return "\n    ".join(configurables)

    def parse_body(self, body):
        if isinstance(body, tuple) and len(body) >= 3:
            self.name, self.desc, self.severity, *self.configurable = body
        else:
            raise robocop.exceptions.InvalidRuleBodyError(self.rule_id, body)
        for configurable in self.configurable:
            if not isinstance(configurable, tuple) or len(configurable) not in (3, 4):
                raise robocop.exceptions.InvalidRuleConfigurableError(self.rule_id, body)

    def prepare_message(self, *args, source, node, lineno, col, end_lineno, end_col, ext_disablers):
        return Message(
            *args,
            rule=self,
            source=source,
            node=node,
            lineno=lineno,
            col=col,
            end_col=end_col,
            end_lineno=end_lineno,
            ext_disablers=ext_disablers,
        )

    def matches_pattern(self, pattern):
        """check if this rule matches given pattern"""

class Message:
    def __init__(
        self,
        *args,
        rule,
        source,
        node,
        lineno,
        col,
        end_lineno,
        end_col,
        ext_disablers=None,
    ):
        self.enabled = rule.enabled
        self.rule_id = rule.rule_id
        self.name = rule.name
        self.severity = rule.severity
        self.desc = rule.desc
        try:
            self.desc %= args
        except TypeError as err:
            raise robocop.exceptions.InvalidRuleUsageError(rule.rule_id, err)
        self.source = source
        self.line = 1
        if node is not None and node.lineno > -1:
            self.line = node.lineno
        if lineno is not None:
            self.line = lineno
        self.col = 1 if col is None else col
        self.end_line = self.line if end_lineno is None else end_lineno
        self.end_col = self.col if end_col is None else end_col
        self.ext_disablers = ext_disablers if ext_disablers else []

    def __lt__(self, other):
        return (self.line, self.col, self.rule_id) < (
            other.line,
            other.col,
            other.rule_id,
        )

    def get_fullname(self):
        return f"{self.severity.value}{self.rule_id} ({self.name})"

    def to_json(self):
        return {
            "source": self.source,
            "line": self.line,
            "column": self.col,
            "severity": self.severity.value,
            "rule_id": self.rule_id,
            "description": self.desc,
            "rule_name": self.name,
        }