# Copyright (c) 2013 OpenStack Foundation
# 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
import itertools
import operator

from oslo_config import cfg
from oslo_db import api as oslo_db_api
from oslo_db import exception as db_exc
from oslo_log import log
from six import moves

from neutron.common import exceptions as exc
from neutron.common import topics
from neutron.db import api as db_api
from neutron.i18n import _LI, _LW
from neutron.plugins.common import utils as plugin_utils
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2.drivers import helpers

LOG = log.getLogger(__name__)

TUNNEL = 'tunnel'


def chunks(iterable, chunk_size):
    """Chunks data into chunk with size<=chunk_size."""
    iterator = iter(iterable)
    chunk = list(itertools.islice(iterator, 0, chunk_size))
    while chunk:
        yield chunk
        chunk = list(itertools.islice(iterator, 0, chunk_size))


class TunnelTypeDriver(helpers.SegmentTypeDriver):
    """Define stable abstract interface for ML2 type drivers.

    tunnel type networks rely on tunnel endpoints. This class defines abstract
    methods to manage these endpoints.
    """
    BULK_SIZE = 100

    def __init__(self, model):
        super(TunnelTypeDriver, self).__init__(model)
        self.segmentation_key = next(iter(self.primary_keys))

    @abc.abstractmethod
    def add_endpoint(self, ip, host):
        """Register the endpoint in the type_driver database.

        param ip: the IP address of the endpoint
        param host: the Host name of the endpoint
        """

    @abc.abstractmethod
    def get_endpoints(self):
        """Get every endpoint managed by the type_driver

        :returns a list of dict [{ip_address:endpoint_ip, host:endpoint_host},
        ..]
        """

    @abc.abstractmethod
    def get_endpoint_by_host(self, host):
        """Get endpoint for a given host managed by the type_driver

        param host: the Host name of the endpoint

        if host found in type_driver database
           :returns db object for that particular host
        else
           :returns None
        """

    @abc.abstractmethod
    def get_endpoint_by_ip(self, ip):
        """Get endpoint for a given tunnel ip managed by the type_driver

        param ip: the IP address of the endpoint

        if ip found in type_driver database
           :returns db object for that particular ip
        else
           :returns None
        """

    @abc.abstractmethod
    def delete_endpoint(self, ip):
        """Delete the endpoint in the type_driver database.

        param ip: the IP address of the endpoint
        """

    def _initialize(self, raw_tunnel_ranges):
        self.tunnel_ranges = []
        self._parse_tunnel_ranges(raw_tunnel_ranges, self.tunnel_ranges)
        self.sync_allocations()

    def _parse_tunnel_ranges(self, tunnel_ranges, current_range):
        for entry in tunnel_ranges:
            entry = entry.strip()
            try:
                tun_min, tun_max = entry.split(':')
                tun_min = tun_min.strip()
                tun_max = tun_max.strip()
                tunnel_range = int(tun_min), int(tun_max)
            except ValueError as ex:
                raise exc.NetworkTunnelRangeError(tunnel_range=entry, error=ex)
            plugin_utils.verify_tunnel_range(tunnel_range, self.get_type())
            current_range.append(tunnel_range)
        LOG.info(_LI("%(type)s ID ranges: %(range)s"),
                 {'type': self.get_type(), 'range': current_range})

    @oslo_db_api.wrap_db_retry(
        max_retries=db_api.MAX_RETRIES, retry_on_deadlock=True)
    def sync_allocations(self):
        # determine current configured allocatable tunnel ids
        tunnel_ids = set()
        for tun_min, tun_max in self.tunnel_ranges:
            tunnel_ids |= set(moves.range(tun_min, tun_max + 1))

        tunnel_id_getter = operator.attrgetter(self.segmentation_key)
        tunnel_col = getattr(self.model, self.segmentation_key)