#!/usr/bin/env python3
"""
Display the schema in human-readable form

The BSN REST API is self-documenting: you can GET http://.../api/v1/schema/controller
to get a JSON document describing the tree of containers, lists, and leaf
nodes. The raw JSON is very verbose though, so it's not easy to use as
documentation when writing scripts against the REST API. This script shows
the most important information in an easy to read way.

By default this will display the entire schema. To navigate the schema more
easily, you'll want to use the 'path' and '--max-depth' options. For example:

    pybsn-schema -H $HOSTNAME -d 2 -v controller.core.switch

This shows 2 levels of schema starting from the path "controller.core.switch".
"""
from __future__ import print_function

import pybsn
import argparse
import json
import textwrap
import re


parser = argparse.ArgumentParser(description=__doc__)

parser.add_argument('path', type=str, default='controller', nargs='?')
parser.add_argument('--host', '-H', type=str, default="127.0.0.1", help="Controller IP/Hostname to connect to")
parser.add_argument('--user', '-u', type=str, default="admin", help="Username")
parser.add_argument('--password', '-p', type=str, default="adminadmin", help="Password")
parser.add_argument('--json-file', '-j', type=str, help="JSON Schema to consume")

parser.add_argument("--max-depth", "-d", type=int, help="Maximum recursion depth")
parser.add_argument("--raw", action="store_true", help="Print raw JSON")
parser.add_argument("--verbose", "-v", action="store_true", help="Include descriptions in the output")

args = parser.parse_args()


def pretty_type(node):
    if not 'typeSchemaNode' in node:
        return node['leafType'].lower()

    t = node['typeSchemaNode']

    if t['leafType'] == 'ENUMERATION':
        names = list([x for x in t['typeValidator'] if x['type'] == 'ENUMERATION_VALIDATOR'][0]['names'].keys())
        if not args.verbose and len(names) > 4:
            names = names[:4] + ['...']
        return "enum { %s }" % ', '.join(names)
    elif t['leafType'] == 'UNION':
        names = [x['name'] for x in t['typeSchemaNodes']]
        return "union { %s }" % ', '.join(names)
    else:
        return t['leafType'].lower()

def traverse(node, depth=0, name="root"):
    def output(*s, **kw):
        d = kw.get("depth", depth)
        print(" " * (d * 2) + ' '.join(s))

    if args.max_depth is not None and depth > args.max_depth:
        return

    if args.verbose and 'description' in node:
        description = re.sub(r"\s+", " ", node['description'])
        indent = " "*(depth*2) + "  # "
        description = "\n" + textwrap.fill(
            description,
            initial_indent=indent,
            subsequent_indent=indent,
            width=70 - depth*2)
    else:
        description = ''

    if args.verbose:
        config = "config" in node['dataSources'] and "(config)" or ""
    else:
        config = ""

    if node['nodeType'] == 'CONTAINER' or node['nodeType'] == 'LIST_ELEMENT':
        if node['nodeType'] == 'CONTAINER':
            output(name, description)
        for child_name, child in node.get('childNodes', {}).items():
            traverse(child, depth+1, child_name)
    elif node['nodeType'] == 'LIST':
        output(name, "(list)", description)
        traverse(node['listElementSchemaNode'], depth, name)
    elif node['nodeType'] == 'LEAF':
        output(name, ":", pretty_type(node), config, description)
    elif node['nodeType'] == 'LEAF_LIST':
        output(name, ":", "list of", pretty_type(node['leafSchemaNode']), config, description)
    elif node['nodeType'] == 'RPC':
        output(name, description, "(RPC)")
        for name, item in ( ("in", "inputSchemaNode"), ("out", "outputSchemaNode")):
            if item in node:
                traverse(node[item], depth+1, name)
            else:
                output(name, "(NONE)", depth=depth+1)
    else:
        assert False, "unknown node type %s" % node['nodeType']

path = args.path.replace('.', '/').replace('_', '-')


if args.json_file:
    with open(args.json_file) as file_:
        schema = json.load(file_)
else:
    bcf = pybsn.connect(args.host, args.user, args.password)
    schema = bcf.schema(path)

if args.raw:
    print(json.dumps(schema, indent=4, cls=pybcf.BCFJSONEncoder))
else:
    traverse(schema, name=path)
