"""
Class to interface with cisco ucm axl api.
Author: Jeff Levensailor
Version: 0.1
Dependencies:
 - zeep: https://python-zeep.readthedocs.io/en/master/

Links:
 - https://developer.cisco.com/site/axl/
"""

import sys
import pathlib
import os

from requests import Session 
from requests.auth import HTTPBasicAuth 
import re
import urllib3 
from zeep import Client, Settings, Plugin 
from zeep.transports import Transport 
from zeep.cache import SqliteCache 
from zeep.plugins import HistoryPlugin 
from zeep.exceptions import Fault 
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class axl(object):
    """
    The AXL class sets up the connection to the call manager with methods for configuring UCM.
    Tested with environment of;
    Python 3.6
    """

    def __init__(self, username, password, cucm, cucm_version):
        """
        :param username: axl username
        :param password: axl password
        :param cucm: UCM IP address
        :param cucm_version: UCM version

        example usage:
        >>> from axl import AXL
        >>> ucm = AXL('axl_user', 'axl_pass', '192.168.200.10')
        """

        cwd = os.path.dirname(os.path.abspath(__file__))
        wsdl = pathlib.PurePosixPath(f"{cwd}/schema/{cucm_version}/AXLAPI.wsdl").as_uri()
        session = Session() 
        session.verify = False 
        session.auth = HTTPBasicAuth(username, password) 
        settings = Settings(strict=False, xml_huge_tree=True) 
        transport = Transport(session=session, timeout=10, cache=SqliteCache()) 
        axl_client = Client(wsdl, settings=settings, transport=transport) 

        self.wsdl = wsdl
        self.username = username
        self.password = password
        self.wsdl = wsdl
        self.cucm = cucm
        self.cucm_version = cucm_version
        self.UUID_PATTERN = re.compile(r'^[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}$', re.IGNORECASE)
        self.client = axl_client.create_service("{http://www.cisco.com/AXLAPIService/}AXLAPIBinding", f"https://{cucm}:8443/axl/")

    def get_locations(self, mini=False):
        """
        Get location details
        :param mini: return a list of tuples of location details
        :return: A list of dictionary's
        """
        resp = self.client.listLocation(
                {'name': '%'}, returnedTags={
                    'name': '',
                    'withinAudioBandwidth': '',
                    'withinVideoBandwidth': '',
                    'withinImmersiveKbits': '',
                })[1]['return']['location']
        if mini:
            return [(i['name'],
                     i['withinAudioBandwidth'],
                     i['withinVideoBandwidth'],
                     i['withinImmersiveKbits'],
                     ) for i in resp]
        else:
            return resp

    def execute_sql_query(self, query):
        """
        Execute SQL query
        :param query: SQL Query to execute
        :return: result dictionary
        """
        resp = self.client.executeSQLQuery(query)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']
            return result
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def execute_sql_update(self, query):
        """
        Execute SQL update
        :param query: SQL Update to execute
        :return: result dictionary
        """
        resp = self.client.executeSQLUpdate(query)
        

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return'][0]
            return result
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def get_ldap_dir(self):
        """
        Get LDAP Syncs
        :return: result dictionary
        """
        resp = self.client.listLdapDirectory(
                {'name': '%'}, returnedTags={
                        'name': '',
                        'ldapDn': '',
                        'userSearchBase': '',
                })[1]['return']['ldapDirectory']
        return resp

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['row']
            return result
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result


    def do_ldap_sync(self, uuid):
        """
        Do LDAP Sync
        :param uuid: uuid
        :return: result dictionary
        """
        resp = self.client.doLdapSync(uuid=uuid, sync=True)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']
            return result
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def do_change_dnd_status(self, userID, status):
        """
        Do Change DND Status
        :param userID:
        :param status:
        :return: result dictionary
        """
        resp = self.client.doChangeDNDStatus(userID=userID, status=status)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['row']
            return result['response']
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def do_device_login(self, device, userId):
        """
        Do Device Login
        :param device:
        :param userId:
        :return: result dictionary
        """
        resp = self.client.doDeviceLogin(deviceName=device, userId=userId)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['row']
            return result['response']
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def do_device_logout(self, device, userId):
        """
        Do Device Logout
        :param device:
        :param userId:
        :return: result dictionary
        """
        resp = self.client.doDeviceLogout(deviceName=device, userId=userId)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['row']
            return result['response']
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result                    

    def do_device_reset(self, name='', uuid=''):
        """
        Do Device Reset
        :param name: device name
        :param uuid: device uuid
        :return: result dictionary
        """
        if name != '' and uuid == '':
            resp = self.client.doDeviceReset(deviceName=name, isHardReset=True)
        elif name == '' and uuid != '':
            resp = self.client.doDeviceReset(uuid=uuid, isHardReset=True)
            
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        print(resp[1]['return'])
        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']
            return result['response']
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def reset_sip_trunk(self, name='', uuid=''):
        """
        Reset SIP Trunk
        :param name: device name
        :param uuid: device uuid
        :return: result dictionary
        """
        if name != '' and uuid == '':
            resp = self.client.resetSipTrunk(name=name)
        elif name == '' and uuid != '':
            resp = self.client.resetSipTrunk(uuid=uuid)
            
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        print(resp[1]['return'])
        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']
            return result['response']
        elif resp[0] == 500 and 'syntax' in resp[1].faultstring:
            result['response'] = 'Syntax error'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def get_location(self, **args):
        """
        Get device pool parameters
        :param name: location name
        :param uuid: location uuid
        :return: result dictionary
        """
        resp = self.client.getLocation(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['location']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Location: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_location(self,
                     name,
                     kbits=512,
                     video_kbits=-1,
                     within_audio_bw=512,
                     within_video_bw=-1,
                     within_immersive_kbits=-1):

        """
        Add a location
        :param name: Name of the location to add
        :param cucm_version: ucm version
        :param kbits: ucm 8.5
        :param video_kbits: ucm 8.5
        :param within_audio_bw: ucm 10
        :param within_video_bw: ucm 10
        :param within_immersive_kbits: ucm 10
        :return: result dictionary
        """
        if int(self.cucm_version) >= 10:

            resp = self.client.addLocation({
                'name': name,
                # CUCM 10.6
                'withinAudioBandwidth': within_audio_bw,
                'withinVideoBandwidth': within_video_bw,
                'withinImmersiveKbits': within_immersive_kbits,
            })

        else:

            resp = self.client.addLocation({
                'name': name,
                # CUCM 8.6
                'kbits': kbits,
                'videoKbits': video_kbits,
            })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Location successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Location already exists'.format(name)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Location could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_location(self, **args):
        """
        Delete a location
        :param name: The name of the location to delete
        :param uuid: The uuid of the location to delete
        :return: result dictionary
        """
        resp = self.client.removeLocation(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Location successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Location: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Location could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_location(self, **args):
        """
        Update a Location
        :param name:
        :param uuid:
        :param newName:
        :param withinAudioBandwidth:
        :param withinVideoBandwidth:
        :param withImmersiveKbits:
        :param betweenLocations:
        :return:
        """
        
        resp = self.client.updateLocation(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Location successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**args) in resp[1].faultstring:
            result['response'] = 'Location: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Location could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def get_regions(self, mini=False):
        """
        Get region details
        :param mini: return a list of tuples of region details
        :return: A list of dictionary's
        """
        resp = self.client.listRegion(
                {'name': '%'}, returnedTags={'_uuid': '', 'name': ''})[1]['return']['region']
        if mini:
            return [(i['_uuid'][1:-1]) for i in resp]
        else:
            return resp

    def get_region(self, **args):
        """
        Get region information
        :param name: Region name
        :return: result dictionary
        """
        resp = self.client.getRegion(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['region']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Region: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_region(self, region):
        """
        Add a region
        :param region: Name of the region to add
        :return: result dictionary
        """
        resp = self.client.addRegion({
            'name': region
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Region successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Region already exists'.format(region)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Region could not be added'
            result['error'] = resp[1].faultstring
            return result

    def update_region(self, name='', uuid='', moh_region=''):
        """
        Update region and assign region to all other regions
        :param name:
        :param uuid:
        :param moh_region:
        :return:
        """
        # Get all Regions
        all_regions = self.client.listRegion({'name': '%'}, returnedTags={'name': ''})

        # Make list of region names
        region_names = [str(i['name']) for i in all_regions[1]['return']['region']]

        # Build list of dictionaries to add to region api call
        region_list = []

        for i in region_names:
            # Highest codec within a region
            if i == name:
                region_list.append({
                    'regionName': i,
                    'bandwidth': '256 kbps',
                    'videoBandwidth': '-1',
                    'immersiveVideoBandwidth': '-1',
                    'lossyNetwork': 'Use System Default',
                })

            # Music on hold region name
            elif i == moh_region:
                region_list.append({
                    'regionName': i,
                    'bandwidth': '64 kbps',
                    'videoBandwidth': '-1',
                    'immersiveVideoBandwidth': '-1',
                    'lossyNetwork': 'Use System Default',
                })

            # All else G.711
            else:
                region_list.append({
                    'regionName': i,
                    'bandwidth': '64 kbps',
                    'videoBandwidth': '-1',
                    'immersiveVideoBandwidth': '-1',
                    'lossyNetwork': 'Use System Default',
                })

        if name != '' and uuid == '':
            resp = self.client.updateRegion(name=name,
                                                    relatedRegions={'relatedRegion': region_list})
        elif name == '' and uuid != '':
            resp = self.client.updateRegion(uuid=uuid,
                                                    relatedRegions={'relatedRegion': region_list})            

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Region successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(name) in resp[1].faultstring:
            result['response'] = 'Region: {0} not found'.format(name)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Region could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def delete_region(self, **args):
        """
        Delete a location
        :param name: The name of the region to delete
        :param uuid: The uuid of the region to delete
        :return: result dictionary
        """
        resp = self.client.removeRegion(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Region successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Region: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Region could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def get_srsts(self, mini=False):
        """
        Get all SRST details
        :param mini: return a list of tuples of SRST details
        :return: A list of dictionary's
        """
        resp = self.client.listSrst(
                {'name': '%'}, returnedTags={'_uuid': ''})[1]['return']['srst']
        if mini:
            return [(i['_uuid'][1:-1]) for i in resp]
        else:
            return resp

    def get_srst(self, srst):
        """
        Get SRST information
        :param srst: SRST name
        :return: result dictionary
        """
        resp = self.client.getSrst(name=srst)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['srst']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'SRST: {0} not found'.format(srst)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_srst(self, srst, ip_address, port=2000, sip_port=5060):
        """
        Add SRST
        :param srst: SRST name
        :param ip_address: SRST ip address
        :param port: SRST port
        :param sip_port: SIP port
        :return: result dictionary
        """
        resp = self.client.addSrst({
            'name': srst,
            'port': port,
            'ipAddress': ip_address,
            'SipPort': sip_port,
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'SRST successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'SRST already exists'.format(srst)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'SRST could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_srst(self, srst):
        """
        Delete a SRST
        :param srst: The name of the SRST to delete
        :return: result dictionary
        """
        resp = self.client.removeSrst(name=srst)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'SRST successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'SRST: {0} not found'.format(srst)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'SRST could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def get_device_pools(self, mini=False):
        """
        Get a dictionary of device pools
        :param mini: return a list of tuples of device pool info
        :return: a list of dictionary's of device pools information
        """
        resp = self.client.listDevicePool(
                {'name': '%'}, returnedTags={
                    'name': '',
                    'dateTimeSettingName': '',
                    'callManagerGroupName': '',
                    'mediaResourceListName': '',
                    'regionName': '',
                    'srstName': '',
                    # 'localRouteGroup': [0],
                })[1]['return']['devicePool']
        if mini:
            return [(i['name'],
                     i['dateTimeSettingName']['value'],
                     i['callManagerGroupName']['value'],
                     i['regionName']['value'],
                     i['srstName']['value'],
                     # i['localRouteGroup'][0]['value'],
                     ) for i in resp]
        else:
            return resp

    def get_device_pool(self, **args):
        """
        Get device pool parameters
        :param device_pool: device pool name
        :return: result dictionary
        """
        resp = self.client.getDevicePool(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['devicePool']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Device pool: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_device_pool(self,
                        device_pool,
                        date_time_group='CMLocal',
                        region='Default',
                        location='',
                        route_group='',
                        media_resource_group_list='',
                        srst='Disable',
                        cm_group='Default',
                        network_locale=''):

        """
        Add a device pool
        :param device_pool: Device pool name
        :param date_time_group: Date time group name
        :param region: Region name
        :param location: Location name
        :param route_group: Route group name
        :param media_resource_group_list: Media resource group list name
        :param srst: SRST name
        :param cm_group: CM Group name
        :param network_locale: Network locale name
        :return: result dictionary
        """
        resp = self.client.addDevicePool({
            'name': device_pool,
            'dateTimeSettingName': date_time_group,  # update to state timezone
            'regionName': region,
            'locationName': location,
            'localRouteGroup': {'name': 'Standard Local Route Group', 'value': route_group},
            'mediaResourceListName': media_resource_group_list,
            'srstName': srst,
            'callManagerGroupName': cm_group,
            'networkLocale': network_locale,
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Device pool successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Device pool already exists'.format(srst)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Device pool could not be added'
            result['error'] = resp[1].faultstring
            return result

    def update_device_pool(self, **args):
        """
        Update a device pools route group and media resource group list
        :param name:
        :param uuid:
        :param newName:
        :param mediaResourceGroupListName:
        :param dateTimeSettingName:
        :param callManagerGroupName:
        :param regionName:
        :param locationName:
        :param networkLocale:
        :param srstName:
        :param localRouteGroup:
        :param elinGroup:
        :param media_resource_group_list:
        :return:
        """

        
        resp = self.client.updateDevicePool(**args)
        # print(resp)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Device pool successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**args) in resp[1].faultstring:
            result['response'] = 'Device pool: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**args) in resp[1].faultstring:
            result['response'] = 'Route group: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**args) in resp[1].faultstring:
            result['response'] = 'Media resource group list: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Device pool could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def delete_device_pool(self, **args):
        """
        Delete a Device pool
        :param device_pool: The name of the Device pool to delete
        :return: result dictionary
        """
        resp = self.client.removeDevicePool(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Device pool successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Device pool: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Device pool could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def get_conference_bridges(self, mini=False):
        """
        Get conference bridges
        :param mini: List of tuples of conference bridge details
        :return: results dictionary
        """
        resp = self.client.listConferenceBridge(
                {'name': '%'},
                returnedTags={'name': '',
                              'description': '',
                              'devicePoolName': '',
                              'locationName': ''})[1]['return']['conferenceBridge']

        if mini:
            return [(i['name'], i['description'], i['devicePoolName']['value'], i['locationName']['value'])
                    for i in resp]
        else:
            return resp

    def get_conference_bridge(self, conference_bridge):
        """
        Get conference bridge parameters
        :param conference_bridge: conference bridge name
        :return: result dictionary
        """
        resp = self.client.getConferenceBridge(name=conference_bridge)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['conferenceBridge']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Conference bridge: {0} not found'.format(conference_bridge)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_conference_bridge(self,
                              conference_bridge,
                              description='',
                              device_pool='Default',
                              location='Hub_None',
                              product='Cisco IOS Enhanced Conference Bridge',
                              security_profile='Non Secure Conference Bridge'):
        """
        Add a conference bridge
        :param conference_bridge: Conference bridge name
        :param description: Conference bridge description
        :param device_pool: Device pool name
        :param location: Location name
        :param product: Conference bridge type
        :param security_profile: Conference bridge security type
        :return: result dictionary
        """
        resp = self.client.addConferenceBridge({
            'name': conference_bridge,
            'description': description,
            'devicePoolName': device_pool,
            'locationName': location,
            'product': product,
            'securityProfileName': security_profile
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Conference bridge successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Conference bridge already exists'.format(conference_bridge)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Conference bridge could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_conference_bridge(self, conference_bridge):
        """
        Delete a Conference bridge
        :param conference_bridge: The name of the Conference bridge to delete
        :return: result dictionary
        """
        resp = self.client.removeConferenceBridge(name=conference_bridge)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Conference bridge successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Conference bridge: {0} not found'.format(conference_bridge)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Conference bridge could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def get_transcoders(self, mini=False):
        """
        Get transcoders
        :param mini: List of tuples of transcoder details
        :return: results dictionary
        """
        resp = self.client.listTranscoder(
                {'name': '%'},
                returnedTags={'name': '',
                              'description': '',
                              'devicePoolName': ''})[1]['return']['transcoder']

        if mini:
            return [(i['name'], i['description'], i['devicePoolName']['value']) for i in resp]
        else:
            return resp

    def get_transcoder(self, transcoder):
        """
        Get conference bridge parameters
        :param transcoder: conference bridge name
        :return: result dictionary
        """
        resp = self.client.getTranscoder(name=transcoder)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['transcoder']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Transcoder: {0} not found'.format(transcoder)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_transcoder(self,
                       transcoder,
                       description='',
                       device_pool='Default',
                       product='Cisco IOS Enhanced Media Termination Point'):
        """
        Add a transcoder
        :param transcoder: Transcoder name
        :param description: Transcoder description
        :param device_pool: Transcoder device pool
        :param product: Trancoder product
        :return: result dictionary
        """
        resp = self.client.addTranscoder({
            'name': transcoder,
            'description': description,
            'devicePoolName': device_pool,
            'product': product,
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Conference bridge successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Conference bridge already exists'.format(transcoder)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Conference bridge could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_transcoder(self, transcoder):
        """
        Delete a Transcoder
        :param transcoder: The name of the Transcoder to delete
        :return: result dictionary
        """
        resp = self.client.removeTranscoder(name=transcoder)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Transcoder successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Transcoder: {0} not found'.format(transcoder)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Transcoder could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def get_h323_gateways(self, mini=False):
        """
        Get H323 Gateways
        :param mini: List of tuples of H323 Gateway details
        :return: results dictionary
        """
        resp = self.client.listH323Gateway(
                {'name': '%'},
                returnedTags={'name': '',
                              'description': '',
                              'devicePoolName': '',
                              'locationName': '',
                              'sigDigits': ''})[1]['return']['h323Gateway']

        if mini:
            return [(i['name'],
                     i['description'],
                     i['devicePoolName']['value'],
                     i['locationName']['value'],
                     i['sigDigits']['value']) for i in resp]
        else:
            return resp

    def get_h323_gateway(self, h323_gateway):
        """
        Get H323 Gateway parameters
        :param h323_gateway: H323 Gateway name
        :return: result dictionary
        """
        resp = self.client.getH323Gateway(name=h323_gateway)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['h323Gateway']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'H323 Gateway: {0} not found'.format(h323_gateway)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_h323_gateway(self,
                         h323_gateway,
                         description='',
                         device_pool='Default',
                         location='Hub_None',
                         media_resource_group_list='',
                         prefix_dn='',
                         sig_digits='99',
                         css='',
                         aar_css='',
                         aar_neighborhood='',
                         product='H.323 Gateway',
                         protocol='H.225',
                         protocol_side='Network',
                         pstn_access='true',
                         redirect_in_num_ie='false',
                         redirect_out_num_ie='false',
                         cld_party_ie_num_type='Unknown',
                         clng_party_ie_num_type='Unknown',
                         clng_party_nat_pre='',
                         clng_party_inat_prefix='',
                         clng_party_unknown_prefix='',
                         clng_party_sub_prefix='',
                         clng_party_nat_strip_digits='',
                         clng_party_inat_strip_digits='',
                         clng_party_unknown_strip_digits='',
                         clng_party_sub_strip_digits='',
                         clng_party_nat_trans_css='',
                         clng_party_inat_trans_css='',
                         clng_party_unknown_trans_css='',
                         clng_party_sub_trans_css=''):
        """
        Add H323 gateway
        :param h323_gateway:
        :param description:
        :param device_pool:
        :param location:
        :param media_resource_group_list: Media resource group list name
        :param prefix_dn:
        :param sig_digits: Significant digits, 99 = ALL
        :param css:
        :param aar_css:
        :param aar_neighborhood:
        :param product:
        :param protocol:
        :param protocol_side:
        :param pstn_access:
        :param redirect_in_num_ie:
        :param redirect_out_num_ie:
        :param cld_party_ie_num_type:
        :param clng_party_ie_num_type:
        :param clng_party_nat_pre:
        :param clng_party_inat_prefix:
        :param clng_party_unknown_prefix:
        :param clng_party_sub_prefix:
        :param clng_party_nat_strip_digits:
        :param clng_party_inat_strip_digits:
        :param clng_party_unknown_strip_digits:
        :param clng_party_sub_strip_digits:
        :param clng_party_nat_trans_css:
        :param clng_party_inat_trans_css:
        :param clng_party_unknown_trans_css:
        :param clng_party_sub_trans_css:
        :return:
        """
        resp = self.client.addH323Gateway({
            'name': h323_gateway,
            'description': description,
            'product': product,
            'protocol': protocol,
            'protocolSide': protocol_side,
            'callingSearchSpaceName': css,
            'automatedAlternateRoutingCssName': aar_css,
            'devicePoolName': device_pool,
            'locationName': location,
            'mediaResourceListName': media_resource_group_list,
            'aarNeighborhoodName': aar_neighborhood,
            'pstnAccess': pstn_access,
            'sigDigits': sig_digits,
            'prefixDn': prefix_dn,
            'redirectInboundNumberIe': redirect_in_num_ie,
            'redirectOutboundNumberIe': redirect_out_num_ie,
            'calledPartyIeNumberType': cld_party_ie_num_type,
            'callingPartyIeNumberType': clng_party_ie_num_type,
            'callingPartyNationalPrefix': clng_party_nat_pre,
            'callingPartyInternationalPrefix': clng_party_inat_prefix,
            'callingPartyUnknownPrefix': clng_party_unknown_prefix,
            'callingPartySubscriberPrefix': clng_party_sub_prefix,
            'callingPartyNationalStripDigits': clng_party_nat_strip_digits,
            'callingPartyInternationalStripDigits': clng_party_inat_strip_digits,
            'callingPartyUnknownStripDigits': clng_party_unknown_strip_digits,
            'callingPartySubscriberStripDigits': clng_party_sub_strip_digits,
            'callingPartyNationalTransformationCssName': clng_party_nat_trans_css,
            'callingPartyInternationalTransformationCssName': clng_party_inat_trans_css,
            'callingPartyUnknownTransformationCssName': clng_party_unknown_trans_css,
            'callingPartySubscriberTransformationCssName': clng_party_sub_trans_css
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'H323 gateway successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'H323 gateway already exists'.format(h323_gateway)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'H323 gateway could not be added'
            result['error'] = resp[1].faultstring
            return result

    def update_h323_gateway_mrgl(self, h323_gateway, media_resource_group_list):
        """

        :param h323_gateway:
        :param media_resource_group_list:
        :return:
        """
        resp = self.client.updateH323Gateway(
                name=h323_gateway,
                mediaResourceListName=media_resource_group_list,
        )

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'H323 gateway successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(h323_gateway) in resp[1].faultstring:
            result['response'] = 'H323 gateway: {0} not found'.format(h323_gateway)
            result['error'] = resp[1].faultstring
            return result
        elif resp[0] == 500 and '{0} was not found'.format(media_resource_group_list) in resp[1].faultstring:
            result['response'] = 'Media resource group list: {0} not found'.format(media_resource_group_list)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'H323 gateway could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def delete_h323_gateway(self, h323_gateway):
        """
        Delete a H323 gateway
        :param h323_gateway: The name of the H323 gateway to delete
        :return: result dictionary
        """
        resp = self.client.removeH323Gateway(name=h323_gateway)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'H323 gateway successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'H323 gateway: {0} not found'.format(h323_gateway)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'H323 gateway could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def get_route_groups(self, mini=False):
        """
        Get route groups
        :param mini: return a list of tuples of route group details
        :return: A list of dictionary's
        """
        resp = self.client.listRouteGroup(
                {'name': '%'}, returnedTags={'name': '', 'distributionAlgorithm': ''})[1]['return']['routeGroup']
        if mini:
            return [(i['name'], i['distributionAlgorithm']) for i in resp]
        else:
            return resp

    def get_route_group(self, **args):
        """
        Get route group
        :param name: route group name
        :param uuid: route group uuid
        :return: result dictionary
        """
        resp = self.client.getRouteGroup(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['routeGroup']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route group: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_route_group(self,
                        name,
                        distribution_algorithm='Top Down',
                        members=[]):
        """
        Add a route group
        :param name: Route group name
        :param distribution_algorithm: Top Down/Circular
        :param members: A list of devices to add (must already exist DUH!)
        """
        req = {
            'name': name,
            'distributionAlgorithm': distribution_algorithm,
            'members': {'member': []},
        }

        if members:
            [req['members']['member'].append({
                'deviceName': i,
                'deviceSelectionOrder': members.index(i) + 1,
                'port': 0
            }) for i in members]

        resp = self.client.addRouteGroup(req)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Route group successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Route group already exists'.format(name)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Route group could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_route_group(self, **args):
        """
        Delete a Route group
        :param name: The name of the Route group to delete
        :return: result dictionary
        """
        resp = self.client.removeRouteGroup(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Route group successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route group: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Route group could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_route_group(self, **args):
        """
        Update a Route group
        :param name: The name of the Route group to update
        :param distribution_algorithm: Top Down/Circular
        :param members: A list of devices to add (must already exist DUH!)        
        :return: result dictionary
        """
        resp = self.client.updateRouteGroup(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Route group successfully updated'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route group: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Route group could not be updated'
            result['error'] = resp[1].faultstring
            return result


    def get_route_lists(self, mini=False):
        """
        Get route lists
        :param mini: return a list of tuples of route list details
        :return: A list of dictionary's
        """
        resp = self.client.listRouteList(
                {'name': '%'}, returnedTags={'name': '', 'description': ''})[1]['return']['routeList']
        if mini:
            return [(i['name'], i['description']) for i in resp]
        else:
            return resp

    def get_route_list(self, **args):
        """
        Get route list
        :param name: route list name
        :param uuid: route list uuid
        :return: result dictionary
        """
        resp = self.client.getRouteList(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['routeList']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route list: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_route_list(self,
                       name,
                       description='',
                       cm_group_name='Default',
                       route_list_enabled='true',
                       run_on_all_nodes='false',
                       members=[]):

        """
        Add a route list
        :param name: Route list name
        :param description: Route list description
        :param cm_group_name: Route list call mangaer group name
        :param route_list_enabled: Enable route list
        :param run_on_all_nodes: Run route list on all nodes
        :param members: A list of route groups
        :return: Result dictionary
        """
        req = {
            'name': name,
            'description': description,
            'callManagerGroupName': cm_group_name,
            'routeListEnabled': route_list_enabled,
            'runOnEveryNode': run_on_all_nodes,
            'members': {'member': []},
        }

        if members:
            [req['members']['member'].append({
                'routeGroupName': i,
                'selectionOrder': members.index(i) + 1,
                'calledPartyTransformationMask': '',
                'callingPartyTransformationMask': '',
                'digitDiscardInstructionName': '',
                'callingPartyPrefixDigits': '',
                'prefixDigitsOut': '',
                'useFullyQualifiedCallingPartyNumber': 'Default',
                'callingPartyNumberingPlan': 'Cisco CallManager',
                'callingPartyNumberType': 'Cisco CallManager',
                'calledPartyNumberingPlan': 'Cisco CallManager',
                'calledPartyNumberType': 'Cisco CallManager',
            }) for i in members]

        resp = self.client.addRouteList(req)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Route list successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Route list already exists'.format(name)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Route list could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_route_list(self, **args):
        """
        Delete a Route list
        :param name: The name of the Route list to delete
        :param uuid: The uuid of the Route list to delete
        :return: result dictionary
        """
        resp = self.client.removeRouteList(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Route list successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route list: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Route list could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_route_list(self, **args):
        """
        Update a Route list
        :param name: The name of the Route list to update
        :param uuid: The uuid of the Route list to update
        :param description: Route list description
        :param cm_group_name: Route list call mangaer group name
        :param route_list_enabled: Enable route list
        :param run_on_all_nodes: Run route list on all nodes
        :param members: A list of route groups
        :return: result dictionary
        """
        resp = self.client.updateRouteList(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Route list successfully updated'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route list: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Route list could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def get_partitions(self, mini=False):
        """
        Get partitions
        :param mini: return a list of tuples of partition details
        :return: A list of dictionary's
        """
        resp = self.client.listRoutePartition(
                {'name': '%'}, returnedTags={
                    'name': '', 'description': ''})[1]['return']['routePartition']
        if mini:
            return [(i['name'], i['description']) for i in resp]
        else:
            return resp

    def get_partition(self, **args):
        """
        Get partition details
        :param partition: Partition name
        :param uuid: UUID name
        :return: result dictionary
        """
        resp = self.client.getRoutePartition(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['routePartition']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route partition: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_partition(self,
                      name,
                      description='',
                      time_schedule_name='All the time'):
        """
        Add a partition
        :param name: Name of the partition to add
        :param description: Partition description
        :param time_schedule_name: Name of the time schedule to use
        :return: result dictionary
        """
        resp = self.client.addRoutePartition({
            'name': name,
            'description': description,
            'timeScheduleIdName': time_schedule_name,
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Partition successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Partition already exists'.format(name)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Partition could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_partition(self, **args):
        """
        Delete a partition
        :param partition: The name of the partition to delete
        :return: result dictionary
        """
        resp = self.client.removeRoutePartition(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Partition successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Partition:not found'
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Partition could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_partition(self, **args):
        """
        Update calling search space
        :param uuid: CSS UUID
        :param name: CSS Name
        :param description:
        :param newName:
        :param timeScheduleIdName:
        :param useOriginatingDeviceTimeZone:
        :param timeZone:
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        resp = self.client.updateRoutePartition(**args)
        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Partition successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**args) in resp[1].faultstring:
            result['response'] = 'Partition: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Partition could not be updated'
            result['error'] = resp[1].faultstring
            return result


    def get_calling_search_spaces(self, mini=False):
        """
        Get calling search spaces
        :param mini: return a list of tuples of css details
        :return: A list of dictionary's
        """
        resp = self.client.listCss(
                {'name': '%'}, returnedTags={
                    'name': '', 'description': ''})[1]['return']['css']
        if mini:
            return [(i['name'], i['description']) for i in resp]
        else:
            return resp

    def get_calling_search_space(self, **css):
        """
        Get Calling search space details
        :param name: Calling search space name
        :param uuid: Calling search space uuid
        :return: result dictionary
        """
        resp = self.client.getCss(**css)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['css']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Calling search space: {0} not found'.format(**css)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_calling_search_space(self,
                                 name,
                                 description='',
                                 members=[]):
        """
        Add a Calling search space
        :param name: Name of the CSS to add
        :param description: Calling search space description
        :param members: A list of partitions to add to the CSS
        :return: result dictionary
        """
        req = {
            'name': name,
            'description': description,
            'members': {'member': []},
        }

        if members:
            [req['members']['member'].append({
                'routePartitionName': i,
                'index': members.index(i) + 1,
            }) for i in members]

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        resp = self.client.addCss(req)

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Calling search space successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Calling search space already exists'.format(name)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Calling search space could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_calling_search_space(self, **args):
        """
        Delete a Calling search space
        :param calling_search_space: The name of the partition to delete
        :return: result dictionary
        """
        resp = self.client.removeCss(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Calling search space successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Calling search space: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Calling search space could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_calling_search_space(self, **args):
        """
        Update calling search space
        :param uuid: CSS UUID
        :param name: CSS Name
        :param description:
        :param newName:
        :param members:
        :param removeMembers:
        :param addMembers:
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        resp = self.client.updateCss(**args)
        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'CSS successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**args) in resp[1].faultstring:
            result['response'] = 'CSS: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'CSS could not be updated'
            result['error'] = resp[1].faultstring
            return result


    def get_route_patterns(self, mini=False):
        """
        Get route patterns
        :param mini: return a list of tuples of route pattern details
        :return: A list of dictionary's
        """
        resp = self.client.listRoutePattern(
                {'pattern': '%'}, returnedTags={
                    'pattern': '', 'description': '', '_uuid': ''})[1]['return']['routePattern']
        if mini:
            return [(i['pattern'], i['description'], i['_uuid'][1:-1]) for i in resp]
        else:
            return resp

    def get_route_pattern(self, pattern='', uuid=''):
        """
        Get route pattern
        :param pattern: route pattern
        :param uuid: route pattern uuid
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if uuid == '' and pattern != '':
        # Cant get pattern directly so get UUID first
            uuid = self.client.listRoutePattern({'pattern': pattern}, returnedTags={'_uuid': ''})

            if uuid[0] == 200 and uuid[1]['return'] == '':
                result['response'] = 'Route pattern: {0} not found'.format(pattern)
                result['error'] = 'Route pattern: {0} not found'.format(pattern)
                return result

            else:
                resp = self.client.getRoutePattern(uuid=uuid[1]['return']['routePattern'][0]['_uuid'][1:-1])

                if resp[0] == 200:
                    result['success'] = True
                    result['response'] = resp[1]['return']['routePattern']
                    return result
                elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
                    result['response'] = 'Route pattern: {0} not found'.format(pattern)
                    result['error'] = resp[1].faultstring
                    return result
                else:
                    result['response'] = 'Unknown error'
                    result['error'] = resp[1].faultstring
                    return result
        elif uuid != '' and pattern == '':
                resp = self.client.getRoutePattern(uuid=uuid)

                if resp[0] == 200:
                    result['success'] = True
                    result['response'] = resp[1]['return']['routePattern']
                    return result
                elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
                    result['response'] = 'Route pattern: {0} not found'.format(uuid)
                    result['error'] = resp[1].faultstring
                    return result
                else:
                    result['response'] = 'Unknown error'
                    result['error'] = resp[1].faultstring
                    return result           

    def add_route_pattern(self,
                          pattern,
                          gateway='',
                          route_list='',
                          description='',
                          partition='',
                          blockEnable=False,
                          patternUrgency=False,
                          releaseClause='Call Rejected'):
        """
        Add a route pattern
        :param pattern: Route pattern - required
        :param gateway: Destination gateway - required
        :param route_list: Destination route list - required
               Either a gateway or route list can be used at the same time
        :param description: Route pattern description
        :param partition: Route pattern partition
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        req = {
            'pattern': pattern,
            'description': description,
            'destination': {},
            'routePartitionName': partition,
            'blockEnable': blockEnable,
            'releaseClause': releaseClause
        }

        if gateway == '' and route_list == '':
            result['response'] = 'Either a gateway OR route list, is a required parameter'
            result['error'] = 'Enter either a gateway OR route list, not both'
            return result
        elif gateway != '' and route_list != '':
            result['response'] = 'Enter a gateway OR route list, not both'
            result['error'] = 'Destination can be a gateway OR route list, not both'
            return result
        elif gateway != '':
            req['destination'].update({'gatewayName': gateway})
            destination = gateway
        elif route_list != '':
            req['destination'].update({'routeListName': route_list})
            destination = route_list

        resp = self.client.addRoutePattern(req)

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Pattern successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Pattern already exists'.format(pattern)
            result['error'] = resp[1].faultstring
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Gateway or route list: {0} not found'.format(destination)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Pattern could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_route_pattern(self, **args):
        """
        Delete a route pattern
        :param uuid: The pattern uuid
        :param pattern: The pattern of the route to delete
        :param partition: The name of the partition
        :return: result dictionary
        """
        resp = self.client.removeRoutePattern(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Route Pattern successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route Pattern: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Route Pattern could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_route_pattern(self, **args):
        """
        Update a route pattern
        :param uuid: The pattern uuid
        :param pattern: The pattern of the route to update
        :param partition: The name of the partition
        :param gateway: Destination gateway - required
        :param route_list: Destination route list - required
               Either a gateway or route list can be used at the same time
        :param description: Route pattern description
        :param partition: Route pattern partition
        :return: result dictionary
        """
        resp = self.client.updateRoutePattern(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Route Pattern successfully updated'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Route Pattern: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Route Pattern could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def get_media_resource_groups(self, mini=False):
        """
        Get media resource groups
        :param mini: return a list of tuples of route pattern details
        :return: A list of dictionary's
        """
        resp = self.client.listMediaResourceGroup(
                {'name': '%'}, returnedTags={
                    'name': '', 'description': ''})[1]['return']['mediaResourceGroup']
        if mini:
            return [(i['name'], i['description']) for i in resp]
        else:
            return resp

    def get_media_resource_group(self, media_resource_group):
        """
        Get a media resource group details
        :param media_resource_group: Media resource group name
        :return: result dictionary
        """
        resp = self.client.getMediaResourceGroup(name=media_resource_group)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['mediaResourceGroup']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Media resource group: {0} not found'.format(media_resource_group)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_media_resource_group(self,
                                 media_resource_group,
                                 description='',
                                 multicast='false',
                                 members=[]):
        """
        Add a media resource group
        :param media_resource_group: Media resource group name
        :param description: Media resource description
        :param multicast: Mulicast enabled
        :param members: Media resource group members
        :return: result dictionary
        """
        req = {
            'name': media_resource_group,
            'description': description,
            'multicast': multicast,
            'members': {'member': []}
        }

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if members:
            [req['members']['member'].append({'deviceName': i}) for i in members]

        resp = self.client.addMediaResourceGroup(req)

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Media resource group successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Media resource group already exists'.format(media_resource_group)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Media resource group could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_media_resource_group(self, media_resource_group):
        """
        Delete a Media resource group
        :param media_resource_group: The name of the media resource group to delete
        :return: result dictionary
        """
        resp = self.client.removeMediaResourceGroup(name=media_resource_group)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Media resource group successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Media resource group: {0} not found'.format(media_resource_group)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Media resource group could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def get_media_resource_group_lists(self, mini=False):
        """
        Get media resource groups
        :param mini: return a list of tuples of route pattern details
        :return: A list of dictionary's
        """
        resp = self.client.listMediaResourceList(
                {'name': '%'}, returnedTags={
                    'name': ''})[1]['return']['mediaResourceList']
        if mini:
            return [i['name'] for i in resp]
        else:
            return resp

    def get_media_resource_group_list(self, media_resource_group_list):
        """
        Get a media resource group list details
        :param media_resource_group_list: Media resource group list name
        :return: result dictionary
        """
        resp = self.client.getMediaResourceList(name=media_resource_group_list)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['mediaResourceList']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Media resource group list: {0} not found'.format(media_resource_group_list)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_media_resource_group_list(self, media_resource_group_list, members=[]):
        """
        Add a media resource group list
        :param media_resource_group_list: Media resource group list name
        :param members: A list of members
        :return:
        """
        req = {
            'name': media_resource_group_list,
            'members': {'member': []}
        }

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if members:
            [req['members']['member'].append({'order': members.index(i),
                                              'mediaResourceGroupName': i}) for i in members]

        resp = self.client.addMediaResourceList(req)

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Media resource group list successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Media resource group list already exists'.format(media_resource_group_list)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Media resource group list could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_media_resource_group_list(self, media_resource_group_list):
        """
        Delete a Media resource group list
        :param media_resource_group_list: The name of the media resource group list to delete
        :return: result dictionary
        """
        resp = self.client.removeMediaResourceList(name=media_resource_group_list)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Media resource group list successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Media resource group list: {0} not found'.format(media_resource_group_list)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Media resource group list could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def get_directory_numbers(self, mini=False):
        """
        Get directory numbers
        :param mini: return a list of tuples of directory number details
        :return: A list of dictionary's
        """
        resp = self.client.listLine(
                {'pattern': '%'}, returnedTags={
                    'pattern': '', 'description': '', 'routePartitionName': ''})[1]['return']['line']
        if mini:
            return [(i['pattern'], i['description'], i['routePartitionName']) for i in resp]
        else:
            return resp

    def get_directory_number(self, **args):
        """
        Get directory number details
        :param name:
        :param partition:
        :return: result dictionary
        """
        resp = self.client.getLine(**args)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['line']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Directory Number: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_directory_number(self,
                             pattern,
                             partition='',
                             description='',
                             alerting_name='',
                             ascii_alerting_name='',
                             shared_line_css='',
                             aar_neighbourhood='',
                             call_forward_css='',
                             vm_profile_name='NoVoiceMail',
                             aar_destination_mask='',
                             call_forward_destination='',
                             forward_all_to_vm='false',
                             forward_all_destination='',
                             forward_to_vm='false'):
        """
        Add a directory number
        :param pattern: Directory number
        :param partition: Route partition name
        :param description: Directory number description
        :param alerting_name: Alerting name
        :param ascii_alerting_name: ASCII alerting name
        :param shared_line_css: Calling search space
        :param aar_neighbourhood: AAR group
        :param call_forward_css: Call forward calling search space
        :param vm_profile_name: Voice mail profile
        :param aar_destination_mask: AAR destination mask
        :param call_forward_destination: Call forward destination
        :param forward_all_to_vm: Forward all to voice mail checkbox
        :param forward_all_destination: Forward all destination
        :param forward_to_vm: Forward to voice mail checkbox
        :return: result dictionary
        """

        resp = self.client.addLine({
            'pattern': pattern,
            'routePartitionName': partition,
            'description': description,
            'alertingName': alerting_name,
            'asciiAlertingName': ascii_alerting_name,
            'voiceMailProfileName': vm_profile_name,
            'shareLineAppearanceCssName': shared_line_css,
            'aarNeighborhoodName': aar_neighbourhood,
            'aarDestinationMask': aar_destination_mask,
            'callForwardAll': {
                'forwardToVoiceMail': forward_all_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': forward_all_destination,
            },
            'callForwardBusy': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
            'callForwardBusyInt': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
            'callForwardNoAnswer': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
            'callForwardNoAnswerInt': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
            'callForwardNoCoverage': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
            'callForwardNoCoverageInt': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
            'callForwardOnFailure': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
            'callForwardNotRegistered': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
            'callForwardNotRegisteredInt': {
                'forwardToVoiceMail': forward_to_vm,
                'callingSearchSpaceName': call_forward_css,
                'destination': call_forward_destination,
            },
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Directory number successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Directory number already exists'.format(pattern)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Directory number could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_directory_number(self, uuid):
        """
        Delete a directory number
        :param directory_number: The name of the directory number to delete
        :return: result dictionary
        """
        resp = self.client.removeLine(uuid=uuid)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Directory number successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Directory number: {0} not found'.format(uuid)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Directory number could not be deleted'
            result['error'] = resp[1].faultstring
            return result


    def update_directory_number(self, **args):
        """
        Update a directory number
        :param pattern: Directory number
        :param partition: Route partition name
        :param description: Directory number description
        :param alerting_name: Alerting name
        :param ascii_alerting_name: ASCII alerting name
        :param shared_line_css: Calling search space
        :param aar_neighbourhood: AAR group
        :param call_forward_css: Call forward calling search space
        :param vm_profile_name: Voice mail profile
        :param aar_destination_mask: AAR destination mask
        :param call_forward_destination: Call forward destination
        :param forward_all_to_vm: Forward all to voice mail checkbox
        :param forward_all_destination: Forward all destination
        :param forward_to_vm: Forward to voice mail checkbox
        :return: result dictionary
        """
        resp = self.client.updateLine(**args)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Directory number successfully updated'
            return result
        else:
            result['response'] = 'Directory number could not be added'
            result['error'] = resp[1].faultstring
            return result


    def get_cti_route_points(self, mini=False):
        """
        Get CTI route points
        :param mini: return a list of tuples of CTI route point details
        :return: A list of dictionary's
        """
        resp = self.client.listCtiRoutePoint(
                {'name': '%'}, returnedTags={
                    'name': '', 'description': ''})[1]['return']['ctiRoutePoint']
        if mini:
            return [(i['name'], i['description']) for i in resp]
        else:
            return resp

    def get_cti_route_point(self, **args):
        """
        Get CTI route point details
        :param name: CTI route point name
        :param uuid: CTI route point uuid
        :return: result dictionary
        """
        resp = self.client.getCtiRoutePoint(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['ctiRoutePoint']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'CTI route point: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_cti_route_point(self,
                            name,
                            description='',
                            device_pool='Default',
                            location='Hub_None',
                            common_device_config='',
                            css='',
                            product='CTI Route Point',
                            dev_class='CTI Route Point',
                            protocol='SCCP',
                            protocol_slide='User',
                            use_trusted_relay_point='Default',
                            lines=[]):
        """
        Add CTI route point
        lines should be a list of tuples containing the pattern and partition
        EG: [('77777', 'AU_PHONE_PT')]
        :param name: CTI route point name
        :param description: CTI route point description
        :param device_pool: Device pool name
        :param location: Location name
        :param common_device_config: Common device config name
        :param css: Calling search space name
        :param product: CTI device type
        :param dev_class: CTI device type
        :param protocol: CTI protocol
        :param protocol_slide: CTI protocol slide
        :param use_trusted_relay_point: Use trusted relay point: (Default, On, Off)
        :param lines: A list of tuples of [(directory_number, partition)]
        :return:
        """

        req = {
            'name': name,
            'description': description,
            'product': product,
            'class': dev_class,
            'protocol': protocol,
            'protocolSide': protocol_slide,
            'commonDeviceConfigName': common_device_config,
            'callingSearchSpaceName': css,
            'devicePoolName': device_pool,
            'locationName': location,
            'useTrustedRelayPoint': use_trusted_relay_point,
            'lines': {'line': []}
        }

        if lines:
            [req['lines']['line'].append({
                'index': lines.index(i) + 1,
                'dirn': {
                    'pattern': i[0], 
                    'routePartitionName': i[1]}
            }) for i in lines]

        resp = self.client.addCtiRoutePoint(req)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'CTI route point successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'CTI route point already exists'.format(name)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'CTI route point could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_cti_route_point(self, **args):
        """
        Delete a CTI route point
        :param cti_route_point: The name of the CTI route point to delete
        :return: result dictionary
        """
        resp = self.client.removeCtiRoutePoint(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'CTI route point successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'CTI route point: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'CTI route point could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_cti_route_point(self, **args):
        """
        Add CTI route point
        lines should be a list of tuples containing the pattern and partition
        EG: [('77777', 'AU_PHONE_PT')]
        :param name: CTI route point name
        :param description: CTI route point description
        :param device_pool: Device pool name
        :param location: Location name
        :param common_device_config: Common device config name
        :param css: Calling search space name
        :param product: CTI device type
        :param dev_class: CTI device type
        :param protocol: CTI protocol
        :param protocol_slide: CTI protocol slide
        :param use_trusted_relay_point: Use trusted relay point: (Default, On, Off)
        :param lines: A list of tuples of [(directory_number, partition)]
        :return:
        """

        resp = self.client.updateCtiRoutePoint(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'CTI route point successfully updated'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'CTI route point already updated'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'CTI route point could not be updated'
            result['error'] = resp[1].faultstring
            return result


    def get_phones(self, mini=False, first=1000, skip=0):
        """
        Get phone details
        :param mini: return a list of tuples of phone details
        :return: A list of dictionaries. If > 1000 records are returned, a list of list of dictionaries will be returned
        """
        paginated = []
        resp = self.client.listPhone(
                {'name': '%'}, returnedTags={
                    'name': '',
                    'product': '',
                    'protocol': '',
                    'locationName': '',
                }, first=first, skip=skip)[1]['return']['phone']
        if len(resp) >= 1000:
            skip=first+skip
            first+=first
            paginated.append(resp)
            self.get_phones(first=first, skip=skip)
        if mini:
            return [(i['name'],
                     i['product'],
                     i['protocol'],
                     i['locationName']['value'],
                     ) for i in resp]
        else:
            if first > 1000:
                return paginated
            else:
                return resp

    def get_phone(self, **args):
        """
        Get device profile parameters
        :param phone: profile name
        :return: result dictionary
        """
        resp = self.client.getPhone(**args)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['phone']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Phone: {0} not found'.format('phone')
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_phone(self,
                  name,
                  description='',
                  product='Cisco 7941',
                  device_pool='Default',
                  location='Hub_None',
                  phone_template='Standard 8861 SIP',
                  common_device_config='',
                  css='',
                  aar_css='',
                  subscribe_css='',
                  securityProfileName='',
                  lines=[],
                  dev_class='Phone',
                  protocol='SCCP',
                  softkey_template='Standard User',
                  enable_em='true',
                  em_service_name='Extension Mobility',
                  em_service_url=False,
                  em_url_button_enable=False,
                  em_url_button_index='1',
                  em_url_label='Press here to logon',
                  ehook_enable=1):
        """
        lines takes a list of Tuples with properties for each line EG:

                                               display                           external
            DN     partition    display        ascii          label               mask
        [('77777', 'LINE_PT', 'Jim Smith', 'Jim Smith', 'Jim Smith - 77777', '0294127777')]
        Add A phone
        :param name:
        :param description:
        :param product:
        :param device_pool:
        :param location:
        :param phone_template:
        :param common_device_config:
        :param css:
        :param aar_css:
        :param subscribe_css:
        :param lines:
        :param dev_class:
        :param protocol:
        :param softkey_template:
        :param enable_em:
        :param em_service_name:
        :param em_service_url:
        :param em_url_button_enable:
        :param em_url_button_index:
        :param em_url_label:
        :param ehook_enable:
        :return:
        """

        req = {
            'name': name,
            'description': description,
            'product': product,
            'class': dev_class,
            'protocol': protocol,
            'commonDeviceConfigName': common_device_config,
            'softkeyTemplateName': softkey_template,
            'phoneTemplateName': phone_template,
            'devicePoolName': device_pool,
            'locationName': location,
            'enableExtensionMobility': enable_em,
            'callingSearchSpaceName': css,
            'automatedAlternateRoutingCssName': aar_css,
            'subscribeCallingSearchSpaceName': subscribe_css,
            'lines': {'line': []},
            'services': {'service': []},
            'vendorConfig': [{
                'ehookEnable': ehook_enable
            }]
        }

        if lines:
            [req['lines']['line'].append({
                'index': lines.index(i) + 1,
                'dirn': {
                    'pattern': i[0],
                    'routePartitionName': i[1]
                },
                'display': i[2],
                'displayAscii': i[3],
                'label': i[4],
                'e164Mask': i[5]
            }) for i in lines]

        if em_service_url:
            req['services']['service'].append([{
                'telecasterServiceName': em_service_name,
                'name': em_service_name,
                'url': 'http://{0}:8080/emapp/EMAppServlet?device=#DEVICENAME#&EMCC=#EMCC#'.format(self.cucm),
            }])

        if em_url_button_enable:
            req['services']['service'][0].update({'urlButtonIndex': em_url_button_index, 'urlLabel': em_url_label})

        resp = self.client.addPhone(req)
        print(resp)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Phone successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Phone already exists'.format(name)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Phone could not be added'
            result['error'] = resp[1].faultstring
            return result

    def add_phone_api(self, **args):
        print(args)
        """
        lines takes a list of Tuples with properties for each line EG:

                                               display                           external
            DN     partition    display        ascii          label               mask
        [('77777', 'LINE_PT', 'Jim Smith', 'Jim Smith', 'Jim Smith - 77777', '0294127777')]
        Add A phone
        :param name:
        :param description:
        :param product:
        :param device_pool:
        :param location:
        :param phone_template:
        :param common_device_config:
        :param css:
        :param aar_css:
        :param subscribe_css:
        :param lines:
        :param dev_class:
        :param protocol:
        :param softkey_template:
        :param enable_em:
        :param em_service_name:
        :param em_service_url:
        :param em_url_button_enable:
        :param em_url_button_index:
        :param em_url_label:
        :param ehook_enable:
        :return:
        """
        resp = self.client.addPhone(**args)
        print(resp)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Phone successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Phone already exists'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Phone could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_phone(self, **args):
        """
        Delete a phone
        :param phone: The name of the phone to delete
        :return: result dictionary
        """
        resp = self.client.removePhone(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Phone successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Phone: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Phone could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_phone(self, **args):
                  
        """
        lines takes a list of Tuples with properties for each line EG:

                                               display                           external
            DN     partition    display        ascii          label               mask
        [('77777', 'LINE_PT', 'Jim Smith', 'Jim Smith', 'Jim Smith - 77777', '0294127777')]
        Add A phone
        :param name:
        :param description:
        :param product:
        :param device_pool:
        :param location:
        :param phone_template:
        :param common_device_config:
        :param css:
        :param aar_css:
        :param subscribe_css:
        :param lines:
        :param dev_class:
        :param protocol:
        :param softkey_template:
        :param enable_em:
        :param em_service_name:
        :param em_service_url:
        :param em_url_button_enable:
        :param em_url_button_index:
        :param em_url_label:
        :param ehook_enable:
        :return:
        """

        resp = self.client.updatePhone(**args)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Phone successfully updated'
            return result
        else:
            result['response'] = 'Phone could not be updated'
            result['error'] = resp[1].faultstring
            return result



    def get_device_profiles(self, mini=False):
        """
        Get device profile details
        :param mini: return a list of tuples of device profile details
        :return: A list of dictionary's
        """
        resp = self.client.listDeviceProfile(
                {'name': '%'}, returnedTags={
                    'name': '',
                    'product': '',
                    'protocol': '',
                    'phoneTemplateName': '',
                })[1]['return']['deviceProfile']
        if mini:
            return [(i['name'],
                     i['product'],
                     i['protocol'],
                     i['phoneTemplateName']['value'],
                     ) for i in resp]
        else:
            return resp

    def get_device_profile(self, **args):
        """
        Get device profile parameters
        :param name: profile name
        :param uuid: profile uuid
        :return: result dictionary
        """
        resp = self.client.getDeviceProfile(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['deviceProfile']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Profile: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_device_profile(self,
                           profile,
                           description='',
                           product='Cisco 7962',
                           phone_template='Standard 7962G SCCP',
                           dev_class='Device Profile',
                           protocol='SCCP',
                           softkey_template='Standard User',
                           em_service_name='Extension Mobility',
                           lines=[]):
        """
        Add A Device profile for use with extension mobility
        lines takes a list of Tuples with properties for each line EG:

                                               display                           external
            DN     partition    display        ascii          label               mask
        [('77777', 'LINE_PT', 'Jim Smith', 'Jim Smith', 'Jim Smith - 77777', '0294127777')]
        :param profile:
        :param description:
        :param product:
        :param phone_template:
        :param lines:
        :param dev_class:
        :param protocol:
        :param softkey_template:
        :param em_service_name:
        :return:
        """

        req = {
            'name': profile,
            'description': description,
            'product': product,
            'class': dev_class,
            'protocol': protocol,
            'softkeyTemplateName': softkey_template,
            'phoneTemplateName': phone_template,
            'lines': {'line': []},
            'services': {'service': [{
                'telecasterServiceName': em_service_name,
                'name': em_service_name,
                'url': 'http://{0}:8080/emapp/EMAppServlet?device=#DEVICENAME#&EMCC=#EMCC#'.format(self.cucm),
            }]},
        }

        if lines:
            [req['lines']['line'].append({
                'index': lines.index(i) + 1,
                'dirn': {
                    'pattern': i[0],
                    'routePartitionName': i[1]
                },
                'display': i[2],
                'displayAscii': i[3],
                'label': i[4],
                'e164Mask': i[5]
            }) for i in lines]

        resp = self.client.addDeviceProfile(req)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Device profile successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Device profile already exists'.format(profile)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Device profile could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_device_profile(self, **args):
        """
        Delete a device profile
        :param profile: The name of the device profile to delete
        :return: result dictionary
        """
        resp = self.client.removeDeviceProfile(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Device profile successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Device profile: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Device profile could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_device_profile(self,**args):
        """
        Update A Device profile for use with extension mobility
        lines takes a list of Tuples with properties for each line EG:

                                               display                           external
            DN     partition    display        ascii          label               mask
        [('77777', 'LINE_PT', 'Jim Smith', 'Jim Smith', 'Jim Smith - 77777', '0294127777')]
        :param profile:
        :param description:
        :param product:
        :param phone_template:
        :param lines:
        :param dev_class:
        :param protocol:
        :param softkey_template:
        :param em_service_name:
        :return:
        """

        resp = self.client.updateDeviceProfile(**args)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Device profile successfully updated'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Device profile already exists'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Device profile could not be updated'
            result['error'] = resp[1].faultstring
            return result


    def get_users(self, mini=False):
        """
        Get users details
        :param mini: return a list of tuples of user details
        :return: A list of dictionary's
        """
        resp = self.client.listUser(
                {'userid': '%'}, returnedTags={
                    'userid': '',
                    'firstName': '',
                    'lastName': '',
                })[1]['return']['user']
        if mini:
            return [(i['userid'],
                     i['firstName'],
                     i['lastName'],
                     ) for i in resp]
        else:
            return resp

    def get_user(self, user_id):
        """
        Get user parameters
        :param user_id: profile name
        :return: result dictionary
        """
        resp = self.client.getUser(userid=user_id)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['user']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'User: {0} not found'.format(user_id)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_user(self,
                 user_id,
                 last_name,
                 first_name='',
                 user_profile='Standard (Factory Default) User Profile'):
        """
        Add a user
        :param user_id: User ID of the user to add
        :param first_name: First name of the user to add
        :param last_name: Last name of the user to add
        :param user_profile: User profile template
        :return: result dictionary
        """
        resp = self.client.addUser({
            'userid': user_id,
            'firstName': first_name,
            'lastName': last_name,
            'userProfile': user_profile,
        })

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'User successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'User already exists'.format(user_id)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'User could not be added'
            result['error'] = resp[1].faultstring
            return result

    def update_user(self, **args):
        """
        Update end user for credentials
        :param user_id: User ID
        :param password: Web interface password
        :param pin: Extension mobility PIN
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        resp = self.client.updateUser(**args)
        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'User successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**args) in resp[1].faultstring:
            result['response'] = 'User ID: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'User could not be updated'
            result['error'] = resp[1].faultstring
            return result


    def update_user_em(self,
                       user_id,
                       device_profile,
                       default_profile,
                       subscribe_css,
                       primary_extension):
        """
        Update end user for extension mobility
        :param user_id: User ID
        :param device_profile: Device profile name
        :param default_profile: Default profile name
        :param subscribe_css: Subscribe CSS
        :param primary_extension: Primary extension, must be a number from the device profile
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        resp = self.client.getDeviceProfile(name=device_profile)

        if resp[0] == 500 and '{0} was not found'.format(device_profile) in resp[1].faultstring:
            result['response'] = 'Device profile: {0} not found'.format(device_profile)
            result['error'] = resp[1].faultstring
            return result

        else:
            uuid = resp[1]['return']['deviceProfile']['_uuid'][1:-1]

        resp = self.client.updateUser(
                userid=user_id,
                phoneProfiles={'profileName': {'_uuid': uuid}},
                defaultProfile=default_profile,
                subscribeCallingSearchSpaceName=subscribe_css,
                primaryExtension={'pattern': primary_extension},
                associatedGroups={'userGroup': {'name': 'Standard CCM End Users'}}
        )

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'User successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(user_id) in resp[1].faultstring:
            result['response'] = 'User ID: {0} not found'.format(user_id)
            result['error'] = resp[1].faultstring
            return result
        elif resp[0] == 500 and '{0} was not found'.format(default_profile) in resp[1].faultstring:
            result['response'] = 'Default profile: {0} not found'.format(default_profile)
            result['error'] = resp[1].faultstring
            return result
        elif resp[0] == 500 and '{0} was not found'.format(subscribe_css) in resp[1].faultstring:
            result['response'] = 'Subscribe CSS: {0} not found'.format(subscribe_css)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'User could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def update_user_credentials(self,
                                user_id,
                                password='',
                                pin=''):
        """
        Update end user for credentials
        :param user_id: User ID
        :param password: Web interface password
        :param pin: Extension mobility PIN
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if password == '' and pin == '':
            result['response'] = 'User could not be updated'
            result['error'] = 'Password and/or Pin are required'
            return result

        elif password != '' and pin != '':
            resp = self.client.updateUser(
                    userid=user_id,
                    password=password,
                    pin=pin,
            )

        elif password != '':
            resp = self.client.updateUser(
                    userid=user_id,
                    password=password,
            )

        elif pin != '':
            resp = self.client.updateUser(
                    userid=user_id,
                    pin=pin,
            )

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'User successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(user_id) in resp[1].faultstring:
            result['response'] = 'User ID: {0} not found'.format(user_id)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'User could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def delete_user(self, **args):
        """
        Delete a user
        :param user_id: The name of the user to delete
        :return: result dictionary
        """
        resp = self.client.removeUser(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'User successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'User: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'User could not be deleted'
            result['error'] = resp[1].faultstring
            return result


    def get_translations(self, mini=False):
        """
        Get translation patterns
        :param mini: return a list of tuples of route pattern details
        :return: A list of dictionary's
        """
        resp = self.client.listTransPattern(
                {'pattern': '%'}, returnedTags={
                    'pattern': '', 
                    'description': '', 
                    '_uuid': '', 
                    'routePartitionName': '',
                    'callingSearchSpaceName': '',
                    'useCallingPartyPhoneMask': '',
                    'patternUrgency': '',
                    'provideOutsideDialtone': '',
                    'prefixDigitsOut': '',
                    'calledPartyTransformationMask': '',
                    'callingPartyTransformationMask': '',
                    'digitDiscardInstructionName': '',
                    'callingPartyPrefixDigits': '',
                    'provideOutsideDialtone': '' })[1]['return']['transPattern']
        if mini:
            return [(i['pattern'], i['description'], i['_uuid'][1:-1]) for i in resp]
        else:
            return resp

    def get_translation(self, pattern='', partition='', uuid=''):
        """
        Get translation pattern
        :param pattern: translation pattern to match
        :param partition: partition required if searching pattern
        :param uuid: translation pattern uuid
        :return: result dictionary
        """

        if pattern != '' and partition != '' and uuid == '':
            resp = self.client.getTransPattern(
                pattern=pattern, 
                routePartitionName=partition,
                returnedTags={
                'pattern': '', 
                'description': '', 
                'routePartitionName': '',
                'callingSearchSpaceName': '',
                'useCallingPartyPhoneMask': '',
                'patternUrgency': '',
                'provideOutsideDialtone': '',
                'prefixDigitsOut': '',
                'calledPartyTransformationMask': '',
                'callingPartyTransformationMask': '',
                'digitDiscardInstructionName': '',
                'callingPartyPrefixDigits': ''})
        elif uuid != '' and pattern == '' and partition == '':
            resp = self.client.getTransPattern(
            uuid=uuid, returnedTags={
                'pattern': '', 
                'description': '', 
                'routePartitionName': '',
                'callingSearchSpaceName': '',
                'useCallingPartyPhoneMask': '',
                'patternUrgency': '',
                'provideOutsideDialtone': '',
                'prefixDigitsOut': '',
                'calledPartyTransformationMask': '',
                'callingPartyTransformationMask': '',
                'digitDiscardInstructionName': '',
                'callingPartyPrefixDigits': ''})
        else: 
            return "must specify either uuid OR pattern and partition"

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['transPattern']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Translation pattern: {0} not found'.format(pattern)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result


    def add_translation(self,
                        pattern,
                        partition,
                        description='',
                        usage='Translation',
                        callingSearchSpaceName='',
                        useCallingPartyPhoneMask='Off',
                        patternUrgency='f',
                        provideOutsideDialtone='f',
                        prefixDigitsOut='',
                        calledPartyTransformationMask='',
                        callingPartyTransformationMask='',
                        digitDiscardInstructionName='',
                        callingPartyPrefixDigits='',
                        blockEnable='f',
                        routeNextHopByCgpn='f'
                        ):
        """
        Add a translation pattern
        :param pattern: Translation pattern
        :param partition: Route Partition
        :param description: Description - optional
        :param usage: Usage
        :param callingSearchSpaceName: Calling Search Space - optional
        :param patternUrgency: Pattern Urgency - optional
        :param provideOutsideDialtone: Provide Outside Dial Tone - optional
        :param prefixDigitsOut: Prefix Digits Out - optional
        :param calledPartyTransformationMask: - optional
        :param callingPartyTransformationMask: - optional
        :param digitDiscardInstructionName: - optional
        :param callingPartyPrefixDigits: - optional
        :param blockEnable: - optional
        :return: result dictionary
        """
        resp = self.client.addTransPattern({
            'pattern': pattern, 
            'description': description,
            'routePartitionName': partition,
            'usage': usage,
            'callingSearchSpaceName': callingSearchSpaceName,
            'useCallingPartyPhoneMask': useCallingPartyPhoneMask,
            'patternUrgency': patternUrgency,
            'provideOutsideDialtone': provideOutsideDialtone,
            'prefixDigitsOut': prefixDigitsOut,
            'calledPartyTransformationMask': calledPartyTransformationMask,
            'callingPartyTransformationMask': callingPartyTransformationMask,
            'digitDiscardInstructionName': digitDiscardInstructionName,
            'callingPartyPrefixDigits': callingPartyPrefixDigits,
            'blockEnable': blockEnable
        })
        # print(resp)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Translation successfully added'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Translation already exists'.format(pattern)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Translation could not be added'
            result['error'] = resp[1].faultstring
            return result


    def delete_translation(self, pattern='', partition='', uuid=''):
        """
        Delete a translation pattern
        :param pattern: The pattern of the route to delete
        :param partition: The name of the partition
        :param uuid: Required if pattern and partition are not specified
        :return: result dictionary
        """

        if pattern != '' and partition != '' and uuid == '':
            resp = self.client.removeTransPattern(pattern=pattern, routePartitionName=partition)
        elif uuid != '' and pattern == '' and partition == '':
            resp = self.client.removeTransPattern(uuid=uuid)
        else: 
            return "must specify either uuid OR pattern and partition"

        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Translation Pattern successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Translation Pattern: {0} not found'.format(pattern, partition)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Translation Pattern could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_translation(self,
                        pattern='',
                        partition='',
                        uuid='',
                        newPattern='',
                        description='',
                        newRoutePartitionName='',
                        callingSearchSpaceName='',
                        useCallingPartyPhoneMask='',
                        patternUrgency='',
                        provideOutsideDialtone='',
                        prefixDigitsOut='',
                        calledPartyTransformationMask='',
                        callingPartyTransformationMask='',
                        digitDiscardInstructionName='',
                        callingPartyPrefixDigits='',
                        blockEnable=''
                        ):
        """
        Update a translation pattern
        :param uuid: UUID or Translation + Partition Required
        :param pattern: Translation pattern
        :param partition: Route Partition
        :param description: Description - optional
        :param usage: Usage
        :param callingSearchSpaceName: Calling Search Space - optional
        :param patternUrgency: Pattern Urgency - optional
        :param provideOutsideDialtone: Provide Outside Dial Tone - optional
        :param prefixDigitsOut: Prefix Digits Out - optional
        :param calledPartyTransformationMask: - optional
        :param callingPartyTransformationMask: - optional
        :param digitDiscardInstructionName: - optional
        :param callingPartyPrefixDigits: - optional
        :param blockEnable: - optional
        :return: result dictionary
        """

        args = {}
        if description != '':
            args['description'] = description
        if pattern != '' and partition != '' and uuid == '':
            args['pattern'] = pattern
            args['routePartitionName'] = partition
        if pattern == '' and partition == '' and uuid != '':
            args['uuid'] = uuid
        if newPattern != '':
            args['newPattern'] = newPattern
        if newRoutePartitionName != '':
            args['newRoutePartitionName'] = newRoutePartitionName
        if callingSearchSpaceName != '':
            args['callingSearchSpaceName'] = callingSearchSpaceName
        if useCallingPartyPhoneMask != '':
            args['useCallingPartyPhoneMask'] = useCallingPartyPhoneMask
        if digitDiscardInstructionName != '':
            args['digitDiscardInstructionName'] = digitDiscardInstructionName
        if callingPartyTransformationMask != '':
            args['callingPartyTransformationMask'] = callingPartyTransformationMask            
        if calledPartyTransformationMask != '':
            args['calledPartyTransformationMask'] = calledPartyTransformationMask
        if patternUrgency != '':
            args['patternUrgency'] = patternUrgency
        if provideOutsideDialtone != '':
            args['provideOutsideDialtone'] = provideOutsideDialtone
        if prefixDigitsOut != '':
            args['prefixDigitsOut'] = prefixDigitsOut
        if callingPartyPrefixDigits != '':
            args['callingPartyPrefixDigits'] = callingPartyPrefixDigits
        if blockEnable != '':
            args['blockEnable'] = blockEnable
        
        resp = self.client.updateTransPattern(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Translation successfully updated'
            return result
        elif resp[0] == 500 and 'duplicate value' in resp[1].faultstring:
            result['response'] = 'Translation not found'.format(pattern)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Translation could not be updated'
            result['error'] = resp[1].faultstring
            return result


    def list_route_plan(self, pattern='', mini=False):
        """
        List Route Plan
        :param pattern: Route Plan Contains Pattern
        :return: results dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        try:
            resp = self.client.listRoutePlan(
                {'dnOrPattern': '%'+pattern+'%'}, returnedTags={
                                'dnOrPattern': '',
                                'partition': '',
                                'type': '',
                                'routeDetail': ''
                })
        except:
            print('some error')
        #print(resp)
        if resp[0] == 200:
            if 'routePlan' in resp[1]['return']:
                result['success'] = True
                result['response'] = resp[1]['return']['routePlan']
                return result
            else:
                result['success'] = True
                result['response'] = 'Empty'
                return result
        else:
            result['response'] = 'No Patterns found?'
            result['error'] = resp[1]
            return result

    def list_route_plan_specific(self, pattern='', mini=False):
        """
        List Route Plan
        :param pattern: Route Plan Contains Pattern
        :return: results dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        resp = self.client.listRoutePlan(
            {'dnOrPattern': pattern}, returnedTags={
                            'dnOrPattern': '',
                            'partition': '',
                            'type': '',
                            'routeDetail': ''
            })

        if resp[0] == 200 and 'routePlan' in resp[1]['return']:
            result['success'] = True
            result['response'] = resp[1]['return']['routePlan']
            return result
        else:
            result['response'] = 'No Patterns found?'
            result['error'] = resp[1]
            return result

    def get_called_party_xforms(self, mini=False):
        """
        Get called party xforms
        :param mini: return a list of tuples of called party transformation pattern details
        :return: A list of dictionary's
        """
        resp = self.client.listCalledPartyTransformationPattern(
                {'pattern': '%'}, returnedTags={
                    'pattern': '', 'description': '', '_uuid': ''})[1]['return']['calledPartyTransformationPattern']
        if mini:
            return [(i['pattern'], i['description'], i['_uuid'][1:-1]) for i in resp]
        else:
            return resp

    def get_called_party_xform(self, **args):
        """
        Get called party xform details
        :param name:
        :param partition:
        :param uuid:
        :return: result dictionary
        """
        resp = self.client.getCalledPartyTransformationPattern(**args)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Called Party Transformation Pattern: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_called_party_xform(self, **args):
        """
        Add a called party transformation pattern
        :param pattern: pattern - required
        :param routePartitionName: partition required
        :param description: Route pattern description
        :param calledPartyTransformationmask:
        :param dialPlanName:
        :param digitDiscardInstructionName:
        :param routeFilterName:
        :param calledPartyPrefixDigits:
        :param calledPartyNumberingPlan:
        :param calledPartyNumberType:
        :param mlppPreemptionDisabled: does anyone use this?
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        resp = self.client.addCalledPartyTransformationPattern(**args)
        print(resp)
        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Transformation pattern successfully added'
            return result
        else:
            result['response'] = 'Transformation pattern could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_called_party_xform(self, **args):
        """
        Delete a called party transformation pattern
        :param uuid: The pattern uuid
        :param pattern: The pattern of the transformation to delete
        :param partition: The name of the partition
        :return: result dictionary
        """
        resp = self.client.removeCalledPartyTransformationPattern(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Transformation successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Transformation Pattern: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Transformation could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_called_party_xform(self, **args):
        """
        Update a called party transformation
        :param uuid: required unless pattern and routePartitionName is given
        :param pattern: pattern - required
        :param routePartitionName: partition required
        :param description: Route pattern description
        :param calledPartyTransformationmask:
        :param dialPlanName:
        :param digitDiscardInstructionName:
        :param routeFilterName:
        :param calledPartyPrefixDigits:
        :param calledPartyNumberingPlan:
        :param calledPartyNumberType:
        :param mlppPreemptionDisabled: does anyone use this?
        :return: result dictionary
        :return: result dictionary
        """
        resp = self.client.updateCalledPartyTransformationPattern(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Transformation Pattern successfully updated'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Transformation Pattern: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Transformation Pattern could not be updated'
            result['error'] = resp[1].faultstring
            return result


    def get_calling_party_xforms(self, mini=False):
        """
        Get calling party xforms
        :param mini: return a list of tuples of calling party transformation pattern details
        :return: A list of dictionary's
        """
        resp = self.client.listCallingPartyTransformationPattern(
                {'pattern': '%'}, returnedTags={
                    'pattern': '', 'description': '', '_uuid': ''})[1]['return']['callingPartyTransformationPattern']
        if mini:
            return [(i['pattern'], i['description'], i['_uuid'][1:-1]) for i in resp]
        else:
            return resp

    def get_calling_party_xform(self, **args):
        """
        Get calling party xform details
        :param name:
        :param partition:
        :param uuid:
        :return: result dictionary
        """
        resp = self.client.getCallingPartyTransformationPattern(**args)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Calling Party Transformation Pattern: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result

    def add_calling_party_xform(self, **args):
        """
        Add a calling party transformation pattern
        :param pattern: pattern - required
        :param routePartitionName: partition required
        :param description: Route pattern description
        :param callingPartyTransformationmask:
        :param dialPlanName:
        :param digitDiscardInstructionName:
        :param routeFilterName:
        :param callingPartyPrefixDigits:
        :param callingPartyNumberingPlan:
        :param callingPartyNumberType:
        :param mlppPreemptionDisabled: does anyone use this?
        :return: result dictionary
        """
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        resp = self.client.addCallingPartyTransformationPattern(**args)

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Transformation pattern successfully added'
            return result
        else:
            result['response'] = 'Transformation pattern could not be added'
            result['error'] = resp[1].faultstring
            return result

    def delete_calling_party_xform(self, **args):
        """
        Delete a calling party transformation pattern
        :param uuid: The pattern uuid
        :param pattern: The pattern of the transformation to delete
        :param partition: The name of the partition
        :return: result dictionary
        """
        resp = self.client.removeCallingPartyTransformationPattern(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Transformation successfully deleted'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Transformation Pattern: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Transformation could not be deleted'
            result['error'] = resp[1].faultstring
            return result

    def update_calling_party_xform(self, **args):
        """
        Update a calling party transformation
        :param uuid: required unless pattern and routePartitionName is given
        :param pattern: pattern - required
        :param routePartitionName: partition required
        :param description: Route pattern description
        :param calledPartyTransformationmask:
        :param dialPlanName:
        :param digitDiscardInstructionName:
        :param routeFilterName:
        :param calledPartyPrefixDigits:
        :param calledPartyNumberingPlan:
        :param calledPartyNumberType:
        :param mlppPreemptionDisabled: does anyone use this?
        :return: result dictionary
        :return: result dictionary
        """
        resp = self.client.updateCallingPartyTransformationPattern(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'Transformation Pattern successfully updated'
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'Transformation Pattern: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Transformation Pattern could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def get_sip_trunks(self):
        resp = self.client.listSipTrunk(
                {'name': '%'}, returnedTags={
                    'name': '',
                    'sipProfileName': '',
                    'callingSearchSpaceName': '',
                })[1]['return']['sipTrunk']
        return resp

    def get_sip_trunk(self, **args):
        """
        Get sip trunk
        :param name:
        :param uuid:
        :return: result dictionary
        """
        resp = self.client.getSipTrunk(**args)
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = resp[1]['return']['sipTrunk']
            return result
        elif resp[0] == 500 and 'was not found' in resp[1].faultstring:
            result['response'] = 'SIP Trunk: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result


    def update_sip_trunk(self, **args):
        """
        Update a SIP Trunk
        :param name:
        :param uuid:
        :param newName:
        :param description:
        :param callingSearchSpaceName:
        :param devicePoolName:
        :param locationName:
        :param sipProfileName:
        :param mtpRequired:

        :return:
        """
        
        resp = self.client.updateSipTrunk(**args)

        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'SIP Trunk successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**args) in resp[1].faultstring:
            result['response'] = 'SIP Trunk: {0} not found'.format(**args)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'SIP Trunk could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def add_sip_trunk(self, **kwargs):
        """
        Add a SIP Trunk
        :param name:
        :param description:
        :param product:
        :param protocol:
        :param protocolSide:
        :param callingSearchSpaceName:
        :param devicePoolName:
        :param securityProfileName:
        :param sipProfileName:
        :param destinations: param destination:
        :param runOnEveryNode:

        :return:
        """
    
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        resp = self.client.addSipTrunk(kwargs)

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'SIP Trunk successfully added'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**kwargs) in resp[1].faultstring:
            result['response'] = 'SIP Trunk: {0} not found'.format(**kwargs)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'SIP Trunk could not be added'
            result['error'] = resp[1].faultstring
            return result

    def update_dhcp_subnet(self, **kwargs):
        """
        Update DHCP Subnet
        :param uuid:
        :param dhcpServerName:
        :param subnetIpAddress:
        :param subnetMask:
        :param domainName:
        :param tftpServerName:
        :param primaryTftpServerIpAddress:
        :param secondaryTftpServerIpAddress:
        :param primaryRouterIpAddress:
        :param primaryStartIpAddress:
        :param primaryEndIpAddress:
        :param secondaryStartIpAddress:
        :param secondaryEndIpAddress:

        :return:
        """
    
        result = {
            'success': False,
            'response': '',
            'error': '',
        }

        resp = self.client.updateDhcpSubnet(**kwargs)

        if resp[0] == 200:
            result['success'] = True
            result['response'] = 'DHCP Subnet successfully updated'
            return result
        elif resp[0] == 500 and '{0} was not found'.format(**kwargs) in resp[1].faultstring:
            result['response'] = 'DHCP Subnet: {0} not found'.format(**kwargs)
            result['error'] = resp[1].faultstring
            return result
        else:
            result['response'] = 'DHCP Subnet could not be updated'
            result['error'] = resp[1].faultstring
            return result

    def listProcessNodes(self):
        resp = self.client.listProcessNode({'name': '%', 'processNodeRole': 'CUCM Voice/Video'}, returnedTags={'name': ''})
        result = {
            'success': False,
            'response': '',
            'error': '',
        }
        if resp[0] == 200:
            result['success'] = True
            subs = []
            nodes = resp[1]['return']['processNode']
            
            # only return call processing nodes and not the enterprisewidedata node
            for node in nodes:
                    if node.name != 'EnterpriseWideData':
                        subs.append(node.name)
            result['response'] = subs
            return result
        else:
            result['response'] = 'Unknown error'
            result['error'] = resp[1].faultstring
            return result