.. versionadded:: 2019.2.0

    Ensure a security rule exists.

    :param name:
        Name of the security rule.

    :param access:
        'allow' or 'deny'

    :param direction:
        'inbound' or 'outbound'

    :param priority:
        Integer between 100 and 4096 used for ordering rule application.

    :param protocol:
        'tcp', 'udp', or '*'

    :param security_group:
        The name of the existing network security group to contain the security rule.

    :param resource_group:
        The resource group assigned to the network security group.

    :param description:
        Optional description of the security rule.

    :param destination_address_prefix:
        The CIDR or destination IP range. Asterix '*' can also be used to match all destination IPs.
        Default tags such as 'VirtualNetwork', 'AzureLoadBalancer' and 'Internet' can also be used.
        If this is an ingress rule, specifies where network traffic originates from.

    :param destination_port_range:
        The destination port or range. Integer or range between 0 and 65535. Asterix '*'
        can also be used to match all ports.

    :param source_address_prefix:
        The CIDR or source IP range. Asterix '*' can also be used to match all source IPs.
        Default tags such as 'VirtualNetwork', 'AzureLoadBalancer' and 'Internet' can also be used.
        If this is an ingress rule, specifies where network traffic originates from.

    :param source_port_range:
        The source port or range. Integer or range between 0 and 65535. Asterix '*'
        can also be used to match all ports.

    :param destination_address_prefixes:
        A list of destination_address_prefix values. This parameter overrides destination_address_prefix
        and will cause any value entered there to be ignored.

    :param destination_port_ranges:
        A list of destination_port_range values. This parameter overrides destination_port_range
        and will cause any value entered there to be ignored.

    :param source_address_prefixes:
        A list of source_address_prefix values. This parameter overrides source_address_prefix
        and will cause any value entered there to be ignored.

    :param source_port_ranges:
        A list of source_port_range values. This parameter overrides source_port_range
        and will cause any value entered there to be ignored.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure security rule exists:
            azurearm_network.security_rule_present:
                - name: nsg1_rule2
                - security_group: nsg1
                - resource_group: group1
                - priority: 101
                - protocol: tcp
                - access: allow
                - direction: inbound
                - source_address_prefix: internet
                - destination_address_prefix: virtualnetwork
                - source_port_range: '*'
                - destination_port_ranges:
                  - '80'
                  - '443'
                - connection_auth: {{ profile }}
                - require:
                  - azurearm_network: Ensure network security group exists

def security_rule_present(name, access, direction, priority, protocol, security_group, resource_group,
                          destination_address_prefix=None, destination_port_range=None, source_address_prefix=None,
                          source_port_range=None, description=None, destination_address_prefixes=None,
                          destination_port_ranges=None, source_address_prefixes=None, source_port_ranges=None,
                          connection_auth=None, **kwargs):
    '''
    .. versionadded:: 2019.2.0

    Ensure a security rule exists.

    :param name:
        Name of the security rule.

    :param access:
        'allow' or 'deny'

    :param direction:
        'inbound' or 'outbound'

    :param priority:
        Integer between 100 and 4096 used for ordering rule application.

    :param protocol:
        'tcp', 'udp', or '*'

    :param security_group:
        The name of the existing network security group to contain the security rule.

    :param resource_group:
        The resource group assigned to the network security group.

    :param description:
        Optional description of the security rule.

    :param destination_address_prefix:
        The CIDR or destination IP range. Asterix '*' can also be used to match all destination IPs.
        Default tags such as 'VirtualNetwork', 'AzureLoadBalancer' and 'Internet' can also be used.
        If this is an ingress rule, specifies where network traffic originates from.

    :param destination_port_range:
        The destination port or range. Integer or range between 0 and 65535. Asterix '*'
        can also be used to match all ports.

    :param source_address_prefix:
        The CIDR or source IP range. Asterix '*' can also be used to match all source IPs.
        Default tags such as 'VirtualNetwork', 'AzureLoadBalancer' and 'Internet' can also be used.
        If this is an ingress rule, specifies where network traffic originates from.

    :param source_port_range:
        The source port or range. Integer or range between 0 and 65535. Asterix '*'
        can also be used to match all ports.

    :param destination_address_prefixes:
        A list of destination_address_prefix values. This parameter overrides destination_address_prefix
        and will cause any value entered there to be ignored.

    :param destination_port_ranges:
        A list of destination_port_range values. This parameter overrides destination_port_range
        and will cause any value entered there to be ignored.

    :param source_address_prefixes:
        A list of source_address_prefix values. This parameter overrides source_address_prefix
        and will cause any value entered there to be ignored.

    :param source_port_ranges:
        A list of source_port_range values. This parameter overrides source_port_range
        and will cause any value entered there to be ignored.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure security rule exists:
            azurearm_network.security_rule_present:
                - name: nsg1_rule2
                - security_group: nsg1
                - resource_group: group1
                - priority: 101
                - protocol: tcp
                - access: allow
                - direction: inbound
                - source_address_prefix: internet
                - destination_address_prefix: virtualnetwork
                - source_port_range: '*'
                - destination_port_ranges:
                  - '80'
                  - '443'
                - connection_auth: {{ profile }}
                - require:
                  - azurearm_network: Ensure network security group exists

    '''
    ret = {
        'name': name,
        'result': False,
        'comment': '',
        'changes': {}
    }

    if not isinstance(connection_auth, dict):
        ret['comment'] = 'Connection information must be specified via connection_auth dictionary!'
        return ret

    exclusive_params = [
        ('source_port_ranges', 'source_port_range'),
        ('source_address_prefixes', 'source_address_prefix'),
        ('destination_port_ranges', 'destination_port_range'),
        ('destination_address_prefixes', 'destination_address_prefix'),
    ]

    for params in exclusive_params:
        # pylint: disable=eval-used
        if not eval(params[0]) and not eval(params[1]):
            ret['comment'] = 'Either the {0} or {1} parameter must be provided!'.format(params[0], params[1])
            return ret
        # pylint: disable=eval-used
        if eval(params[0]):
            # pylint: disable=eval-used
            if not isinstance(eval(params[0]), list):
                ret['comment'] = 'The {0} parameter must be a list!'.format(params[0])
                return ret
            # pylint: disable=exec-used
            exec('{0} = None'.format(params[1]))

    rule = __salt__['azurearm_network.security_rule_get'](
        name,
        security_group,
        resource_group,
        azurearm_log_level='info',
        **connection_auth
    )

    if 'error' not in rule:
        # access changes
        if access.capitalize() != rule.get('access'):
            ret['changes']['access'] = {
                'old': rule.get('access'),
                'new': access
            }

        # description changes
        if description != rule.get('description'):
            ret['changes']['description'] = {
                'old': rule.get('description'),
                'new': description
            }

        # direction changes
        if direction.capitalize() != rule.get('direction'):
            ret['changes']['direction'] = {
                'old': rule.get('direction'),
                'new': direction
            }

        # priority changes
        if int(priority) != rule.get('priority'):
            ret['changes']['priority'] = {
                'old': rule.get('priority'),
                'new': priority
            }

        # protocol changes
        if protocol.lower() != rule.get('protocol', '').lower():
            ret['changes']['protocol'] = {
                'old': rule.get('protocol'),
                'new': protocol
            }

        # destination_port_range changes
        if destination_port_range != rule.get('destination_port_range'):
            ret['changes']['destination_port_range'] = {
                'old': rule.get('destination_port_range'),
                'new': destination_port_range
            }

        # source_port_range changes
        if source_port_range != rule.get('source_port_range'):
            ret['changes']['source_port_range'] = {
                'old': rule.get('source_port_range'),
                'new': source_port_range
            }

        # destination_port_ranges changes
        if sorted(destination_port_ranges or []) != sorted(rule.get('destination_port_ranges', [])):
            ret['changes']['destination_port_ranges'] = {
                'old': rule.get('destination_port_ranges'),
                'new': destination_port_ranges
            }

        # source_port_ranges changes
        if sorted(source_port_ranges or []) != sorted(rule.get('source_port_ranges', [])):
            ret['changes']['source_port_ranges'] = {
                'old': rule.get('source_port_ranges'),
                'new': source_port_ranges
            }

        # destination_address_prefix changes
        if (destination_address_prefix or '').lower() != rule.get('destination_address_prefix', '').lower():
            ret['changes']['destination_address_prefix'] = {
                'old': rule.get('destination_address_prefix'),
                'new': destination_address_prefix
            }

        # source_address_prefix changes
        if (source_address_prefix or '').lower() != rule.get('source_address_prefix', '').lower():
            ret['changes']['source_address_prefix'] = {
                'old': rule.get('source_address_prefix'),
                'new': source_address_prefix
            }

        # destination_address_prefixes changes
        if sorted(destination_address_prefixes or []) != sorted(rule.get('destination_address_prefixes', [])):
            if len(destination_address_prefixes or []) != len(rule.get('destination_address_prefixes', [])):
                ret['changes']['destination_address_prefixes'] = {
                    'old': rule.get('destination_address_prefixes'),
                    'new': destination_address_prefixes
                }
            else:
                local_dst_addrs, remote_dst_addrs = (sorted(destination_address_prefixes),
                                                     sorted(rule.get('destination_address_prefixes')))
                for idx in six_range(0, len(local_dst_addrs)):
                    if local_dst_addrs[idx].lower() != remote_dst_addrs[idx].lower():
                        ret['changes']['destination_address_prefixes'] = {
                            'old': rule.get('destination_address_prefixes'),
                            'new': destination_address_prefixes
                        }
                        break

        # source_address_prefixes changes
        if sorted(source_address_prefixes or []) != sorted(rule.get('source_address_prefixes', [])):
            if len(source_address_prefixes or []) != len(rule.get('source_address_prefixes', [])):
                ret['changes']['source_address_prefixes'] = {
                    'old': rule.get('source_address_prefixes'),
                    'new': source_address_prefixes
                }
            else:
                local_src_addrs, remote_src_addrs = (sorted(source_address_prefixes),
                                                     sorted(rule.get('source_address_prefixes')))
                for idx in six_range(0, len(local_src_addrs)):
                    if local_src_addrs[idx].lower() != remote_src_addrs[idx].lower():
                        ret['changes']['source_address_prefixes'] = {
                            'old': rule.get('source_address_prefixes'),
                            'new': source_address_prefixes
                        }
                        break

        if not ret['changes']:
            ret['result'] = True
            ret['comment'] = 'Security rule {0} is already present.'.format(name)
            return ret

        if __opts__['test']:
            ret['result'] = None
            ret['comment'] = 'Security rule {0} would be updated.'.format(name)
            return ret

    else:
        ret['changes'] = {
            'old': {},
            'new': {
                'name': name,
                'access': access,
                'description': description,
                'direction': direction,
                'priority': priority,
                'protocol': protocol,
                'destination_address_prefix': destination_address_prefix,
                'destination_address_prefixes': destination_address_prefixes,
                'destination_port_range': destination_port_range,
                'destination_port_ranges': destination_port_ranges,
                'source_address_prefix': source_address_prefix,
                'source_address_prefixes': source_address_prefixes,
                'source_port_range': source_port_range,
                'source_port_ranges': source_port_ranges,
            }
        }

    if __opts__['test']:
        ret['comment'] = 'Security rule {0} would be created.'.format(name)
        ret['result'] = None
        return ret

    rule_kwargs = kwargs.copy()
    rule_kwargs.update(connection_auth)

    rule = __salt__['azurearm_network.security_rule_create_or_update'](
        name=name,
        access=access,
        description=description,
        direction=direction,
        priority=priority,
        protocol=protocol,
        security_group=security_group,
        resource_group=resource_group,
        destination_address_prefix=destination_address_prefix,
        destination_address_prefixes=destination_address_prefixes,
        destination_port_range=destination_port_range,
        destination_port_ranges=destination_port_ranges,
        source_address_prefix=source_address_prefix,
        source_address_prefixes=source_address_prefixes,
        source_port_range=source_port_range,
        source_port_ranges=source_port_ranges,
        **rule_kwargs
    )

    if 'error' not in rule:
        ret['result'] = True
        ret['comment'] = 'Security rule {0} has been created.'.format(name)
        return ret

    ret['comment'] = 'Failed to create security rule {0}! ({1})'.format(name, rule.get('error'))
    return ret