# Copyright 2016 VMware, Inc.
# 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.

import abc

from neutron_lib.api.definitions import availability_zone as az_def
from neutron_lib import exceptions as n_exc
from neutron_lib.exceptions import availability_zone as az_exc

from vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsx_exc

DEFAULT_NAME = 'default'


class ConfiguredAvailabilityZone(object):

    def __init__(self, config_line, default_name=DEFAULT_NAME):
        self.name = ""
        self._is_default = False
        if config_line and ':' in config_line:
            # Older configuration - each line contains all the relevant
            # values for one availability zones, separated by ':'
            values = config_line.split(':')
            self.name = values[0]
            self._validate_zone_name(self.name)
            self.init_from_config_line(config_line)
        elif config_line:
            # Newer configuration - the name of the availability zone can be
            # used to get the rest of the configuration for this AZ
            self.name = config_line
            self._validate_zone_name(config_line)
            self.init_from_config_section(self.name)
        else:
            # Default zone configuration
            self.name = default_name
            self._is_default = True
            self.init_default_az()

    def is_default(self):
        return self._is_default

    def _validate_zone_name(self, config_line):
        if len(self.name) > 36:
            raise nsx_exc.NsxInvalidConfiguration(
                opt_name="availability_zones",
                opt_value=config_line,
                reason=_("Maximum name length is 36"))

    @abc.abstractmethod
    def init_from_config_line(self, config_values):
        pass

    @abc.abstractmethod
    def init_from_config_section(self, az_name):
        pass

    @abc.abstractmethod
    def init_default_az(self):
        pass


class ConfiguredAvailabilityZones(object):

    default_name = DEFAULT_NAME

    def __init__(self, az_conf, az_class, default_availability_zones=None):
        self.availability_zones = {}

        # Add the configured availability zones
        for az in az_conf:
            obj = az_class(az)
            self.availability_zones[obj.name] = obj

        # add a default entry
        obj = az_class(None, default_name=self.default_name)
        self.availability_zones[obj.name] = obj

        # validate the default az:
        if default_availability_zones:
            # we support only 1 default az
            if len(default_availability_zones) > 1:
                raise nsx_exc.NsxInvalidConfiguration(
                    opt_name="default_availability_zones",
                    opt_value=default_availability_zones,
                    reason=_("The NSX plugin supports only 1 default AZ"))
            default_az_name = default_availability_zones[0]
            if (default_az_name not in self.availability_zones):
                raise nsx_exc.NsxInvalidConfiguration(
                    opt_name="default_availability_zones",
                    opt_value=default_availability_zones,
                    reason=_("The default AZ is not defined in the NSX "
                             "plugin"))
            else:
                self._default_az = self.availability_zones[default_az_name]
        else:
            self._default_az = self.availability_zones[self.default_name]

    def get_availability_zone(self, name):
        """Return an availability zone object by its name
        """
        if name in self.availability_zones.keys():
            return self.availability_zones[name]
        return self.get_default_availability_zone()

    def get_default_availability_zone(self):
        """Return the default availability zone object
        """
        return self._default_az

    def list_availability_zones(self):
        """Return a list of availability zones names
        """
        return self.availability_zones.keys()

    def list_availability_zones_objects(self):
        """Return a list of availability zones objects
        """
        return self.availability_zones.values()


class NSXAvailabilityZonesPluginCommon(object):

    @abc.abstractmethod
    def init_availability_zones(self):
        # To be initialized by the real plugin
        self._availability_zones_data = None

    def get_azs_list(self):
        return self._availability_zones_data.list_availability_zones_objects()

    def get_azs_names(self):
        return self._availability_zones_data.list_availability_zones()

    def validate_obj_azs(self, availability_zones):
        """Verify that the availability zones exist, and only 1 hint
        was set.
        """
        # For now we support only 1 hint per network/router
        # TODO(asarfaty): support multiple hints
        if len(availability_zones) > 1:
            err_msg = _("Can't use multiple availability zone hints")
            raise n_exc.InvalidInput(error_message=err_msg)

        # check that all hints appear in the predefined list of availability
        # zones
        diff = (set(availability_zones) - set(self.get_azs_names()))
        if diff:
            raise az_exc.AvailabilityZoneNotFound(
                availability_zone=diff.pop())

    def get_az_by_hint(self, hint):
        az = self._availability_zones_data.get_availability_zone(hint)
        if not az:
            raise az_def.AvailabilityZoneNotFound(availability_zone=hint)
        return az

    def get_default_az(self):
        return self._availability_zones_data.get_default_availability_zone()

    def get_obj_az_by_hints(self, obj):
        if az_def.AZ_HINTS in obj:
            for hint in obj[az_def.AZ_HINTS]:
                # For now we use only the first hint
                return self.get_az_by_hint(hint)

        # return the default
        return self.get_default_az()

    def get_network_az(self, network):
        return self.get_obj_az_by_hints(network)

    def get_router_az(self, router):
        return self.get_obj_az_by_hints(router)
