/*
 * Copyright (C) 2020 Sylvain Afchain
 *
 * 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 { Node, Link, NodeAttrs, LinkAttrs } from './Topology'
import Tools from './Tools'

const SHOW_DEBUG = false

const WEIGHT_K8S_FEDERATION = 3000
const WEIGHT_K8S_CLUSTER = 3010
const WEIGHT_K8S_NODE = 3020
const WEIGHT_K8S_NAMESPACE = 3030
const WEIGHT_K8S_POD = 3040
const WEIGHT_K8S_CONTAINER = 3050
const WEIGHT_K8S_SERVICE = 3060
const WEIGHT_K8S_OTHER = 3200

const WEIGHT_PHY_FABRIC = 5010
const WEIGHT_PHY_HOST = 5020
const WEIGHT_PHY_BRIDGES = 5030
const WEIGHT_PHY_NET = 5050
const WEIGHT_PHY_PORTS = 5060

const WEIGHT_VIRT_NAMESPACE = 7010
const WEIGHT_VIRT_VMS = 7020
const WEIGHT_VIRT_CONTAINERS = 7030
const WEIGHT_VIRT_BRIDGES = 7040
const WEIGHT_VIRT_NET = 7050
const WEIGHT_VIRT_PORTS = 7060

const WEIGHT_NONE = 20000

export interface Filter {
    id: string
    label: string
    category: string
    tag?: string
    callback: () => void
}

export interface MenuItem {
    class: string
    text: string
    disabled: boolean
    callback: () => void
}

export interface GraphField {
    type: string,
    data: any
}

export interface NodeDataField {
    field: string
    title?: string
    expanded: boolean
    icon: string
    iconClass?: string
    sortKeys?: (data: any) => Array<string>
    filterKeys?: (data: any) => Array<string>
    normalizer?: (data: any) => any
    graph?: (data: any) => GraphField
}

export interface LinkDataField {
    field: string
    title: string
    expanded: boolean
    icon: string
}

export interface Config {
    subTitle?(subTitle: string): string
    filters?(): Promise<Array<Filter>>
    defaultFilter?(): Filter

    nodeAttrs?(attrs: NodeAttrs | null, node: Node): NodeAttrs
    nodeSortFnc?(a: Node, b: Node): number
    nodeClicked?(node: Node): void
    nodeDblClicked?(node: Node): void

    nodeMenu?(items: Array<MenuItem>, node: Node): Array<MenuItem>
    nodeTags?(tags: Array<string>, node: Node): Array<string>

    defaultNodeTag?(): string
    nodeTabTitle?(node: Node): string

    groupSize?(): number
    groupType?(node: Node): string | undefined
    groupName?(node: Node): string | undefined
    weightTitles?(): Map<number, string>

    suggestions?(): Array<string>

    nodeDataFields?(dataFields: Array<NodeDataField>): Array<NodeDataField>

    linkAttrs?(attrs: LinkAttrs | null, link: Link): LinkAttrs
    linkTabTitle?(link: Link): string

    linkDataFields?(dataFields: Array<LinkDataField>): Array<LinkDataField>

    defaultLinkTagMode?(): number
}

class ConfigWithID {
    id: string
    config: Config

    constructor(id: string, config: Config) {
        this.id = id
        this.config = config
    }
}

export default class ConfigReducer {
    default: DefaultConfig
    configs: Array<ConfigWithID>

    constructor() {
        this.default = new DefaultConfig()
        this.configs = new Array<ConfigWithID>()
    }

    append(id: string, config: Config) {
        this.configs.push(new ConfigWithID(id, config))
    }

    appendURL(id: string, url: string): Promise<Config | undefined> {
        var promise = new Promise<Config>((resolve, reject) => {
            if (!url) {
                resolve()
                return
            }

            fetch(url).then(resp => {
                resp.text().then(data => {
                    try {
                        var config = eval(data)
                        this.append(id, config)

                        resolve(config)
                    } catch (e) {
                        reject(e)
                    }
                })
            }).catch((reason) => {
                throw Error(reason)
            })
        })

        return promise
    }

    subTitle(): string {
        var subTitle = this.default.subTitle()
        for (let c of this.configs) {
            if (c.config.subTitle) {
                subTitle = c.config.subTitle(subTitle)
            }
        }
        return subTitle
    }

    filters(): Promise<Array<Filter>> {
        var promise = new Promise<Array<Filter>>(resolve => {
            this.default.filters().then(filters => {
                var all = new Array<Promise<Array<Filter>>>()
                for (let c of this.configs) {
                    if (c.config.filters) {
                        all.push(c.config.filters())
                    }
                }

                if (all.length > 0) {
                    Promise.all(all).then(values => {
                        for (let vfs of values) {
                            for (let filter of vfs) {
                                if (!filters.some(f => filter.id === f.id)) {
                                    filters.push(filter)
                                }
                            }
                        }
                        resolve(filters)
                    })
                } else {
                    resolve(filters)
                }
            })
        })
        return promise
    }

    defaultFilter(): Filter {
        var defaultFilter = this.default.defaultFilter()
        for (let c of this.configs) {
            if (c.config.defaultFilter) {
                defaultFilter = c.config.defaultFilter()
            }
        }
        return defaultFilter
    }

    nodeAttrs(node: Node): NodeAttrs {
        var attrs = this.default.nodeAttrs(node)
        for (let c of this.configs) {
            if (c.config.nodeAttrs) {
                attrs = c.config.nodeAttrs(attrs, node)
            }
        }
        return attrs
    }

    nodeSortFnc(a: Node, b: Node): number {
        var fnc = this.default.nodeSortFnc
        for (let c of this.configs) {
            if (c.config.nodeSortFnc) {
                fnc = c.config.nodeSortFnc
            }
        }
        return fnc(a, b)
    }

    nodeClicked(node: Node): void {
        var fnc = this.default.nodeClicked
        for (let c of this.configs) {
            if (c.config.nodeClicked) {
                fnc = c.config.nodeClicked
            }
        }
        return fnc(node)
    }

    nodeDblClicked(node: Node): void {
        var fnc = this.default.nodeDblClicked
        for (let c of this.configs) {
            if (c.config.nodeDblClicked) {
                fnc = c.config.nodeDblClicked
            }
        }
        return fnc(node)
    }

    nodeMenu(node: Node): Array<MenuItem> {
        var items = this.default.nodeMenu(node)
        for (let c of this.configs) {
            if (c.config.nodeMenu) {
                items = c.config.nodeMenu(items, node)
            }
        }
        return items
    }

    nodeTags(node: Node): Array<string> {
        var tags = this.default.nodeTags(node)
        for (let c of this.configs) {
            if (c.config.nodeTags) {
                tags = c.config.nodeTags([], node)
            }
        }
        return tags
    }

    defaultNodeTag(): string {
        var defaultNodeTag = this.default.defaultNodeTag()
        for (let c of this.configs) {
            if (c.config.defaultNodeTag) {
                defaultNodeTag = c.config.defaultNodeTag()
            }
        }
        return defaultNodeTag
    }

    nodeTabTitle(node: Node): string {
        var nodeTabTitle = this.default.nodeTabTitle(node)
        for (let c of this.configs) {
            if (c.config.nodeTabTitle) {
                nodeTabTitle = c.config.nodeTabTitle(node)
            }
        }
        return nodeTabTitle
    }

    groupSize(): number {
        var size = this.default.groupSize()
        for (let c of this.configs) {
            if (c.config.groupSize) {
                size = c.config.groupSize()
            }
        }
        return size
    }

    groupType(node: Node): string | undefined {
        var groupType = this.default.groupType(node)
        for (let c of this.configs) {
            if (c.config.groupType) {
                groupType = c.config.groupType(node)
            }
        }
        return groupType
    }

    groupName(node: Node): string | undefined {
        var groupName = this.default.groupName(node)
        for (let c of this.configs) {
            if (c.config.groupName) {
                groupName = c.config.groupName(node)
            }
        }
        return groupName
    }

    weightTitles(): Map<number, string> {
        var titles = this.default.weightTitles()
        for (let c of this.configs) {
            if (c.config.weightTitles) {
                titles = c.config.weightTitles()
            }
        }
        return titles
    }

    suggestions(): Array<string> {
        var result = this.default.suggestions()
        for (let c of this.configs) {
            if (c.config.suggestions) {
                result = c.config.suggestions()
            }
        }
        return result
    }

    nodeDataFields(): Array<NodeDataField> {
        var fields = this.default.nodeDataFields()
        for (let c of this.configs) {
            if (c.config.nodeDataFields) {
                fields = c.config.nodeDataFields(fields)
            }
        }
        return fields
    }

    linkAttrs(link: Link): LinkAttrs {
        var attrs = this.default.linkAttrs(link)
        for (let c of this.configs) {
            if (c.config.linkAttrs) {
                attrs = c.config.linkAttrs(attrs, link)
            }
        }
        return attrs
    }

    linkTabTitle(link: Link): string {
        var title = this.default.linkTabTitle(link)
        for (let c of this.configs) {
            if (c.config.linkTabTitle) {
                title = c.config.linkTabTitle(link)
            }
        }
        return title
    }

    linkDataFields(): Array<LinkDataField> {
        var fields = this.default.linkDataFields()
        for (let c of this.configs) {
            if (c.config.linkDataFields) {
                fields = c.config.linkDataFields(fields)
            }
        }
        return fields
    }

    defaultLinkTagMode(): number {
        var size = this.default.defaultLinkTagMode()
        for (let c of this.configs) {
            if (c.config.defaultLinkTagMode) {
                size = c.config.defaultLinkTagMode()
            }
        }
        return size
    }
}

class DefaultConfig {
    subTitle(): string {
        return ""
    }

    filters(): Promise<Array<Filter>> {
        var promise = new Promise<Array<Filter>>(resolve => {

            const nf = (name: string, type: string, tag: string, limit: number) => {
                return {
                    id: name,
                    label: name,
                    category: type,
                    tag: tag,
                    callback: () => {
                        var gremlin = "G.V().Has(" +
                            "'Name','" + name + "'," +
                            "'Type','" + type + "').descendants(10).as('k8s').Out().In().Has('Type', 'netns').Descendants(10).as('infra').select('k8s', 'infra').SubGraph()"
                        window.App.setGremlinFilter(gremlin)
                    }
                }
            }

            var filters = new Array<Filter>()

            // TODO replace by only one query once merged:
            // https://github.com/skydive-project/skydive/pull/2338
            var api = new window.API.TopologyApi(window.App.apiConf)
            api.searchTopology({ GremlinQuery: `G.V().Has("Type", "host").Values("Name")` }).then(result => {
                for (let name of result) {
                    filters.push(nf(name, "host", "infrastucture", 1))
                }

                api.searchTopology({ GremlinQuery: `G.V().Has("Type", "namespace").Values("Name")` }).then(result => {
                    if (result) {
                        for (let name of result) {
                            filters.push(nf(name, "namespace", "kubernetes", 10))
                        }

                        resolve(filters)
                    }
                })
            })
        })

        return promise
    }

    defaultFilter(): Filter {
        return {
            id: "default",
            label: "Default",
            category: "default",
            tag: "infrastructure",
            callback: () => {
                var gremlin = ""
                window.App.setGremlinFilter(gremlin)
            }
        }
    }

    private newAttrs(node: Node): NodeAttrs {
        var name = node.data.Name
        if (name.length > 24) {
            name = node.data.Name.substring(0, 24) + "."
        }

        var attrs = {
            classes: [node.data.Type],
            name: name,
            icon: "\uf192",
            href: '',
            iconClass: '',
            weight: 0,
            badges: []
        }

        return attrs
    }

    nodeAttrs(node: Node): NodeAttrs {
        switch (node.data.Manager) {
            case "k8s":
                return this.nodeAttrsK8s(node)
            default:
                return this.nodeAttrsInfra(node)
        }
    }

    private nodeAttrsK8s(node: Node): NodeAttrs {
        var attrs = this.newAttrs(node)

        switch (node.data.Type) {
            case "cluster":
                attrs.href = "assets/icons/cluster.png"
                attrs.weight = WEIGHT_K8S_CLUSTER
                break
            /*
            case "configmap":
                attrs.href = "assets/icons/configmap.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "cronjob":
                attrs.href = "assets/icons/cronjob.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "daemonset":
                attrs.href = "assets/icons/daemonset.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "deployment":
                attrs.href = "assets/icons/deployment.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "endpoints":
                attrs.href = "assets/icons/endpoints.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "ingress":
                attrs.href = "assets/icons/ingress.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "job":
                attrs.href = "assets/icons/job.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "persistentvolume":
                attrs.href = "assets/icons/persistentvolume.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "persistentvolumeclaim":
                attrs.href = "assets/icons/persistentvolumeclaim.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "networkpolicy":
                attrs.href = "assets/icons/networkpolicy.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "replicaset":
                attrs.href = "assets/icons/replicaset.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "replicationcontroller":
                attrs.href = "assets/icons/replicationcontroller.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "secret":
                attrs.href = "assets/icons/secret.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "statefulset":
                attrs.href = "assets/icons/statefulset.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "storageclass":
                attrs.href = "assets/icons/storageclass.png"
                attrs.weight = WEIGHT_K8S_NODE
                break
            */
            case "node":
                attrs.icon = "\uf109"
                attrs.weight = WEIGHT_K8S_NODE
                break
            case "namespace":
                attrs.icon = "\uf24d"
                attrs.weight = WEIGHT_K8S_NAMESPACE
                break
            case "pod":
                attrs.href = "assets/icons/pod.png"
                attrs.weight = WEIGHT_K8S_POD
                break
            case "container":
                attrs.href = "assets/icons/container.png"
                attrs.weight = WEIGHT_K8S_CONTAINER
                break
            case "service":
                attrs.href = "assets/icons/service.png"
                attrs.weight = WEIGHT_K8S_SERVICE
                break
            default:
                attrs.href = "assets/icons/k8s.png"
                attrs.weight = WEIGHT_K8S_OTHER
        }

        if (SHOW_DEBUG) {
            attrs.name = attrs.weight.toString() + "|" + attrs.name
        }
        return attrs
    }

    private nodeAttrsInfra(node: Node): NodeAttrs {
        var attrs = this.newAttrs(node)

        switch (node.data.Type) {
            case "host":
                attrs.icon = "\uf109"
                attrs.weight = WEIGHT_PHY_HOST
                break
            case "switch":
            case "bridge":
                attrs.icon = "\uf6ff"
                attrs.weight = WEIGHT_PHY_BRIDGES
                break
            case "patch":
            case "port":
            case "switchport":
                attrs.icon = "\uf0e8"
                attrs.weight = WEIGHT_PHY_PORTS
                break
            case "erspan":
                attrs.icon = "\uf1e0"
                attrs.weight = WEIGHT_PHY_PORTS
                break
            case "device":
            case "internal":
            case "interface":
            case "tun":
            case "tap":
                attrs.icon = "\uf796"
                attrs.weight = WEIGHT_PHY_NET
                break
            case "geneve":
            case "vxlan":
            case "gre":
            case "gretap":
                attrs.icon = "\uf55b"
                attrs.weight = WEIGHT_VIRT_NET
                break
            case "veth":
                attrs.icon = "\uf4d7"
                attrs.weight = WEIGHT_VIRT_NET
                break
            case "ovsport":
                attrs.icon = "\uf0e8"
                attrs.weight = WEIGHT_VIRT_PORTS
                break
            case "ovsbridge":
            case "openvswitch":
                attrs.icon = "\uf6ff"
                attrs.weight = WEIGHT_VIRT_BRIDGES
                break
            case "netns":
                attrs.icon = "\uf24d"
                attrs.weight = WEIGHT_VIRT_NAMESPACE
                break
            case "libvirt":
                attrs.icon = "\uf109"
                attrs.weight = WEIGHT_VIRT_VMS
                break
            case "container":
                attrs.icon = "\uf49e"
                attrs.weight = WEIGHT_VIRT_CONTAINERS
                break
            default:
                attrs.icon = "\uf796"
                attrs.weight = WEIGHT_NONE
        }

        if (node.data.IPV4 && node.data.IPV4.length) {
            attrs.weight = WEIGHT_PHY_NET
        }

        if (node.data.Probe === "fabric") {
            attrs.weight = WEIGHT_PHY_FABRIC
        }

        if (node.data.OfPort) {
            attrs.weight = WEIGHT_VIRT_PORTS
        }

        var virt = ["tap", "veth", "tun", "openvswitch"]
        if (node.data.Driver && virt.indexOf(node.data.Driver) > 0) {
            attrs.weight = WEIGHT_VIRT_NET
        }

        if (node.data.Manager === "docker") {
            attrs.badges = [{ text: "\uf395", iconClass: 'font-brands', fill: '#3888ae', stroke: '#fff' }]
        } else if (node.data.Manager === "runc") {
            attrs.badges = [{ text: "\uf7bc", iconClass: 'font-brands', fill: '#000', stroke: '#f44336' }]
        }

        if (node.data.Captures) {
            attrs.badges = [{ text: "\uf03d" }]
        }

        if (SHOW_DEBUG) {
            attrs.name = attrs.weight.toString() + "|" + attrs.name
        }
        return attrs
    }

    nodeSortFnc(a: Node, b: Node): number {
        return a.data.Name.localeCompare(b.data.Name)
    }

    nodeClicked(node: Node): void {
        window.App.tc.selectNode(node.id)
    }

    nodeDblClicked(node: Node): void {
        window.App.tc.expand(node)
    }

    nodeMenu(node: Node): Array<MenuItem> {
        return [
            {
                class: "", text: "Capture", disabled: false, callback: () => {
                    var api = new window.API.CapturesApi(window.App.apiConf)
                    api.createCapture({ GremlinQuery: `G.V('${node.id}')` }).then(result => {
                        console.log(result)
                    })
                }
            },
            { class: "", text: "Capture all", disabled: true, callback: () => { console.log("Capture all") } },
            { class: "", text: "Injection", disabled: false, callback: () => { console.log("Injection") } },
            //{ class: "", text: "Flows", disabled: false, callback: () => { console.log("Flows") } },
            //{ class: "", text: "Filter NS(demo)", disabled: false, callback: () => { window.App.loadExtraConfig("/assets/nsconfig.js") } }
        ]
    }

    nodeTags(data: any): Array<string> {
        if (data.Manager && data.Manager === "k8s") {
            switch (data.Type) {
                case "namespace":
                case "pod":
                case "container":
                    return ["kubernetes", "compute", "network"]
                default:
                    return ["kubernetes"]
            }
        } else {
            switch (data.Type) {
                case "container":
                    return ["infrastructure", "compute", "network"]
                case "netns":
                case "veth":
                    return ["infrastructure", "network"]
                default:
                    return ["infrastructure"]
            }
        }
    }

    defaultNodeTag() {
        return "infrastructure"
    }

    nodeTabTitle(node: Node): string {
        return node.data.Name.substring(0, 8)
    }

    groupSize(): number {
        return 3
    }

    groupType(node: Node): string | undefined {
        var nodeType = node.data.Type
        if (!nodeType) {
            return
        }

        switch (nodeType) {
            case "configmap":
            case "cronjob":
            case "daemonset":
            case "deployment":
            case "endpoints":
            case "ingress":
            case "job":
            case "persistentvolume":
            case "persistentvolumeclaim":
            case "pod":
            case "networkpolicy":
            case "replicaset":
            case "replicationcontroller":
            case "secret":
            case "service":
            case "statefulset":
                return "app"
            default:
                return nodeType
        }
    }

    groupName(node: Node): string | undefined {
        if (node.data.K8s) {
            var labels = node.data.K8s.Labels
            if (!labels) {
                return name + "(s)"
            }

            var app = labels["k8s-app"] || labels["app"]
            if (!app) {
                return "default"
            }
            return app
        }

        var nodeType = this.groupType(node)
        if (!nodeType) {
            return
        }

        return nodeType + "(s)"
    }

    weightTitles(): Map<number, string> {
        var wt = new Map<number, string>()

        wt.set(WEIGHT_K8S_FEDERATION, "k8s-Federations")
        wt.set(WEIGHT_K8S_CLUSTER, "k8s-clusters")
        wt.set(WEIGHT_K8S_NODE, "k8s-nodes")
        wt.set(WEIGHT_K8S_NAMESPACE, "k8s-namespaces")
        wt.set(WEIGHT_K8S_POD, "k8s-pods")
        wt.set(WEIGHT_K8S_CONTAINER, "k8s-containers")
        wt.set(WEIGHT_K8S_SERVICE, "k8s-services")
        wt.set(WEIGHT_K8S_OTHER, "k8s-more")

        wt.set(WEIGHT_VIRT_VMS, "virt-VMs")
        wt.set(WEIGHT_VIRT_CONTAINERS, "virt-containers")
        wt.set(WEIGHT_VIRT_BRIDGES, "virt-bridges")
        wt.set(WEIGHT_VIRT_NAMESPACE, "virt-namespaces")
        wt.set(WEIGHT_VIRT_NET, "virt-net")
        wt.set(WEIGHT_VIRT_PORTS, "virt-ports")

        wt.set(WEIGHT_PHY_FABRIC, "phy-fabric")
        wt.set(WEIGHT_PHY_HOST, "phy-hosts")
        wt.set(WEIGHT_PHY_BRIDGES, "phy-bridges")
        wt.set(WEIGHT_PHY_NET, "phy-net")
        wt.set(WEIGHT_PHY_PORTS, "phy-ports")

        wt.set(WEIGHT_NONE, "Not classified")

        if (SHOW_DEBUG) {
            for (let [key, value] of wt) {
                wt.set(key, key.toString() + "|" + value)
            }
        }

        return wt
    }

    suggestions(): Array<string> {
        return [
            "data.IPV4",
            "data.MAC",
            "data.Name"
        ]
    }

    nodeDataFields(): Array<NodeDataField> {
        return [
            {
                field: "",
                title: "General",
                expanded: true,
                icon: "\uf05a",
                sortKeys: (data: any): Array<string> => {
                    return ['Name', 'Type', 'MAC', 'Driver', 'State']
                },
                filterKeys: (data: any): Array<string> => {
                    switch (data.Type) {
                        case "host":
                            return ['Name']
                        default:
                            return ['Name', 'Type', 'MAC', 'Driver', 'State']
                    }
                }
            },
            {
                field: "Sockets",
                expanded: false,
                icon: "\uf1e6"
            },
            {
                field: "Captures",
                expanded: false,
                icon: "\uf51f",
                normalizer: (data: any): any => {
                    for (let capture of data) {
                        capture.ID = capture.ID.split('-')[0]
                    }
                    return data
                }
            },
            {
                field: "Injections",
                expanded: false,
                icon: "\uf48e"
            },
            {
                field: "Docker",
                expanded: false,
                icon: "\uf395",
                iconClass: "font-brands"
            },
            {
                field: "Runc",
                expanded: false,
                icon: "\uf7bc",
                iconClass: "font-brands"
            },
            {
                field: "IPV4",
                expanded: true,
                icon: "\uf1fa"
            },
            {
                field: "IPV6",
                expanded: true,
                icon: "\uf1fa"
            },
            {
                field: "LastUpdateMetric",
                title: "Last metrics",
                expanded: false,
                icon: "\uf201",
                normalizer: (data: any): any => {
                    return {
                        RxPackets: data.RxPackets ? data.RxPackets.toLocaleString() : 0,
                        RxBytes: data.RxBytes ? Tools.prettyBytes(data.RxBytes) : 0,
                        TxPackets: data.TxPackets ? data.TxPackets.toLocaleString() : 0,
                        TxBytes: data.TxPackets ? Tools.prettyBytes(data.TxBytes) : 0,
                        Start: data.Start ? new Date(data.Start).toLocaleString() : 0,
                        Last: data.Last ? new Date(data.Last).toLocaleString() : 0
                    }
                },
                graph: (data: any): any => {
                    return {
                        type: "LineChart",
                        data: [
                            [
                                { type: "datetime", label: "time" },
                                "RxBytes",
                                "TxBytes"
                            ],
                            [new Date(data.Last || 0), data.RxBytes || 0, data.TxBytes || 0]
                        ]
                    }
                }
            },
            {
                field: "Metric",
                title: "Total metrics",
                expanded: false,
                icon: "\uf201",
                normalizer: (data: any): any => {
                    return {
                        RxPackets: data.RxPackets ? data.RxPackets.toLocaleString() : 0,
                        RxBytes: data.RxBytes ? Tools.prettyBytes(data.RxBytes) : 0,
                        TxPackets: data.TxPackets ? data.TxPackets.toLocaleString() : 0,
                        TxBytes: data.TxPackets ? Tools.prettyBytes(data.TxBytes) : 0,
                        Last: data.Last ? new Date(data.Last).toLocaleString() : 0
                    }
                }
            },
            {
                field: "Features",
                expanded: false,
                icon: "\uf022"
            },
            {
                field: "FDB",
                expanded: false,
                icon: "\uf0ce"
            },
            {
                field: "Neighbors",
                expanded: false,
                icon: "\uf0ce"
            },
            {
                field: "RoutingTables",
                title: "Routing tables",
                expanded: false,
                icon: "\uf0ce",
                normalizer: (data: any): any => {
                    var rows = new Array<any>()
                    for (let table of data) {
                        if (!table.Routes) {
                            continue
                        }
                        for (let route of table.Routes) {
                            if (!route.NextHops) {
                                continue
                            }
                            for (let nh of route.NextHops) {
                                rows.push({
                                    ID: table.ID,
                                    Src: table.Src,
                                    Protocol: route["Protocol"],
                                    Prefix: route["Prefix"],
                                    Priority: nh["Priority"],
                                    IP: nh["IP"],
                                    IfIndex: nh["IfIndex"]
                                })
                            }
                        }
                    }

                    return rows
                }
            }
        ]
    }

    linkAttrs(link: Link): LinkAttrs {
        var metric = link.source.data.LastUpdateMetric
        var bandwidth = 0
        if (metric) {
            bandwidth = (metric.RxBytes + metric.TxBytes) * 8
            bandwidth /= (metric.Last - metric.Start) / 1000
        }

        var attrs = {
            classes: [link.data.RelationType],
            icon: "\uf362",
            directed: false,
            href: '',
            iconClass: '',
            label: bandwidth ? Tools.prettyBandwidth(bandwidth) : ""
        }

        if (bandwidth > 0) {
            attrs.classes.push('traffic')
        }

        if (link.data.RelationType === "layer2") {
            attrs.classes.push("traffic")
        }

        if (link.data.Directed) {
            attrs.directed = true
        }

        return attrs
    }

    linkTabTitle(link: Link): string {
        var src = link.source.data.Name
        var dst = link.target.data.Name
        if (src && dst) {
            return src.substring(0, 8) + " / " + dst.substring(0, 8)
        }
        return link.id.split("-")[0]
    }

    linkDataFields(): Array<LinkDataField> {
        return [
            {
                field: "",
                title: "General",
                expanded: true,
                icon: "\uf05a",
            },
            {
                field: "NSM",
                title: "Network Service Mesh",
                expanded: true,
                icon: "\uf542",
            },
            {
                field: "NSM.Source",
                title: "Source",
                expanded: false,
                icon: "\uf018",
            },
            {
                field: "NSM.Via",
                title: "Via",
                expanded: false,
                icon: "\uf018",
            },
            {
                field: "NSM.Destination",
                title: "Destination",
                expanded: false,
                icon: "\uf018",
            }
        ]
    }

    defaultLinkTagMode(): number {
        return 2
    }
}
