#!/usr/bin/env python3
import json
import sys
import argparse

parser = argparse.ArgumentParser(description="JSON CLI Pretty Printer")
parser.add_argument("module", help="IETF Module")
parser.add_argument("-n", "--name", help="Focus on specific name")
args = parser.parse_args()

class Pad:
    iface = 16
    proto = 11
    state = 12
    data = 41

class Decore():
    @staticmethod
    def decorate(sgr, txt, restore="0"):
        return f"\033[{sgr}m{txt}\033[{restore}m"

    @staticmethod
    def invert(txt):
        return Decore.decorate("7", txt)

    @staticmethod
    def red(txt):
        return Decore.decorate("31", txt, "39")

    @staticmethod
    def green(txt):
        return Decore.decorate("32", txt, "39")

class Iface:
    def get_json_data(self, default, *args):
        data = self.data
        for arg in args:
            if data.get(arg):
                data = data.get(arg)
            else:
                return default

        return data

    def __init__(self, data):
        self.data = data
        self.name = data.get('name', '')
        self.index = data.get('if-index', '')
        self.oper_status = data.get('oper-status', '')
        self.phys_address = data.get('phys-address', '')

        if data.get('statistics'):
            self.in_octets = data.get('statistics').get('in-octets', '')
            self.out_octets = data.get('statistics').get('out-octets', '')
        else:
            self.in_octets = ''
            self.out_octets = ''

        if self.data.get('ietf-ip:ipv4'):
            self.mtu = self.data.get('ietf-ip:ipv4').get('mtu', '')
            self.ipv4_addr = self.data.get('ietf-ip:ipv4').get('address', '')
        else:
            self.mtu = ''
            self.ipv4_addr = []

        if self.data.get('ietf-ip:ipv6'):
            self.ipv6_addr = self.data.get('ietf-ip:ipv6').get('address', '')
        else:
            self.ipv6_addr = []

        if self.data.get('infix-interfaces:bridge-port'):
            self.bridge = self.data.get('infix-interfaces:bridge-port').get('bridge', None)
        else:
            self.bridge = ''

        if self.data.get('infix-interfaces:vlan'):
            self.lower_if = self.data.get('infix-interfaces:vlan', None).get('lower-layer-if',None)
        else:
            self.lower_if = ''

    def is_vlan(self):
        return self.data['type'] == "infix-if-type:vlan"

    def is_bridge(self):
        return self.data['type'] == "infix-if-type:bridge"

    def pr_name(self, pipe=""):
        print(f"{pipe}{self.name:<{Pad.iface - len(pipe)}}", end="")


    def pr_proto_ipv4(self, pipe=''):
        for addr in self.ipv4_addr:
            origin = f"({addr['origin']})" if addr.get('origin') else ""

            row =  f"{pipe:<{Pad.iface}}"
            row += f"{'ipv4':<{Pad.proto}}"
            row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
            print(row)

    def pr_proto_ipv6(self, pipe=''):
        for addr in self.ipv6_addr:
            origin = f"({addr['origin']})" if addr.get('origin') else ""

            row =  f"{pipe:<{Pad.iface}}"
            row += f"{'ipv6':<{Pad.proto}}"
            row += f"{'':<{Pad.state}}{addr['ip']}/{addr['prefix-length']} {origin}"
            print(row)

    def pr_proto_eth(self):
        row = f"{'ethernet':<{Pad.proto}}"
        dec = Decore.green if self.oper_status == "up" else Decore.red
        row += dec(f"{self.oper_status.upper():<{Pad.state}}")
        row += f"{self.data['phys-address']:<{Pad.data}}"
        print(row)

    def pr_bridge(self, _ifaces):
        self.pr_name(pipe="")
        self.pr_proto_eth()


        lowers = []
        for _iface in [Iface(data) for data in _ifaces]:
            if _iface.bridge and _iface.bridge == self.name:
                lowers.append(_iface)

        if lowers:
            self.pr_proto_ipv4(pipe='│')
            self.pr_proto_ipv6(pipe='│')
        else:
            self.pr_proto_ipv4()
            self.pr_proto_ipv6()

        for i, lower in enumerate(lowers):
            pipe = '└ ' if (i == len(lowers) -1)  else '├ '
            lower.pr_name(pipe)
            lower.pr_proto_eth()

    def pr_vlan(self, _ifaces):
        self.pr_name(pipe="")
        self.pr_proto_eth()

        if self.lower_if:
            self.pr_proto_ipv4(pipe='│')
            self.pr_proto_ipv6(pipe='│')
        else:
            self.pr_proto_ipv4()
            self.pr_proto_ipv6()
            return

        parent = find_iface(_ifaces, self.lower_if)
        if not parent:
            print(f"Error, didn't find parent interface for vlan {self.name}")
            sys.exit(1)
        parent.pr_name(pipe='└ ')
        parent.pr_proto_eth()

    def pr_iface(self):
        print(f"{'name':<{20}}: {self.name}")
        print(f"{'index':<{20}}: {self.index}")
        if self.mtu:
            print(f"{'mtu':<{20}}: {self.mtu}")
        if self.oper_status:
            print(f"{'operational status':<{20}}: {self.oper_status}")
        if self.phys_address:
            print(f"{'physical address':<{20}}: {self.phys_address}")

        if self.ipv4_addr:
            first = True
            for addr in self.ipv4_addr:
                origin = f"({addr['origin']})" if addr.get('origin') else ""
                key = 'ipv4 addresses' if first else ''
                colon = ':' if first else ' '
                row = f"{key:<{20}}{colon} "
                row += f"{addr['ip']}/{addr['prefix-length']} {origin}"
                print(row)
                first = False
        else:
                print(f"{'ipv4 addresses':<{20}}:")

        if self.ipv6_addr:
            first = True
            for addr in self.ipv6_addr:
                origin = f"({addr['origin']})" if addr.get('origin') else ""
                key = 'ipv6 addresses' if first else ''
                colon = ':' if first else ' '
                row = f"{key:<{20}}{colon} "
                row += f"{addr['ip']}/{addr['prefix-length']} {origin}"
                print(row)
                first = False
        else:
                print(f"{'ipv6 addresses':<{20}}:")

        if self.in_octets and self.out_octets:
            print(f"{'in-octets':<{20}}: {self.in_octets}")
            print(f"{'out-octets':<{20}}: {self.out_octets}")

        frame = self.get_json_data([], 'ieee802-ethernet-interface:ethernet',
                                   'statistics', 'frame')
        if frame:
            print(f"")
            for key, val in frame.items():
                print(f"eth-{key:<{25}}: {val}")


def find_iface(_ifaces, name):
    for _iface in [Iface(data) for data in _ifaces]:
        if _iface.name == name:
            return _iface

    return False


def pr_interface_list(json):
    hdr = (f"{'INTERFACE':<{Pad.iface}}"
           f"{'PROTOCOL':<{Pad.proto}}"
           f"{'STATE':<{Pad.state}}"
           f"{'DATA':<{Pad.data}}")

    print(Decore.invert(hdr))

    ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=lambda x: x['name'])

    for iface in [Iface(data) for data in ifaces]:
        if iface.is_bridge():
            iface.pr_bridge(ifaces)
            continue

        if iface.is_vlan():
            iface.pr_vlan(ifaces)
            continue

        # These interfaces are printed by there parent, such as bridge
        if iface.lower_if:
            continue
        if iface.bridge:
            continue

        iface.pr_name()
        iface.pr_proto_eth()
        iface.pr_proto_ipv4()
        iface.pr_proto_ipv6()

def ietf_interfaces(json, name):
    if not json or not json.get("ietf-interfaces:interfaces"):
        print(f"Error, top level \"ietf-interfaces:interfaces\" missing")
        sys.exit(1)

    if not name:
        return pr_interface_list(json)

    iface = find_iface(json["ietf-interfaces:interfaces"]["interface"], name)
    if not iface:
        print(f"Interface {name} not found")
        sys.exit(1)
    return iface.pr_iface()

def main():
    try:
        json_data = json.load(sys.stdin)
    except json.JSONDecodeError:
        print("Error, invalid JSON input")
        sys.exit(1)
    except Exception as e:
        print("Error, unexpected error parsing JSON")
        sys.exit(1)

    if args.module == "ietf-interfaces":
        sys.exit(ietf_interfaces(json_data, args.name))
    else:
        print(f"Error, unknown module {args.module}")
        sys.exit(1)

if __name__ == "__main__":
    main()
