# Copyright 2014 Embrane, 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.
#
# @author:  Ivar Lazzaro, Embrane, Inc. ivar@embrane.com

from functools import wraps

from heleosapi import exceptions as h_exc

from neutron.openstack.common import log as logging
from neutron.services.loadbalancer import constants as lcon
from neutron.services.loadbalancer.drivers.embrane import constants as econ

LOG = logging.getLogger(__name__)
handlers = {}


def handler(event, handler):
    def wrap(f):
        if event not in handler.keys():
            handler[event] = [f]
        else:
            handler[event].append(f)

        @wraps(f)
        def wrapped_f(*args, **kwargs):
            return f(*args, **kwargs)

        return wrapped_f

    return wrap


@handler(econ.Events.CREATE_VIP, handlers)
def _provision_load_balancer(driver, context, vip, flavor,
                             vip_utif_info, vip_ip_allocation_info,
                             pool_utif_info=None,
                             pool_ip_allocation_info=None,
                             pool=None, members=None,
                             monitors=None):
    api = driver._heleos_api
    tenant_id = context.tenant_id
    admin_state = vip["admin_state_up"]
    # Architectural configuration
    api.create_load_balancer(tenant_id=tenant_id,
                             router_id=vip["id"],
                             name=vip["name"],
                             flavor=flavor,
                             up=False)
    api.grow_interface(vip_utif_info, False, tenant_id, vip["id"])
    if pool:
        api.grow_interface(pool_utif_info, False, tenant_id,
                           vip["id"])

    # Logical configuration
    api.allocate_address(vip["id"], True, vip_ip_allocation_info)
    if pool:
        api.allocate_address(vip["id"], True, pool_ip_allocation_info)
    dva = api.configure_load_balancer(vip["id"], admin_state,
                                      vip, pool,
                                      monitors, members)
    return api.extract_dva_state(dva)


@handler(econ.Events.UPDATE_VIP, handlers)
def _update_load_balancer(driver, context, vip,
                          old_pool_id=None, old_port_id=None,
                          removed_ip=None, pool_utif_info=None,
                          pool_ip_allocation_info=None,
                          new_pool=None, members=None,
                          monitors=None):
    api = driver._heleos_api
    tenant_id = context.tenant_id
    admin_state = vip["admin_state_up"]

    if old_pool_id:
        # Architectural Changes
        api.de_allocate_address(vip['id'], False, old_port_id, removed_ip)
        api.shrink_interface(tenant_id, vip["id"], False, old_port_id)
        api.grow_interface(pool_utif_info, False, tenant_id, vip["id"])
        # Configuration Changes
        api.allocate_address(vip["id"], True, pool_ip_allocation_info)
        api.replace_pool(vip["id"], True, vip, old_pool_id,
                         new_pool, monitors, members)

    api.update_vservice(vip["id"], True, vip)
    # Dva update
    dva = api.update_dva(tenant_id, vip["id"], vip["name"],
                         admin_state, description=vip["description"])

    return api.extract_dva_state(dva)


@handler(econ.Events.DELETE_VIP, handlers)
def _delete_load_balancer(driver, context, vip):
    try:
        driver._heleos_api.delete_dva(context.tenant_id, vip['id'])
    except h_exc.DvaNotFound:
        LOG.warning(_('The load balancer %s had no physical representation, '
                      'likely already deleted'), vip['id'])
    return econ.DELETED


@handler(econ.Events.UPDATE_POOL, handlers)
def _update_server_pool(driver, context, vip, pool,
                        monitors=None):
    api = driver._heleos_api
    cookie = ((vip.get('session_persistence') or {}).get('type') ==
              lcon.SESSION_PERSISTENCE_HTTP_COOKIE)
    return api.extract_dva_state(api.update_pool(vip['id'],
                                                 vip['admin_state_up'],
                                                 pool, cookie, monitors))


@handler(econ.Events.ADD_OR_UPDATE_MEMBER, handlers)
def _add_or_update_pool_member(driver, context, vip, member, protocol):
    api = driver._heleos_api
    return api.extract_dva_state(api.update_backend_server(
        vip['id'], vip['admin_state_up'], member, protocol))


@handler(econ.Events.REMOVE_MEMBER, handlers)
def _remove_member_from_pool(driver, context, vip, member):
    api = driver._heleos_api
    return api.extract_dva_state(api.remove_pool_member(vip['id'],
                                                        vip['admin_state_up'],
                                                        member))


@handler(econ.Events.DELETE_MEMBER, handlers)
def _delete_member(driver, context, vip, member):
    with context.session.begin(subtransactions=True):
        api = driver._heleos_api
        dva = api.delete_backend_server(vip['id'], vip['admin_state_up'],
                                        member)
        driver._delete_member(context, member)
        return api.extract_dva_state(dva)


@handler(econ.Events.ADD_POOL_HM, handlers)
def _create_pool_hm(driver, context, vip, hm, pool_id):
    api = driver._heleos_api
    return api.extract_dva_state(api.add_pool_monitor(
        vip['id'], vip['admin_state_up'], hm, pool_id))


@handler(econ.Events.UPDATE_POOL_HM, handlers)
def _update_pool_hm(driver, context, vip, hm, pool_id):
    api = driver._heleos_api
    return api.extract_dva_state(api.update_pool_monitor(
        vip['id'], vip['admin_state_up'], hm, pool_id))


@handler(econ.Events.DELETE_POOL_HM, handlers)
def _delete_pool_hm(driver, context, vip, hm, pool_id):
    with context.session.begin(subtransactions=True):
        api = driver._heleos_api
        dva = api.add_pool_monitor(vip['id'], vip['admin_state_up'],
                                   hm, pool_id)
        driver._delete_pool_hm(context, hm, pool_id)
        return api.extract_dva_state(dva)


@handler(econ.Events.POLL_GRAPH, handlers)
def _poll_graph(driver, context, vip):
    api = driver._heleos_api
    return api.extract_dva_state(api.get_dva(vip['id']))
