# Copyright (c) 2014 Cisco Systems 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.

from neutron.common import constants as n_constants
from neutron import context
from neutron import manager
from neutron.openstack.common.gettextutils import _LW
from neutron.openstack.common import log
from neutron.openstack.common import loopingcall
from neutron.plugins.ml2 import db as l2_db
from neutron.plugins.ml2 import driver_context

LOG = log.getLogger(__name__)


class SynchronizerBase(object):

    def __init__(self, driver, interval=None):
        self.core_plugin = manager.NeutronManager.get_plugin()
        self.driver = driver
        self.interval = interval

    def sync(self, f, *args, **kwargs):
        """Fire synchronization based on interval.

        Interval can be 0 for 'sync once' >0 for 'sync periodically' and
        <0 for 'no sync'
        """
        if self.interval:
            if self.interval > 0:
                loop_call = loopingcall.FixedIntervalLoopingCall(f, *args,
                                                                 **kwargs)
                loop_call.start(interval=self.interval)
                return loop_call
        else:
            # Fire once
            f(*args, **kwargs)


class ApicBaseSynchronizer(SynchronizerBase):

    def sync_base(self):
        self.sync(self._sync_base)

    def _sync_base(self):
        ctx = context.get_admin_context()
        # Sync Networks
        for network in self.core_plugin.get_networks(ctx):
            mech_context = driver_context.NetworkContext(self.core_plugin, ctx,
                                                         network)
            try:
                self.driver.create_network_postcommit(mech_context)
            except Exception:
                LOG.warn(_LW("Create network postcommit failed for "
                             "network %s"), network['id'])

        # Sync Subnets
        for subnet in self.core_plugin.get_subnets(ctx):
            mech_context = driver_context.SubnetContext(self.core_plugin, ctx,
                                                        subnet)
            try:
                self.driver.create_subnet_postcommit(mech_context)
            except Exception:
                LOG.warn(_LW("Create subnet postcommit failed for"
                             " subnet %s"), subnet['id'])

        # Sync Ports (compute/gateway/dhcp)
        for port in self.core_plugin.get_ports(ctx):
            _, binding = l2_db.get_locked_port_and_binding(ctx.session,
                                                           port['id'])
            network = self.core_plugin.get_network(ctx, port['network_id'])
            mech_context = driver_context.PortContext(self.core_plugin, ctx,
                                                      port, network, binding)
            try:
                self.driver.create_port_postcommit(mech_context)
            except Exception:
                LOG.warn(_LW("Create port postcommit failed for"
                             " port %s"), port['id'])


class ApicRouterSynchronizer(SynchronizerBase):

    def sync_router(self):
        self.sync(self._sync_router)

    def _sync_router(self):
        ctx = context.get_admin_context()
        # Sync Router Interfaces
        filters = {'device_owner': [n_constants.DEVICE_OWNER_ROUTER_INTF]}
        for interface in self.core_plugin.get_ports(ctx, filters=filters):
            try:
                self.driver.add_router_interface_postcommit(
                    ctx, interface['device_id'],
                    {'port_id': interface['id']})
            except Exception:
                LOG.warn(_LW("Add interface postcommit failed for "
                             "port %s"), interface['id'])
