#
# Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
#
# This file deals with the ifmap id handling for both vnc-user-visible entities
# and bgp-visible entities

import uuid
import re
import StringIO
from lxml import etree
from ifmap.client import client, namespaces
from ifmap.request import NewSessionRequest, RenewSessionRequest, \
    EndSessionRequest, PublishRequest, SearchRequest, \
    SubscribeRequest, PurgeRequest, PollRequest
from ifmap.id import IPAddress, MACAddress, Device, AccessRequest, Identity, \
    CustomIdentity
from ifmap.operations import PublishUpdateOperation, PublishNotifyOperation, \
    PublishDeleteOperation, SubscribeUpdateOperation,\
    SubscribeDeleteOperation
from ifmap.util import attr, link_ids
from ifmap.response import Response, newSessionResult
from ifmap.metadata import Metadata


_TENANT_GRP = "(?P<tenant_uuid>.*)"
_VPC_GRP = "(?P<vpc_name>.*)"
_VN_GRP = "(?P<vn_name>.*)"
_SG_GRP = "(?P<sg_name>.*)"
_POL_GRP = "(?P<pol_name>.*)"
_INST_GRP = "(?P<instance_uuid>.*)"
_PORT_GRP = "(?P<port_id>.*)"

_TENANT_ID_RE = "contrail:tenant:%s" % (_TENANT_GRP)
_VPC_NAME_RE = "contrail:network-group:%s:%s" % (_TENANT_GRP, _VPC_GRP)
_VN_NAME_RE = "contrail:virtual-network:%s:%s:%s" % (
    _TENANT_GRP, _VPC_GRP, _VN_GRP)
_SG_NAME_RE = "contrail:security-group:%s:%s:%s" % (
    _TENANT_GRP, _VPC_GRP, _SG_GRP)
_POL_NAME_RE = "contrail:policy:%s:%s:%s" % (_TENANT_GRP, _VPC_GRP, _POL_GRP)
_INST_ID_RE = "contrail:instance:%s:%s:%s:%s" \
    % (_TENANT_GRP, _VPC_GRP, _VN_GRP, _INST_GRP)
_PORT_ID_RE = "contrail:port:%s:%s:%s:%s:%s" \
    % (_TENANT_GRP, _VPC_GRP, _VN_GRP, _INST_GRP, _PORT_GRP)

_CT_NS = "contrail"
_ROOT_IMID = _CT_NS + ":config-root:root"

_SOAP_XSD = "http://www.w3.org/2003/05/soap-envelope"
_IFMAP_XSD = "http://www.trustedcomputinggroup.org/2010/IFMAP/2"
_IFMAP_META_XSD = "http://www.trustedcomputinggroup.org/2010/IFMAP-METADATA/2"
_CONTRAIL_XSD = "http://www.contrailsystems.com/vnc_cfg.xsd"

# Parse ifmap-server returned search results and create list of tuples
# of (ident-1, ident-2, link-attribs)


def parse_result_items(result_items, my_imid=None):
    all_result_list = []
    for r_item in result_items:
        children = r_item.getchildren()
        num_children = len(children)
        if num_children == 1:  # ignore ident-only result-items
            continue
        elif num_children == 2:
            result_info = [children[0], None, children[1]]
        elif num_children == 3:
            result_info = [children[0], children[1], children[2]]
        else:
            raise Exception('Result item of length %s not handled!'
                            % (num_children))
        all_result_list.append(result_info)

    if not my_imid:
        return all_result_list

    # strip ones that don't originate from or to my_imid
    filtered_result_list = []
    for (ident_1, ident_2, meta) in all_result_list:
        if (((ident_2 is not None) and (ident_2.attrib['name'] == my_imid)) or
                (ident_1.attrib['name'] == my_imid)):
            if meta is None:
                filtered_result_list.append((ident_1, ident_2, None))
            else:
                # search gives all props under one metadata. expand it.
                for m_elem in meta:
                    filtered_result_list.append((ident_1, ident_2, m_elem))

    return filtered_result_list
# end parse_result_items


def get_ifmap_id_from_fq_name(type, fq_name):
    my_fqn = ':' + ':'.join(fq_name)
    my_imid = 'contrail:' + type + my_fqn

    return my_imid
# end get_ifmap_id_from_fq_name


def get_type_from_ifmap_id(ifmap_id):
    type = ifmap_id.split(':')[1]
    return type
# end get_type_from_ifmap_id


def get_fq_name_str_from_ifmap_id(ifmap_id):
    return re.sub(r'contrail:.*?:', '', ifmap_id)
# end get_fq_name_str_from_ifmap_id


def get_fq_name_from_ifmap_id(ifmap_id):
    return ifmap_id.split(':')[2:]
# end get_fq_name_from_ifmap_id


def subscribe_root(ssrc_mapc):
    #self._ident_type_subscribe(_CLOUD_IMID, "ct:member-of")
    ident = str(Identity(name=_ROOT_IMID, type="other",
                         other_type="extended"))
    subreq = SubscribeRequest(
        ssrc_mapc.get_session_id(),
        operations=str(SubscribeUpdateOperation("root", ident,
                                                {"max-depth": "255", })))

    result = ssrc_mapc.call('subscribe', subreq)
# end _subscribe_root


def ssrc_initialize(args):
    ssrc_mapc = ifmap_server_connect(args)
    result = ssrc_mapc.call('newSession', NewSessionRequest())
    ssrc_mapc.set_session_id(newSessionResult(result).get_session_id())
    ssrc_mapc.set_publisher_id(newSessionResult(result).get_publisher_id())
    subscribe_root(ssrc_mapc)
    return ssrc_mapc
# end ssrc_initialize


def arc_initialize(args, ssrc_mapc):
    #
    # Poll requests go on ARC channel which don't do newSession but
    # share session-id with ssrc channel. so 2 connections to server but 1
    # session/session-id in ifmap-server (mamma mia!)
    #
    arc_mapc = ifmap_server_connect(args)
    arc_mapc.set_session_id(ssrc_mapc.get_session_id())
    arc_mapc.set_publisher_id(ssrc_mapc.get_publisher_id())

    return arc_mapc
# end arc_initialize


def ifmap_server_connect(args):
    _CLIENT_NAMESPACES = {
        'env':  _SOAP_XSD,
        'ifmap':  _IFMAP_XSD,
        'meta':  _IFMAP_META_XSD,
        _CT_NS:  _CONTRAIL_XSD
    }

    ssl_options = None
    if args.use_certs:
        ssl_options = {
            'keyfile': args.keyfile,
            'certfile': args.certfile,
            'ca_certs': args.ca_certs,
            'cert_reqs': ssl.CERT_REQUIRED,
            'ciphers': 'ALL'
        }
    return client(("%s" % (args.ifmap_server_ip),
                   "%s" % (args.ifmap_server_port)),
                  args.ifmap_username, args.ifmap_password,
                  _CLIENT_NAMESPACES, ssl_options)
# end ifmap_server_connect


def parse_poll_result(poll_result_str):
    _XPATH_NAMESPACES = {
        'a': _SOAP_XSD,
        'b': _IFMAP_XSD,
        'c': _CONTRAIL_XSD
    }

    soap_doc = etree.parse(StringIO.StringIO(poll_result_str))
    #soap_doc.write(sys.stdout, pretty_print=True)

    xpath_error = '/a:Envelope/a:Body/b:response/errorResult'
    error_results = soap_doc.xpath(xpath_error,
                                   namespaces=_XPATH_NAMESPACES)

    if error_results:
        raise Exception(etree.tostring(error_results[0]))

    xpath_expr = '/a:Envelope/a:Body/b:response/pollResult'
    poll_results = soap_doc.xpath(xpath_expr,
                                  namespaces=_XPATH_NAMESPACES)

    result_list = []
    for result in poll_results:
        children = result.getchildren()
        for child in children:
            result_type = child.tag
            if result_type == 'errorResult':
                raise Exception(etree.tostring(child))

            result_items = child.getchildren()
            item_list = parse_result_items(result_items)
            for item in item_list:
                ident1 = item[0]
                ident2 = item[1]
                meta = item[2]
                idents = {}
                ident1_imid = ident1.attrib['name']
                ident1_type = get_type_from_ifmap_id(ident1_imid)
                idents[ident1_type] = get_fq_name_str_from_ifmap_id(
                    ident1_imid)
                if ident2 is not None:
                    ident2_imid = ident2.attrib['name']
                    ident2_type = get_type_from_ifmap_id(ident2_imid)
                    if ident1_type == ident2_type:
                        idents[ident1_type] = [
                            idents[ident1_type],
                            get_fq_name_str_from_ifmap_id(ident2_imid)]
                    else:
                        idents[ident2_type] = get_fq_name_str_from_ifmap_id(
                            ident2_imid)
                result_list.append((result_type, idents, meta))
    return result_list
# end parse_poll_result
