# Copyright 2012 Nicira Networks, Inc.
#
#    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: Brad Hall, Nicira Networks, Inc.

import logging

from quantum.common import exceptions as exception
from quantum.openstack.common import jsonutils
from quantum.plugins.nicira.nicira_nvp_plugin import NvpApiClient


LOG = logging.getLogger("nvplib")
LOG.setLevel(logging.INFO)


def do_single_request(*args, **kwargs):
    """Issue a request to a specified controller if specified via kwargs
       (controller=<controller>)."""
    controller = kwargs["controller"]
    LOG.debug("Issuing request to controller: %s" % controller.name)
    return controller.api_client.request(*args)


def check_default_transport_zone(c):
    """Make sure the default transport zone specified in the config exists"""
    msg = []
    # This will throw an exception on failure and that's ok since it will
    # just propogate to the cli.
    resp = do_single_request(
        "GET",
        "/ws.v1/transport-zone?uuid=%s" % c.default_tz_uuid,
        controller=c)
    result = jsonutils.loads(resp)
    if int(result["result_count"]) == 0:
        msg.append("Unable to find zone \"%s\" for controller \"%s\"" %
                   (c.default_tz_uuid, c.name))
    if len(msg) > 0:
        raise Exception(' '.join(msg))


def check_tenant(controller, net_id, tenant_id):
    """Return true if the tenant "owns" this network"""
    net = get_network(controller, net_id)
    for t in net["tags"]:
        if t["scope"] == "os_tid" and t["tag"] == tenant_id:
            return True
    return False

# -------------------------------------------------------------------
# Network functions
# -------------------------------------------------------------------


def get_network(controller, net_id):
    path = "/ws.v1/lswitch/%s" % net_id
    try:
        resp_obj = do_single_request("GET", path, controller=controller)
        network = jsonutils.loads(resp_obj)
    except NvpApiClient.ResourceNotFound as e:
        raise exception.NetworkNotFound(net_id=net_id)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    LOG.debug("Got network \"%s\": %s" % (net_id, network))
    return network


def create_lswitch(controller, lswitch_obj):
    LOG.debug("Creating lswitch: %s" % lswitch_obj)
    # Warn if no tenant is specified
    found = "os_tid" in [x["scope"] for x in lswitch_obj["tags"]]
    if not found:
        LOG.warn("No tenant-id tag specified in logical switch: %s" %
                 lswitch_obj)
    uri = "/ws.v1/lswitch"
    try:
        resp_obj = do_single_request("POST", uri,
                                     jsonutils.dumps(lswitch_obj),
                                     controller=controller)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()

    r = jsonutils.loads(resp_obj)
    d = {}
    d["net-id"] = r["uuid"]
    d["net-name"] = r["display_name"]
    LOG.debug("Created logical switch: %s" % d["net-id"])
    return d


def update_network(controller, network, **kwargs):
    uri = "/ws.v1/lswitch/" + network
    lswitch_obj = {}
    if "name" in kwargs:
        lswitch_obj["display_name"] = kwargs["name"]
    try:
        resp_obj = do_single_request("PUT",
                                     uri,
                                     jsonutils.dumps(lswitch_obj),
                                     controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Network not found, Error: %s" % str(e))
        raise exception.NetworkNotFound(net_id=network)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()

    obj = jsonutils.loads(resp_obj)
    return obj


def get_all_networks(controller, tenant_id, networks):
    """Append the quantum network uuids we can find in the given controller to
       "networks"
       """
    uri = "/ws.v1/lswitch?fields=*&tag=%s&tag_scope=os_tid" % tenant_id
    try:
        resp_obj = do_single_request("GET", uri, controller=controller)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    if not resp_obj:
        return []
    lswitches = jsonutils.loads(resp_obj)["results"]
    for lswitch in lswitches:
        net_id = lswitch["uuid"]
        if net_id not in [x["net-id"] for x in networks]:
            networks.append({"net-id": net_id,
                             "net-name": lswitch["display_name"]})
    return networks


def query_networks(controller, tenant_id, fields="*", tags=None):
    uri = "/ws.v1/lswitch?fields=%s" % fields
    if tags:
        for t in tags:
            uri += "&tag=%s&tag_scope=%s" % (t[0], t[1])
    try:
        resp_obj = do_single_request("GET", uri, controller=controller)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    if not resp_obj:
        return []
    lswitches = jsonutils.loads(resp_obj)["results"]
    nets = [{'net-id': lswitch["uuid"],
             'net-name': lswitch["display_name"]}
            for lswitch in lswitches]
    return nets


def delete_network(controller, network):
    delete_networks(controller, [network])


def delete_networks(controller, networks):
    for network in networks:
        path = "/ws.v1/lswitch/%s" % network

        try:
            do_single_request("DELETE", path, controller=controller)
        except NvpApiClient.ResourceNotFound as e:
            LOG.error("Network not found, Error: %s" % str(e))
            raise exception.NetworkNotFound(net_id=network)
        except NvpApiClient.NvpApiException as e:
            raise exception.QuantumException()


def create_network(tenant_id, net_name, **kwargs):
    controller = kwargs["controller"]

    transport_zone = kwargs.get("transport_zone",
                                controller.default_tz_uuid)
    transport_type = kwargs.get("transport_type", "gre")
    lswitch_obj = {
        "display_name": net_name,
        "transport_zones": [{
            "zone_uuid": transport_zone,
            "transport_type": transport_type,
        }],
        "tags": [{"tag": tenant_id, "scope": "os_tid"}],
    }

    net = create_lswitch(controller, lswitch_obj)
    net['net-op-status'] = "UP"
    return net

#---------------------------------------------------------------------
# Port functions
#---------------------------------------------------------------------


def get_port_stats(controller, network_id, port_id):
    try:
        do_single_request("GET", "/ws.v1/lswitch/%s" % (network_id),
                          controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Network not found, Error: %s" % str(e))
        raise exception.NetworkNotFound(net_id=network_id)
    try:
        path = "/ws.v1/lswitch/%s/lport/%s/statistic" % (network_id, port_id)
        resp = do_single_request("GET", path, controller=controller)
        stats = jsonutils.loads(resp)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Port not found, Error: %s" % str(e))
        raise exception.PortNotFound(port_id=port_id, net_id=network_id)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    LOG.debug("Returning stats for port \"%s\" on \"%s\": %s" % (port_id,
                                                                 network_id,
                                                                 stats))
    return stats


def check_port_state(state):
    if state not in ["ACTIVE", "DOWN"]:
        LOG.error("Invalid port state (ACTIVE and DOWN are valid states): %s" %
                  state)
        raise exception.StateInvalid(port_state=state)


def query_ports(controller, network, relations=None, fields="*", filters=None):
    uri = "/ws.v1/lswitch/" + network + "/lport?"
    if relations:
        uri += "relations=%s" % relations
    uri += "&fields=%s" % fields
    if filters and "attachment" in filters:
        uri += "&attachment_vif_uuid=%s" % filters["attachment"]
    try:
        resp_obj = do_single_request("GET", uri, controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Network not found, Error: %s" % str(e))
        raise exception.NetworkNotFound(net_id=network)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    return jsonutils.loads(resp_obj)["results"]


def delete_port(controller, network, port):
    uri = "/ws.v1/lswitch/" + network + "/lport/" + port
    try:
        do_single_request("DELETE", uri, controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Port or Network not found, Error: %s" % str(e))
        raise exception.PortNotFound(port_id=port, net_id=network)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()


def delete_all_ports(controller, ls_uuid):
    res = do_single_request("GET", "/ws.v1/lswitch/%s/lport?fields=uuid" %
                            ls_uuid, controller=controller)
    res = jsonutils.loads(res)
    for r in res["results"]:
        do_single_request(
            "DELETE",
            "/ws.v1/lswitch/%s/lport/%s" % (ls_uuid, r["uuid"]),
            controller=controller)


def get_port(controller, network, port, relations=None):
    uri = "/ws.v1/lswitch/" + network + "/lport/" + port + "?"
    if relations:
        uri += "relations=%s" % relations
    try:
        resp_obj = do_single_request("GET", uri, controller=controller)
        port = jsonutils.loads(resp_obj)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Port or Network not found, Error: %s" % str(e))
        raise exception.PortNotFound(port_id=port, net_id=network)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    return port


def plug_interface(controller, network, port, type, attachment=None):
    uri = "/ws.v1/lswitch/" + network + "/lport/" + port + "/attachment"

    lport_obj = {}
    if attachment:
        lport_obj["vif_uuid"] = attachment

    lport_obj["type"] = type
    try:
        resp_obj = do_single_request("PUT",
                                     uri,
                                     jsonutils.dumps(lport_obj),
                                     controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Port or Network not found, Error: %s" % str(e))
        raise exception.PortNotFound(port_id=port, net_id=network)
    except NvpApiClient.Conflict as e:
        LOG.error("Conflict while making attachment to port, "
                  "Error: %s" % str(e))
        raise exception.AlreadyAttached(att_id=attachment,
                                        port_id=port,
                                        net_id=network,
                                        att_port_id="UNKNOWN")
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()

    result = jsonutils.dumps(resp_obj)
    return result


def unplug_interface(controller, network, port):
    uri = "/ws.v1/lswitch/" + network + "/lport/" + port + "/attachment"
    lport_obj = {"type": "NoAttachment"}
    try:
        resp_obj = do_single_request("PUT",
                                     uri,
                                     jsonutils.dumps(lport_obj),
                                     controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Port or Network not found, Error: %s" % str(e))
        raise exception.PortNotFound(port_id=port, net_id=network)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    return jsonutils.loads(resp_obj)


def update_port(network, port_id, **params):
    controller = params["controller"]
    lport_obj = {}

    if "state" in params:
        state = params["state"]
        check_port_state(state)
        admin_status = True
        if state == "DOWN":
            admin_status = False
        lport_obj["admin_status_enabled"] = admin_status

    uri = "/ws.v1/lswitch/" + network + "/lport/" + port_id
    try:
        resp_obj = do_single_request("PUT",
                                     uri,
                                     jsonutils.dumps(lport_obj),
                                     controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Port or Network not found, Error: %s" % str(e))
        raise exception.PortNotFound(port_id=port_id, net_id=network)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()

    obj = jsonutils.loads(resp_obj)
    obj["port-op-status"] = get_port_status(controller, network, obj["uuid"])
    return obj


def create_port(tenant, network, port_init_state, **params):
    # Check initial state -- this throws an exception if the port state is
    # invalid
    check_port_state(port_init_state)

    controller = params["controller"]

    ls_uuid = network

    admin_status = True
    if port_init_state == "DOWN":
        admin_status = False
    lport_obj = {"admin_status_enabled": admin_status}

    path = "/ws.v1/lswitch/" + ls_uuid + "/lport"
    try:
        resp_obj = do_single_request("POST",
                                     path,
                                     jsonutils.dumps(lport_obj),
                                     controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Network not found, Error: %s" % str(e))
        raise exception.NetworkNotFound(net_id=network)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()

    result = jsonutils.loads(resp_obj)
    result['port-op-status'] = get_port_status(controller, ls_uuid,
                                               result['uuid'])
    return result


def get_port_status(controller, lswitch_id, port_id):
    """Retrieve the operational status of the port"""
    # Make sure the network exists first
    try:
        do_single_request("GET", "/ws.v1/lswitch/%s" % (lswitch_id),
                          controller=controller)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Network not found, Error: %s" % str(e))
        raise exception.NetworkNotFound(net_id=lswitch_id)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    try:
        r = do_single_request(
            "GET",
            "/ws.v1/lswitch/%s/lport/%s/status" % (lswitch_id, port_id),
            controller=controller)
        r = jsonutils.loads(r)
    except NvpApiClient.ResourceNotFound as e:
        LOG.error("Port not found, Error: %s" % str(e))
        raise exception.PortNotFound(port_id=port_id, net_id=lswitch_id)
    except NvpApiClient.NvpApiException as e:
        raise exception.QuantumException()
    if r['link_status_up'] is True:
        return "UP"
    else:
        return "DOWN"
