#!/usr/bin/env python
#
# Copyright 2014 Orange
#
# 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 os
import urllib.error
import urllib.request

import optparse
from oslo_config import cfg
from oslo_serialization import jsonutils

from networking_bagpipe.bagpipe_bgp.api import config as api_config


LOOKING_GLASS_BASE = "looking-glass"


def request(options, server, args):
    target_url = "http://%s:%d/%s/%s" % (server, options.port,
                                         options.prefix, "/".join(args))
    try:
        os.environ['NO_PROXY'] = server
        os.environ['no_proxy'] = server
        response = urllib.request.urlopen(target_url)

        if response.getcode() == 200:
            return jsonutils.load(response)
    except urllib.error.HTTPError as e:
        if e.code == 404:
            return {}
        print("Error requesting %s" % target_url)
        raise
    except Exception:
        print("Error requesting %s" % target_url)
        raise


def normalize(string):
    return (string
            .replace("target:", "rt:")
            .replace(":", "_")
            .replace("-", "_")
            .replace(".", "_"))


RT_TXT_STYLE = 'color="orange",fontcolor="orange"'
RT_STYLE = 'color="orange",fontcolor="orange",arrowhead=onormal'
RT_STYLE_REDIR_FROM_RT = ('color="orange",fontcolor="orange",style=dashed,'
                          'arrowhead=diamond,arrowtail=oinv,headclip=false,'
                          'dir=both')
RT_STYLE_REDIR_TO_RT = ('color="orange",fontcolor="orange",style=dashed,'
                        'arrowhead=onormal,tailclip=false,dir=both,'
                        'arrowtail=oinv')
RT_STYLE_FLOWSPEC = 'color="red",fontcolor="red",style=dotted'
RT_STYLE_FLOWSPEC_INTER = ('color="red",fontcolor="red",style=dotted,'
                           'arrowhead=none,dir=both,arrowtail=oinv')
RT_STYLE_FLOWSPEC_ACTION = 'color="red",fontcolor="red",style=dashed'
PORT_LINK_STYLE = 'style=dashed,dir=none,color="gray",weight=3'


def get_all(options):
    rts = set()
    vpns = set()

    for server in options.servers:
        for vpn in request(options, server, ["vpns", "instances"]):
            vpn_id = vpn['id']
            type = vpn['name'][:3]
            vpns.add((server, vpn_id, type))
            for rt_i in request(options, server, ["vpns", "instances", vpn_id,
                                                  "route_targets", "import"]):
                rts.add(normalize(rt_i))
            for rt_e in request(options, server, ["vpns", "instances", vpn_id,
                                                  "route_targets", "export"]):
                rts.add(normalize(rt_e))

            readvertise = request(options, server, ["vpns", "instances",
                                                    vpn_id, "readvertise"])
            if readvertise:
                rts.add(normalize(readvertise['to'][0]))
                rts.add(normalize(readvertise['from'][0]))

                attract = readvertise.get('attract_traffic', None)
                if attract:
                    rts |= {normalize(rt)
                            for rt in attract['redirect_rts']}

    return (rts, vpns)


def vpn_uid(server, vpn):
    return "vpn_{}__{}".format(normalize(server), normalize(vpn))


def vpn_short(vpn):
    if len(vpn) > 11:
        return vpn[:4] + ".." + vpn[-5:]
    else:
        return vpn


def main():
    api_config.register_config()
    try:
        cfg.CONF(args=[],
                 project='bagpipe-impex2dot',
                 default_config_files=['/etc/bagpipe-bgp/bgp.conf'])
        api_port = cfg.CONF.API.port
    except cfg.ConfigFilesNotFoundError:
        api_port = api_config.DEFAULT_PORT

    usage = """ %prog [options]

Example: bagpipe-impex2dot --server s1 --server s2 | dot -Tpdf > impex.pdf
    """
    parser = optparse.OptionParser(usage)

    parser.add_option(
        "--server", dest="servers", default=[], action="append",
        help="IP address of a BaGPipe BGP instances (default: localhost)")

    parser.add_option(
        "--port", dest="port", type="int", default=api_port,
        help="Port of BaGPipe BGP (optional, default: %default)")

    parser.add_option(
        "--prefix", dest="prefix", default=LOOKING_GLASS_BASE,
        help="Looking-glass URL Prefix (optional, default: %default)")

    (options, _) = parser.parse_args()

    if len(options.servers) == 0:
        options.servers = ["localhost"]

    ports = set()
    dests = set()
    print('digraph import_export {')

    print('   node [fontname="Helvetica"];')
    print('   edge [fontname="Helvetica"];')
    print('   nodesep=0.55;')

    (rts, vpns) = get_all(options)
    print('   subgraph rts {')
    print('       rank=same;')
    for rt in rts:
        label = rt.upper().replace('_', '\\n')
        print('        {} [shape="circle",label="{}",{}];'.format(
            rt, label, RT_TXT_STYLE))
    print('    }')

    print('    subgraph ipvpns {')
    print('        rank=same;')
    for (server, vpn, _) in filter(lambda x: x[2] == 'VRF', vpns):
        server_spec = ("\\n[%s]" % server) if server != 'localhost' else ''
        print('        %s [label="{<0>VRF\\n%s%s|<readv>}",'
              'shape="record"];' % (vpn_uid(server, vpn), vpn_short(vpn),
                                    server_spec))
    print('    }')

    print('    subgraph evpns {')
    for (server, vpn, _) in filter(lambda x: x[2] == 'EVI', vpns):
        server_spec = ("\\n[%s]" % server) if server != 'localhost' else ''
        print('        %s [label="{<0>EVI\\n%s%s}",'
              'shape="record"];' % (vpn_uid(server, vpn), vpn_short(vpn),
                                    server_spec))
    print('    }')

    for (server, vpn, _) in vpns:
        print('    /* {}:{} */'.format(server, vpn))
        uid = vpn_uid(server, vpn)

        for rt_i in request(options, server, ["vpns", "instances", vpn,
                                              "route_targets", "import"]):
            print('    {} -> {}:0 [{}];'.format(normalize(rt_i), uid,
                                                RT_STYLE))

        for rt_e in request(options, server, ["vpns", "instances", vpn,
                                              "route_targets", "export"]):
            print('    {}:0 -> {} [{}];'.format(uid, normalize(rt_e),
                                                RT_STYLE))

        readvertise = request(options, server, ["vpns", "instances",
                                                vpn, "readvertise"])
        if readvertise:
            readv = "%s:readv" % vpn_uid(server, vpn)
            print('    %s -> %s [label="",%s];' %
                  (normalize(readvertise['from'][0]),
                   readv,
                   RT_STYLE_REDIR_FROM_RT))
            print('    %s -> %s [label="",%s];' %
                  (readv,
                   normalize(readvertise['to'][0]),
                   RT_STYLE_REDIR_TO_RT))

            attract = readvertise.get('attract_traffic', None)
            if attract:
                intermediate = "{}_{}_attract".format(normalize(server),
                                                      normalize(vpn))
                print('    %s [style=invis,height=0,width=0,'
                      'fixedsize=true,rank=1]' % intermediate)

                redir_rt = attract['redirect_rts'][0]

                # readv -> intermediate
                print('    %s -> %s [label="flow",%s];' %
                      (readv, intermediate,
                       RT_STYLE_FLOWSPEC_INTER))

                # intermediate -> flowspec route RT
                print('    %s -> %s [label="",weight=50,%s];' %
                      (intermediate, normalize(redir_rt),
                       RT_STYLE_FLOWSPEC))

                # intermediate -> flowspec route redirect action  RT
                print('    %s -> %s [label="action",%s,arrowhead=none'
                      ',weight=20];' %
                      (intermediate, normalize(readvertise['to'][0]),
                       RT_STYLE_FLOWSPEC_ACTION))

        for port in request(options, server, ["vpns", "instances",
                                              vpn, "ports"]).keys():
            print('    {} -> port_{}_{} [{},weight=5];'.format(
                uid,
                normalize(server),
                normalize(port),
                PORT_LINK_STYLE))
            ports.add((server, normalize(port)))

        # possible link between an E-VPN and an IPVPN ?
        ipvpn = request(options, server, ["vpns", "instances",
                                          vpn, "gateway_port", "ipvpn"])
        if ipvpn:
            ipvpn_id = ipvpn['external_instance_id']
            print('    {} -> {} [weight=500];'.format(
                uid,
                vpn_uid(server, ipvpn_id)))

    for (server, port) in ports:
        print('    port_%s_%s [label="",style=invis,height=0,width=0,'
              'fixedsize=true];' % (normalize(server),
                                    normalize(port)))
        if port.startswith('to_'):
            dest = port.split('_')[1]
            print('    port_%s_%s -> dest_%s_%s [style=dashed,'
                  'dir=none,color="gray"'
                  ',weight=5];' %
                  (normalize(server), normalize(port), normalize(server),
                   dest))
            dests.add((server, dest))

    for (server, dest) in dests:
        print('    dest_%s_%s [label="%s\\n[%s]",shape="square",'
              'color="gray"];' % (normalize(server),
                                  normalize(dest), dest, server))

    print('}')
