#Copyright 2014 Cloudbase Solutions SRL
#All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from neutron.agent import firewall
from neutron.openstack.common import log as logging
from neutron.plugins.hyperv.agent import utilsfactory
from neutron.plugins.hyperv.agent import utilsv2

LOG = logging.getLogger(__name__)


class HyperVSecurityGroupsDriver(firewall.FirewallDriver):
    """Security Groups Driver.

    Security Groups implementation for Hyper-V VMs.
    """

    _ACL_PROP_MAP = {
        'direction': {'ingress': utilsv2.HyperVUtilsV2._ACL_DIR_IN,
                      'egress': utilsv2.HyperVUtilsV2._ACL_DIR_OUT},
        'ethertype': {'IPv4': utilsv2.HyperVUtilsV2._ACL_TYPE_IPV4,
                      'IPv6': utilsv2.HyperVUtilsV2._ACL_TYPE_IPV6},
        'protocol': {'icmp': utilsv2.HyperVUtilsV2._ICMP_PROTOCOL},
        'default': "ANY",
        'address_default': {'IPv4': '0.0.0.0/0', 'IPv6': '::/0'}
    }

    def __init__(self):
        self._utils = utilsfactory.get_hypervutils()
        self._security_ports = {}

    def prepare_port_filter(self, port):
        LOG.debug('Creating port %s rules' % len(port['security_group_rules']))

        # newly created port, add default rules.
        if port['device'] not in self._security_ports:
            LOG.debug('Creating default reject rules.')
            self._utils.create_default_reject_all_rules(port['id'])

        self._security_ports[port['device']] = port
        self._create_port_rules(port['id'], port['security_group_rules'])

    def _create_port_rules(self, port_id, rules):
        for rule in rules:
            param_map = self._create_param_map(rule)
            try:
                self._utils.create_security_rule(port_id, **param_map)
            except Exception as ex:
                LOG.error(_('Hyper-V Exception: %(hyperv_exeption)s while '
                            'adding rule: %(rule)s'),
                          dict(hyperv_exeption=ex, rule=rule))

    def _remove_port_rules(self, port_id, rules):
        for rule in rules:
            param_map = self._create_param_map(rule)
            try:
                self._utils.remove_security_rule(port_id, **param_map)
            except Exception as ex:
                LOG.error(_('Hyper-V Exception: %(hyperv_exeption)s while '
                            'removing rule: %(rule)s'),
                          dict(hyperv_exeption=ex, rule=rule))

    def _create_param_map(self, rule):
        if 'port_range_min' in rule and 'port_range_max' in rule:
            local_port = '%s-%s' % (rule['port_range_min'],
                                    rule['port_range_max'])
        else:
            local_port = self._ACL_PROP_MAP['default']

        return {
            'direction': self._ACL_PROP_MAP['direction'][rule['direction']],
            'acl_type': self._ACL_PROP_MAP['ethertype'][rule['ethertype']],
            'local_port': local_port,
            'protocol': self._get_rule_protocol(rule),
            'remote_address': self._get_rule_remote_address(rule)
        }

    def apply_port_filter(self, port):
        LOG.info(_('Aplying port filter.'))

    def update_port_filter(self, port):
        LOG.info(_('Updating port rules.'))

        if port['device'] not in self._security_ports:
            self.prepare_port_filter(port)
            return

        old_port = self._security_ports[port['device']]
        rules = old_port['security_group_rules']
        param_port_rules = port['security_group_rules']

        new_rules = [r for r in param_port_rules if r not in rules]
        remove_rules = [r for r in rules if r not in param_port_rules]

        LOG.info(_("Creating %(new)s new rules, removing %(old)s "
                   "old rules."),
                 {'new': len(new_rules),
                  'old': len(remove_rules)})

        self._remove_port_rules(old_port['id'], remove_rules)
        self._create_port_rules(port['id'], new_rules)

        self._security_ports[port['device']] = port

    def remove_port_filter(self, port):
        LOG.info(_('Removing port filter'))
        self._security_ports.pop(port['device'], None)

    @property
    def ports(self):
        return self._security_ports

    def _get_rule_remote_address(self, rule):
        if rule['direction'] is 'ingress':
            ip_prefix = 'source_ip_prefix'
        else:
            ip_prefix = 'dest_ip_prefix'

        if ip_prefix in rule:
            return rule[ip_prefix]
        return self._ACL_PROP_MAP['address_default'][rule['ethertype']]

    def _get_rule_protocol(self, rule):
        protocol = self._get_rule_prop_or_default(rule, 'protocol')
        if protocol in self._ACL_PROP_MAP['protocol'].keys():
            return self._ACL_PROP_MAP['protocol'][protocol]

        return protocol

    def _get_rule_prop_or_default(self, rule, prop):
        if prop in rule:
            return rule[prop]
        return self._ACL_PROP_MAP['default']
