#!/usr/bin/env python3

from __future__ import print_function

import re
import os
import sys
import argparse
import log
import glob
import random
from pprint import pprint


SCHEMA_NAMESPACE = "dn42."


class SchemaDOM:
    src = None
    schema = None
    name = None
    ref = None
    primary = None
    type = None

    def __init__(self, fn):
        self.src = fn
        f = FileDOM(fn)
        self.schema = self.__parse_schema(f)

    def __parse_schema(self, f):
        schema = {}
        for k, v, l in f.dom:
            if k == 'ref':
                self.ref = v
            elif k == 'schema':
                self.name = v

            if k != 'key':
                continue

            v = v.split()
            key = v.pop(0)

            schema[key] = set()
            for i in v:
                if i == ">":
                    break

                schema[key].add(i)

            for k, v in schema.items():
                if 'schema' in v:
                    self.type = k

                if 'primary' in v:
                    self.primary = k
                    schema[k].add("oneline")
                    if "multiline" in v:
                        schema[k].remove("multiline")
                    schema[k].add("single")
                    if "multiple" in v:
                        schema[k].remove("multiple")
                    schema[k].add("required")
                    if "optional" in v:
                        schema[k].remove("optional")
                    if "recommend" in v:
                        schema[k].remove("recommend")
                    if "deprecate" in v:
                        schema[k].remove("deprecate")

                if 'oneline' not in v:
                    schema[k].add("multiline")
                if 'single' not in v:
                    schema[k].add("multiple")

        return schema

    def check_file(self, f, lookups=None):
        status = "PASS"
        for k, v in self.schema.items():
            if 'required' in v and k not in f.keys:
                log.error(
                    "%s Line 0: Key [%s] not found and is required." % (f.src, k))
                status = "FAIL"
            elif 'recommend' in v and k not in f.keys:
                log.notice(
                    "%s Line 0: Key [%s] not found and is recommended." % (f.src, k))
                status = "NOTE"

            if 'schema' in v and SCHEMA_NAMESPACE + f.dom[0][0] != self.ref:
                log.error(
                    "%s Line 1: Key [%s] not found and is required as the first line." % (f.src, k))
                status = "FAIL"

            if 'single' in v and k in f.keys and len(f.keys[k]) > 1:
                log.warning("%s Line %d: Key [%s] first defined here and has repeated keys." % (
                    f.src, f.keys[k][0], k))
                for l in f.keys[k][1:]:
                    log.error(
                        "%s Line %d: Key [%s] can only appear once." % (f.src, l, k))
                    status = "FAIL"

            if 'oneline' in v and k in f.multi:
                for l in f.keys[k]:
                    log.error(
                        "%s Line %d: Key [%s] can not have multiple lines." % (f.src, l, k))
                    status = "FAIL"

        for k, v, l in f.dom:
            if k == self.primary and  not f.src.endswith(v.replace("/","_").replace(" ","")):
                log.error("%s Line %d: Primary [%s: %s] does not match filename." % (f.src, l, k, v))
                status = "FAIL"

            if k.startswith("x-"):
                log.info("%s Line %d: Key [%s] is user defined." % (f.src, l, k))

            elif k not in self.schema:
                log.error("%s Line %d: Key [%s] not in schema." % (f.src, l, k))
                status = "FAIL"
                continue
            else:
                if 'deprecate' in self.schema[k]:
                    log.info(
                        "%s Line %d: Key [%s] was found and is deprecated." % (f.src, l, k))
                    status = "INFO"

                if lookups is not None:
                    for o in self.schema[k]:
                        if o.startswith("lookup="):
                            refs = o.split("=", 2)[1].split(",")
                            val = v.split()[0]
                            found = False
                            for ref in refs:
                                if (ref, val) in lookups:
                                    found = True
                            if not found:
                                log.error("%s Line %d: Key %s references object %s in %s but does not exist." % (
                                    f.src, l, k, val, refs))
                                status = "FAIL"
        if status != "FAIL":
            ck = sanity_check(f)
            if ck == "FAIL":
                status = ck

        print("CHECK\t%-54s\t%s\tMNTNERS: %s" %(f.src, status, ','.join(f.mntner)))
        return status


class FileDOM:

    def __init__(self, fn):
        dom = []
        keys = {}
        multi = {}
        mntner = []
        last_multi = None
        schema = None
        src = fn

        with open(fn, mode='r', encoding='utf-8') as f:
            for lineno, i in enumerate(f.readlines(), 1):

                if re.match(r'[ \t]', i):
                    dom[-1][1] += "\n" + i.strip()

                    if dom[-1][0] not in multi:
                        multi[dom[-1][0]] = []

                    if last_multi is None:
                        multi[dom[-1][0]].append(lineno)
                        last_multi = dom[-1][0]

                else:
                    i = i.split(":")
                    if len(i) < 2:
                        continue

                    dom.append([i[0].strip(), ':'.join(i[1:]).strip(), lineno - 1])

                    if i[0].strip() not in keys:
                        keys[i[0].strip()] = []

                    keys[i[0].strip()].append(len(dom) - 1)

                    last_multi = None

                if dom[-1][0] == 'mnt-by':
                    mntner.append(dom[-1][1])

        self.dom = dom
        self.keys = keys
        self.multi = multi
        self.mntner = mntner
        self.schema = SCHEMA_NAMESPACE + dom[0][0]
        self.src = src

    def __str__(self):
        length = 19
        for i in self.dom:
            if len(i[0]) > length:
                length = len(i[0]) + 2
        s = ""
        for i in self.dom:
            l = i[1].split("\n")

            s += i[0] + ":" + " " * (length - len(i[0])) + l[0] + "\n"
            for m in l[1:]:
                s +=  " " * (length + 1) + m + "\n"

        return s

    def get(self, key, index=0, default=None):
        if key not in self.keys:
            return default
        if index >= len(self.keys[key]) or index <= -len(self.keys[key]):
            return default

        return self.dom[self.keys[key][index]][1]


def main(infile, schema):

    log.debug("Check File: %s" % (infile))
    f = FileDOM(infile)

    if schema is not None:
        f.schema = schema
    else:
        f.schema = "schema/" + f.schema

    if f.schema is None:
        log.error("Schema is not defined for file")
        return False

    log.debug("Use Schema: %s" % (f.schema))

    s = SchemaDOM(f.schema)
    return s.check_file(f)

def check_schemas(path):
    schemas = {}
    for fn in glob.glob(path+"/*"):
        s = SchemaDOM(fn)
        log.info("read schema: %s" % (s.name))
        schemas[s.ref] = s

    ok = True
    c = schemas[SCHEMA_NAMESPACE + "schema"]
    for s in schemas.keys():
        ck = c.check_file(s)
        if not ck:
            ok = False

    return ok

def scan_index(infile, mntner=None):
    idx = {}
    schemas = {}

    with open(infile, 'r') as f:
        for line in f.readlines():
            line = line.split()
            idx[(line[0], line[1])] = line[2:]
            if line[0] == SCHEMA_NAMESPACE + 'schema':
                s = SchemaDOM(line[2])
                log.info("read schema: %s" % (s.name))
                schemas[s.ref] = s

    return __scan_index(idx, schemas, mntner)

def scan_files(path, mntner=None, use_file=None):
    arr = __index_files(path, use_file)

    idx = {}
    schemas = {}

    for line in arr:
        idx[(line[0], line[1])] = line[2:]
        if line[0] == SCHEMA_NAMESPACE + 'schema':
            s = SchemaDOM(line[2])
            log.info("read schema: %s" % (s.name))
            schemas[s.ref] = s

    return __scan_index(idx, schemas, mntner, use_file)

def __scan_index(idx, schemas, mntner, use_file):
    ok = True
    for k, v in idx.items():
        log.debug(k)
        mlist = []
        if len(v) > 1:
            mlist = v[1].split(",")

        if mntner is not None and mntner not in mlist:
            continue

        if use_file is not None and use_file != v[0]:
            continue

        s = schemas.get(k[0], None)
        if s is None:
            log.error("No schema found for %s" % (k[1]))
            ok = False
            continue
        c = FileDOM(v[0])
        ck = s.check_file(c, idx.keys())
        if ck == "INFO" and ok != "FAIL":
            ok = ck
        if ck == "FAIL":
            ok = ck

    return ok

def __index_files(path, use_file):
    xlat = {
        "dns/":          SCHEMA_NAMESPACE + "domain",
        "inetnum/":      SCHEMA_NAMESPACE + "inetnum",
        "inet6num/":     SCHEMA_NAMESPACE + "inet6num",
        "route/":        SCHEMA_NAMESPACE + "route",
        "route6/":       SCHEMA_NAMESPACE + "route6",
        "aut-num/":      SCHEMA_NAMESPACE + "aut-num",
        "as-set/":       SCHEMA_NAMESPACE + "as-set",
        "as-block/":     SCHEMA_NAMESPACE + "as-block",
        "organisation/": SCHEMA_NAMESPACE + "organisation",
        "mntner/":       SCHEMA_NAMESPACE + "mntner",
        "person/":       SCHEMA_NAMESPACE + "person",
        "role/":         SCHEMA_NAMESPACE + "role",
        "tinc-key/":     SCHEMA_NAMESPACE + "tinc-key",
        "tinc-keyset/":  SCHEMA_NAMESPACE + "tinc-keyset",
        "schema/":       SCHEMA_NAMESPACE + "schema",
    }

    for root, dirs, files in os.walk(path):
        ignore = True
        for t in xlat.keys():
            if root+"/" == os.path.join(path, t):
              ignore = False
              break
        if ignore:
          continue

        for f in files:
            dom = FileDOM(os.path.join(root, f))
            yield (dom.schema, dom.src.split("/")[-1].replace("_", "/"), dom.src, ",".join(dom.mntner))

    if use_file is not None:
            dom = FileDOM(use_file)
            yield (dom.schema, dom.src.split("/")[-1].replace("_", "/"), dom.src, ",".join(dom.mntner))

def index_files(path):
    idx = __index_files(path)
    for i in idx:
        print("%s\t%s\t%s\t%s" % i)

def http_get(server, url, query=None, headers=None):
    import urllib.parse
    import http.client
    import json

    if headers is None:
        headers = {}
    if 'User-Agent' not in headers:
        headers['User-Agent'] = "curl"
    if 'Accept' not in headers:
        headers['Accept'] = 'application/json'

    if query is None:
        query = {}

    http_client = http.client.HTTPSConnection(server)

    full_url = url + '?' + urllib.parse.urlencode(query)
    log.debug("GET " + full_url)

    http_client.request('GET', full_url, headers=headers)
    req = http_client.getresponse()
    log.debug("HTTP Response: %d %s" % (req.status, req.reason))

    if "application/json" in req.getheader("Content-Type", "application/json"):
        if req.status > 299:
            return {}
        r = req.read()
        if not isinstance(r, str):
           r = r.decode("utf-8")
        return json.loads(r)

    if req.status > 299:
        return ""

    return req.read()

def find(fields=None, filter=None):
    server = "registry.dn42.us"
    url    = "/v1/reg/reg.objects"
    if fields is None:
        fields = []
    if filter is None:
        filter = {}
    query = {"fields": ",".join(fields), "filter": ",".join([k + "=" + v for k, v in filter.items()])}
    return http_get(server, url, query)

def to_num(ip):
    ip = [int(i) for i in ip.split('.')]
    return ip[3] + ip[2] * 256 + ip[1] * 256 ** 2 + ip[0] * 256 ** 3

def to_ip(num):
    return '.'.join([str(i) for i in [num >> 24, (num >> 16) & 0xFF, (num >> 8) & 0xFF, num & 0xFF]])

def pretty_ip(addr):
    if addr.startswith("00000000000000000000ffff"):
        addr = addr[-8:]
        addr = int(addr, 16)
        return to_ip(addr)
    return ":".join([addr[i:i+4] for i in range(0, len(addr), 4)])

#    addr = ["%x" % (int(addr[i:i+4],16)) for i in range(0, len(addr), 4)]
#
#    last_seg = 8
#    for i, seg in enumerate(addr[::-1]):
#        if seg == "0": last_seg = 7 - i
#        else: break
#
#    return ":".join(addr[:last_seg]) + ("::" if last_seg < 8 else "")

def expand_ipv6(addr):
    addr = addr.lower()
    if "::" in addr:
        if addr.count('::') > 1:
            return False
        addr = addr.replace('::', ':' * (9 - addr.count(':')))
    if addr.count(':') != 7:
        return False
    return ''.join((i.zfill(4) for i in addr.split(":")))

def ip4_to_ip6(ip):
    return "::ffff:%04x:%04x" % (ip >> 16, ip & 0xffff)

def inetrange(inet):
    ip, mask = inet.split('/')
    mask = int(mask)
    ip = to_num(ip) & (0xFFFFFFFF << 32 - mask)
    ip6 = ip4_to_ip6(ip)
    return inet6range("%s/%d" % (ip6, mask + 96))

def inet6range(inet):
    ip, mask = inet.split('/')
    mask = int(mask)

    log.debug(ip)
    ip = expand_ipv6(ip)

    if mask == 128:
        return ip, ip, mask

    offset = int(ip[mask // 4], 16)
    return "%s%x%s" % (
        ip[:mask // 4],
        offset & (0xf0 >> mask % 4),
        "0" * (31 - mask // 4)
    ), "%s%x%s" % (
               ip[:mask // 4],
               offset | (0xf >> mask % 4),
               "f" * (31 - mask // 4)
           ), mask

def test_policy(obj_type, name, mntner):
    log.debug([obj_type, name, mntner])

    if obj_type in ["organisation", "mntner", "person", "role", "as-set", "schema", "dns"]:
        if obj_type == "organisation" and not name.startswith("ORG-"):
            log.error("%s does not start with 'ORG-'" %(name))
            return "FAIL"
        elif obj_type == "mntner" and not name.endswith("-MNT"):
            log.error("%s does not end with '-MNT'" %(name))
            return "FAIL"
        elif obj_type == "dns" and not name.endswith(".dn42"):
            log.error("%s does not end with '.dn42'" %(name))
            return "FAIL"
        elif obj_type == "dns" and len(name.strip(".").split(".")) != 2:
            log.error("%s is not a second level domain" %(name))
            return "FAIL"
        elif obj_type in ["person", "role"] and not name.endswith("-DN42"):
            log.error("%s does not end with '-DN42'" %(name))
            return "FAIL"

        lis = find(["mnt-by"], {"@type": obj_type, "@name": name})
        log.debug(lis)

        if len(lis) == 0:
            log.notice("%s does not currently exist" %(name))
            return "PASS"

        status = 'FAIL'
        for o in lis:
            for n in o:
                log.debug(n)
                log.debug(mntner)
                if n[0] == "mnt-by" and n[1] == mntner:
                    status = 'PASS'
                    return status

        log.error("%s does not have mnt for object" %(mntner))
        return status

    elif args["type"] in ["inetnum","inet6num"]:
        log.info("Checking inetnum type")
        lis = find(["mnt-by"], {"@type": "net", "cidr": name})
        log.debug(lis)

        if len(lis) > 0:
            status = 'FAIL'
            for o in lis:
                for n in o:
                    if n[0] == "mnt-by" and n[1] == mntner:
                        status = 'PASS'
                        log.notice("%s has mnt for current object" %(mntner))
                        return status
            log.error("%s does not have mnt for current object" %(mntner))
            return status

        if args["type"] == "inetnum":
            Lnet, Hnet, mask = inetrange(name)
        else:
            Lnet, Hnet, mask = inet6range(name)

        mask = "%03d" %(mask)

        log.info([Lnet, Hnet, mask])
        lis = find(["inetnum","inet6num","policy","@netlevel","mnt-by","mnt-lower"],
                   {"@type": "net", "@netmin": "le=" + Lnet, "@netmax": "ge=" + Hnet, "@netmask": "lt=" + mask})
        log.debug(lis)

        policy = {}
        select = None
        mntners = []

        for n in lis:
            obj = {}
            for o in n:
                obj[o[0]] = o[1]
                if o[0].startswith("mnt-"):
                    mntners.append(o[1])

            k = obj["@netlevel"]
            policy[k] = obj

            if select is None:
                select = k
            elif select<=k:
                select = k

        if select == None:
            pass

        elif policy.get(select,{}).get("policy","closed") == "open":
            log.notice("Policy is open for parent object")
            return "PASS"

        # 3. Check if mntner or mnt-lower for any as-block in the tree.
        elif mntner in mntners:
            log.notice("%s has mnt in parent object" %(mntner))
            return "PASS"

    elif args["type"] in ["route","route6"]:
        log.info("Checking route type")
        lis = find(["mnt-by"], {"@type": "route", args["type"]: name})
        log.debug(lis)

        if len(lis) > 0:
            status = 'FAIL'
            for o in lis:
                for n in o:
                    if n[0] == "mnt-by" and n[1] == mntner:
                        status = 'PASS'
                        log.notice("%s has mnt for current object" %(mntner))
                        return status
            log.error("%s does not have mnt for current object" %(mntner))
            return status

        if args["type"] == "route":
            Lnet, Hnet, mask = inetrange(name)
        else:
            Lnet, Hnet, mask = inet6range(name)
        mask = "%03d" %(mask)

        log.info([Lnet, Hnet, mask])
        lis = find(["inetnum","inet6num","policy","@netlevel","mnt-by","mnt-lower"], 
                   {"@type": "net", "@netmin": "le=" + Lnet, "@netmax": "ge=" + Hnet, "@netmask": "le=" + mask})
        log.debug(lis)

        policy = {}
        select = None
        mntners = []

        for n in lis:
            obj = {}
            for o in n:
                obj[o[0]] = o[1]
                if o[0].startswith("mnt-"):
                    mntners.append(o[1])

            k = obj["@netlevel"]
            policy[k] = obj

            if select is None:
                select = k
            elif select<=k:
                select = k

        if select == None:
            pass

        elif policy.get(select,{}).get("policy","closed") == "open":
            log.notice("Policy is open for parent object")
            return "PASS"

        # 3. Check if mntner or mnt-lower for any as-block in the tree.
        elif mntner in mntners:
            log.notice("%s has mnt in parent object" %(mntner))
            return "PASS"

    elif args["type"] == "aut-num":
        if not name.startswith("AS"):
            log.error("%s does not start with AS" %(name))
            return "FAIL"

        # 1. Check if they already have an object
        lis = find(["mnt-by"], {"@type": "aut-num", "@name": name})
        log.debug(lis)

        if len(lis) > 0:
            status = 'FAIL'
            for o in lis:
                for n in o:
                    if n[0] == "mnt-by" and n[1] == mntner:
                        status = 'PASS'
                        log.notice("%s has mnt for current object" %(mntner))
                        return status
            log.error("%s does not have mnt for current object" %(mntner))
            return status

        # 2. Check if the as-block has an open policy
        asn = "AS{:0>9}".format(name[2:])
        lis = find(["as-block","policy","@as-min","@as-max","mnt-by","mnt-lower"],
                   {"@type": "as-block","@as-min":"le=" + asn,"@as-max": "ge=" + asn})
        log.info(lis)

        policy = {}
        select = None
        mntners = []

        for n in lis:
            obj = {}
            for o in n:
                obj[o[0]] = o[1]
                if o[0].startswith("mnt-"):
                    mntners.append(o[1])

            k = (obj["@as-min"],obj["@as-max"])
            policy[k] = obj

            if select is None:
                select = k
            elif select[0]<=k[0] or select[1]>=k[1]:
                select = k

        if policy.get(select,{}).get("policy","closed") == "open":
            log.notice("Policy is open for parent object")
            return "PASS"

        # 3. Check if mntner or mnt-lower for any as-block in the tree.
        elif mntner in mntners:
            log.notice("%s has mnt in parent object" %(mntner))
            return "PASS"

    elif args["type"] == "as-block":
        Lname, Hname = name.split("-")
        Lname, Hname = Lname.strip(), Hname.strip()

        if not Lname.startswith("AS") or not Hname.startswith("AS"):
            log.error("%s does not start with AS for min and max" %(name))
            return "FAIL"

        # 1. Check if they already have an object
        lis = find(["mnt-by"], {"@type": "as-block", "@name": name})
        log.debug(lis)

        if len(lis) > 0:
            status = 'FAIL'
            for o in lis:
                for n in o:
                    if n[0] == "mnt-by" and n[1] == mntner:
                        status = 'PASS'
                        log.notice("%s has mnt for current object" %(mntner))
                        return status
            log.notice("%s does not have mnt for current object" %(mntner))
            return status

        # 2. Check if the parent as-blocks have an open policy
        Lasn = "AS{:0>9}".format(Lname[2:])
        Hasn = "AS{:0>9}".format(Hname[2:])

        if Lasn > Hasn:
            log.error("%s should come before %s" %(Lname, Hname))

        lis = find(["as-block","policy","@as-min","@as-max","mnt-by","mnt-lower"], {"@type": "as-block","@as-min":"le=" + Lasn,"@as-max": "ge=" + Hasn})
        log.debug(lis)

        policy = {}
        select = None
        mntners = []

        for n in lis:
            obj = {}
            for o in n:
                obj[o[0]] = o[1]
                if o[0].startswith("mnt-"):
                    mntners.append(o[1])

            k = (obj["@as-min"],obj["@as-max"])
            policy[k] = obj

            if select is None:
                select = k
            elif select[0]<=k[0] or select[1]>=k[1]:
                select = k

        # Policy Open only applies to aut-nums. as-blocks must be defined by parent mntners only.
        #
        #   if policy[select]["policy"] == "open":
        #       log.notice("Policy is open for parent object")
        #       return "PASS"

        # 3. Check if mntner or mnt-lower for any as-block in the tree.
        if mntner in mntners:
            log.notice("%s has mnt in parent object" %(mntner))
            return "PASS"
        pass

    log.error("%s does not pass checks for %s %s" %(mntner, obj_type, name))
    return "FAIL"

def sanity_check(dom):
#    log.info(dom.keys)
#    log.info(dom.dom)
    ck = "PASS"
    if dom.schema == "dn42.inetnum":
        cidr = dom.get("cidr")
        Lnet, Hnet, mask = inetrange(cidr)
        cidr_range = pretty_ip(Lnet) + "-" + pretty_ip(Hnet)
        file_range = dom.get("inetnum")
        file_range = re.sub(r"\s+", "", file_range, flags=re.UNICODE)

        if cidr_range != file_range:
            log.error("inetnum range [%s] does not match: [%s]" %(file_range, cidr_range))
            ck = "FAIL"

    if dom.schema == "dn42.inet6num":
        cidr = dom.get("cidr")
        log.info(cidr)
        Lnet, Hnet, mask = inet6range(cidr)
        cidr_range = pretty_ip(Lnet) + "-" + pretty_ip(Hnet)
        file_range = dom.get("inet6num")
        file_range = re.sub(r"\s+", "", file_range, flags=re.UNICODE)

        if cidr_range != file_range:
            log.error("inetnum range [%s] does not match: [%s]" %(file_range, cidr_range))
            ck = "FAIL"

    return ck

def get_args():
    """Get and parse command line arguments"""

    parser = argparse.ArgumentParser(
        description='Check Schema. Checks Schema of file for validity')
    parser.add_argument('--merge-output',
                        help="Merge stderr into stdout (helps when reading output with pagers) [Default OFF]",     action="store_true")
    parser.add_argument('-v',  '--verbose',
                        help="Enable verbose output [Default OFF]",        action="store_true")
    parser.add_argument('-vv', '--doubleVerbose',
                        help="Enable full verbose output [Default OFF]",   action="store_true")

    subparsers = parser.add_subparsers(help='sub-command help', dest="command")

    parser_file = subparsers.add_parser(
        'check-file', help='Process a specific file')
    parser_file.add_argument('-s',  '--use-schema',    nargs='?',
                             help="Override schema to validate [Default None]", action="store")
    parser_file.add_argument(
        'infile',                 nargs="?", help="File to check", type=str)

    parser_schema = subparsers.add_parser(
        'check-schemas', help='Validate all schemas')
    parser_schema.add_argument(
        'path',          nargs="?", help="Path for schemas", type=str)

    parser_index = subparsers.add_parser('index', help='Generate index')
    parser_index.add_argument(
        'path',  nargs="?", help="Path for dn42 data", type=str)

    parser_scanindex = subparsers.add_parser(
        'scan-index', help='Validate files in index')
    parser_scanindex.add_argument(
        'infile',  nargs="?", help="Index file to scan", type=str)
    parser_scanindex.add_argument('-m',  '--use-mntner', nargs='?',
                                  help="Only scan files that has MNT [Default None]", action="store")

    parser_scan = subparsers.add_parser('scan', help='Validate files in index')
    parser_scan.add_argument(
        'path',  nargs="?", help="Path for dn42 data", type=str)
    parser_scan.add_argument('-m',  '--use-mntner', nargs='?',
                             help="Only scan files that has a matching MNT [Default None]", action="store")
    parser_scan.add_argument('-f',  '--use-file', nargs='?',
                             help="Only scan file given [Default None]", action="store")

    parser_fmt = subparsers.add_parser('fmt', help='Format file')
    parser_fmt.add_argument(
        'infile',  nargs="?", help="Path for dn42 data file", type=str)
    parser_fmt.add_argument('-i',  '--in-place',
                             help="Format file in place", action="store_true")

    parser_sane = subparsers.add_parser('sanity-check', help='Check the file for sane-ness')
    parser_sane.add_argument(
        'infile',  nargs="?", help="Path for dn42 data file", type=str)

    parser_pol = subparsers.add_parser('policy', help='Format file')
    parser_pol.add_argument('type',   nargs="?", type=str, help="dn42 object type")
    parser_pol.add_argument('name',   nargs="?", type=str, help="dn42 object name")
    parser_pol.add_argument('mntner', nargs="?", type=str, help="dn42 object mntner")

    parser_mroute = subparsers.add_parser('match-routes', help='Match routes to inetnums')

    return vars(parser.parse_args())


if __name__ == '__main__':
    args = get_args()

    if args["merge_output"]:
        log.OUTPUT = sys.stdout

    if args["doubleVerbose"]:
        log.default.level_console = log.VERB_DEBUG
        log.default.level_full = True

    if args["verbose"]:
        log.default.level_console = log.VERB_INFO

    log.debug(args)

    valid = True
    if args["command"] == "check-file":
        valid = main(args["infile"], args["use_schema"])
        if valid:
            log.notice("Check %s: PASS" % (args["infile"]))
        else:
            log.fatal("Check %s: FAIL" % (args["infile"]))

    elif args["command"] == "check-schemas":
        valid = check_schemas(args["path"])

    elif args["command"] == "index":
        index_files(args["path"])

    elif args["command"] == "scan-index":
        scan_index(args["infile"], args["use_mntner"])

    elif args["command"] == "scan":
        import time
        log.notice("## Scan Started at %s" %(time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())))
        ck = scan_files(args["path"], args["use_mntner"], args["use_file"])
        log.notice("## Scan Completed at %s" %(time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime())))

        if ck == "INFO":
            sys.exit(2)
        elif ck == "FAIL":
            sys.exit(1)

    elif args["command"] == "fmt":
        dom = FileDOM(args["infile"])
        if args["in_place"]:
            with open(args["infile"], 'w+') as f:
                f.write(str(dom))
        else:
            print(str(dom))

    elif args["command"] == "policy":

        if args["type"] is None:
            log.fatal("Type should be provided")

        if args["name"] is None:
            log.fatal("Name should be provided")

        if args["mntner"] is None:
            log.fatal("Mntner should be provided")

        if args["type"] in ["inetnum","inet6num","route","route6"]:
            args["name"] = args["name"].replace("_","/")

        status = test_policy(args["type"], args["name"], args["mntner"])

        print("POLICY %-12s\t%-8s\t%20s\t%s" %(args["mntner"], args["type"], args["name"], status))
        if status != "PASS":
            sys.exit(1)

    elif args["command"] == "sanity-check":
        dom = FileDOM(args["infile"])
        ck = sanity_check(dom)
        print("SANITY %-8s\t%20s\t%s" %(dom.schema.split(".")[1], args["infile"], ck))
        if ck != "PASS":
            sys.exit(1)

    elif args["command"] == "match-routes":
        lis = find(["mnt-by","cidr","route","@netlevel", "@netmin", "@netmax", "@uri"], {"@family":"ipv4"})

        def field(x, field):
            for i in x:
                if i[0] == field:
                    return i[1]
            return None

        def lvl(x):
            for i in x:
                if i[0] == "@netlevel":
                    return i[1]

        def net(x):
            for i in x:
                if i[0] == "@netmin":
                    return i[1]

        def is_net(x):
            i = field(x, "cidr")
            if i is not None:
                    return True
            return False

        def obj(x):
            d = {}
            for k,v in x:
                if k in d:
                    d[k].append(v)
                else:
                    d[k] = [v]
            return d

        inet = None
        first = True
        for n in sorted(sorted(lis, key=lvl), key=net):
            o = obj(n)

            if is_net(n):
                if not first:
                    print()
                first = True
                inet = o
                continue

            ilvl = int(inet["@netlevel"][0])
            rlvl = int(o["@netlevel"][0])

            if ilvl + 1 != rlvl:
                print("\nNo Parent > ", o["route"][0], " ", rlvl, " ", ",".join(o["mnt-by"]), \
                      "Nearest INET ", inet["cidr"][0], " ", ilvl, " ", ",".join(inet["mnt-by"]))

                first = True
                continue

            if inet["@netmin"][0] > o["@netmin"][0] or inet["@netmax"][0] < o["@netmax"][0]:
                print("\nNo Parent > ", o["route"][0], " ", rlvl, " ", ",".join(o["mnt-by"]), \
                      "Nearest INET ", inet["cidr"][0], " ", ilvl, " ", ",".join(inet["mnt-by"]))

                first = True
                continue

            continue
            if first:
                first = False
                print(inet["cidr"]," ", ilvl, ",".join(inet["mnt-by"]))
                print("            > ", o["route"][0], " ", rlvl, " ", ",".join(o["mnt-by"]))
            else:
                print("            > ", o["route"][0], " ", rlvl, " ", ",".join(o["mnt-by"]))
