#!/usr/bin/env python3
"""Tool for visualising ontologies."""
import sys
import os
import argparse
import json

# Support running from uninstalled package by adding parent dir to sys path
rootdir = os.path.abspath(os.path.realpath((os.path.dirname(
    os.path.dirname(__file__)))))
sys.path.insert(1, rootdir)

from emmo import World, onto_path
from emmo.graph import OntoGraph, plot_modules

import owlready2


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        'iri', metavar='IRI',
        help='File name or URI of the ontology to visualise.')
    parser.add_argument(
        'output', nargs='?',
        help='name of output file.')

    parser.add_argument(
        '--format', '-f',
        help='Format of output file.  By default it is inferred from '
        'the output file extension.')
    parser.add_argument(
        '--database', '-d', metavar='FILENAME', default=':memory:',
        help='Load ontology from Owlready2 sqlite3 database.  The `iri` '
        'argument should in this case be the IRI of the ontology you '
        'want to visualise.')
    parser.add_argument(
        '--local', '-l', action='store_true',
        help='Load imported ontologies locally.  Their paths are specified '
        'in Protègè catalog files or via the --path option.  The IRI should '
        'be a file name.')
    parser.add_argument(
        '--catalog-file', default='catalog-v001.xml',
        help='Name of Protègè catalog file in the same folder as the '
        'ontology.  This option is used together with --local and '
        'defaults to "catalog-v001.xml".')
    parser.add_argument(
        '--path', action='append', default=[],
        help='Paths where imported ontologies can be found.  May be provided '
        'as a comma-separated string and/or with multiple --path options.')
    parser.add_argument(
        '--reasoner', nargs='?', const='HermiT', choices=['HermiT', 'Pellet'],
        help='Run given reasoner on the ontology.  Valid reasoners are '
        '"HermiT" (default) and "Pellet".  Note: these reasoners doesn\'t '
        'work well with EMMO.')
    parser.add_argument(
        '--root', '-r',
        help='Name of root node in the graph.  Defaults to all classes.')
    parser.add_argument(
        '--leafs', action='append', default=[],
        help='Leafs nodes for plotting sub-graphs.  May be provided as '
        'a comma-separated string and/or with multiple --leafs options.')
    parser.add_argument(
        '--exclude', '-E', action='append', default=[],
        help='Nodes, including their subclasses, to exclude from sub-graphs. '
        'May be provided as a comma-separated string and/or with multiple '
        '--exclude options.')
    parser.add_argument(
        '--parents', '-p', metavar='N', type=int, default=0,
        help='Adds N levels of parents to graph.')
    parser.add_argument(
        '--relations', '-R', default='isA',
        help='Comma-separated string of relations to visualise.  Default is '
        '"isA".  "all" means include all relations.')
    parser.add_argument(
        '--edgelabels', '-e', action='store_true',
        help='Whether to add labels to edges.')
    parser.add_argument(
        '--addnodes', '-n', action='store_true',
        help='Whether to add missing target nodes in relations.')
    parser.add_argument(
        '--addconstructs', '-c', action='store_true',
        help='Whether to add nodes representing class constructs.')
    parser.add_argument(
        '--rankdir', default='BT', choices=['BT', 'TB', 'RL', 'LR'],
        help='Graph direction (from leaves to root).  Possible values are: '
        '"BT" (bottom-top, default), "TB" (top-bottom), "RL" (right-left) and '
        '"LR" (left-right).')
    parser.add_argument(
        '--style-file', '-s', metavar='JSON_FILE',
        help='A json file with style definitions.')
    parser.add_argument(
        '--legend', '-L', action='store_true',
        help='Whether to add a legend to the graph.')
    parser.add_argument(
        '--generate-style-file', '-S', metavar='JSON_FILE',
        help='Write default style file to a json file.')
    parser.add_argument(
        '--plot-modules', '-m', action='store_true',
        help='Whether to plot module inter-dependencies instead of their '
        'content.')
    parser.add_argument(
        '--display', '-D', action='store_true',
        help='Whether to display graph.')
    args = parser.parse_args()

    # Append to onto_path
    for paths in args.path:
        for path in paths.split(','):
            if path not in onto_path:
                onto_path.append(path)

    # Customise style
    style = OntoGraph._default_style
    style['graph']['rankdir'] = args.rankdir

    # Update style from json file
    if args.style_file:
        with open(args.style_file, 'rt') as f:
            style.update(json.load(f))

    # Write style to json file
    if args.generate_style_file:
        with open(args.generate_style_file, 'wt') as f:
            json.dump(style, f, indent=4)

    # Join all --leafs options
    leafs = set()
    for leaf in args.leafs:
        if ',' in leaf:
            leafs.update(leaf.split(','))
        else:
            leafs.add(leaf)

    # Join all --exclude options
    exclude = set()
    for e in args.exclude:
        if ',' in e:
            exclude.update(e.split(','))
        else:
            exclude.add(e)

    # Split relations
    relations = None
    if args.relations:
        relations = [r.strip() for r in args.relations.split(',')]

    # Load ontology
    world = World(filename=args.database)
    if args.database != ':memory:' and args.iri not in world.ontologies:
        parser.error('The IRI argument should be one of the ontologies in '
                     'the database:\n  ' +
                     '\n  '.join(world.ontologies.keys()))
    onto = world.get_ontology(args.iri)
    catalog_file = args.catalog_file if args.local else None
    try:
        onto.load(only_local=args.local, catalog_file=catalog_file)
    except owlready2.OwlReadyOntologyParsingError as e:
        parser.error('error parsing %r: %s' % (args.iri, e))

    # Sync reasoner
    if args.reasoner:
        onto.sync_reasoner(reasoner=args.reasoner)

    # Plot modules
    if args.plot_modules:
        plot_modules(onto, filename=args.output, format=args.format,
                     show=args.display, ignore_redundant=True)
        return

    # Create graph
    graph = OntoGraph(onto, style=style)
    graph.add_branch(
        root=args.root, leafs=leafs, exclude=exclude,
        relations=relations,
        edgelabels=args.edgelabels, addnodes=args.addnodes,
        addconstructs=args.addconstructs,
        #parents=args.parents
    )
    if args.legend:
        graph.add_legend()

    # Plot and display
    if args.output:
        graph.save(args.output, format=args.format)

    if not args.output or args.display:
        graph.view()


if __name__ == '__main__':
    main()
