#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2020 Chris Johns (chrisj@rtems.org)
# All rights reserved.
#
# This file is part of the RTEMS Tools package in 'rtems-tools'.
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

#
# This code builds a package compiler tool suite given a tool set. A tool
# set lists the various tools. These are specific tool configurations.
#

from __future__ import print_function

import argparse
import copy
import datetime
import os
import sys

try:
    from . import build
    from . import error
    from . import git
    from . import log
    from . import simhost
    from . import version
except KeyboardInterrupt:
    print('abort: user terminated', file=sys.stderr)
    sys.exit(1)
except:
    raise


def unique(l):
    return sorted(list(set(l)))


def filter_deps(deps, ext):
    rdeps = []
    for d in deps:
        ds = d.split(':', 2)
        if ds[0].endswith(ext):
            rdeps += [ds[0] + ':' + ds[1]]
    return sorted(rdeps)


def normalise_paths(includes, root):
    normalised = []
    for inc in unique(includes):
        config, parent = inc.split(':', 2)
        if config.startswith(root):
            config = config[len(root):]
        if parent.startswith(root):
            parent = parent[len(root):]
        normalised += [config + ':' + parent]
    return normalised


def process_dependencies(includes):
    deps = {}
    incs = [i.split(':', 2) for i in includes]
    for config, parent in incs:
        if parent not in deps:
            deps[parent] = []
        for inc in incs:
            if inc[1] == parent:
                deps[parent] += [inc[0]]
    for d in deps:
        deps[d] = unique(deps[d])
    return deps


def includes_str(includes):
    o = []
    deps = [i.split(':', 2) for i in includes]
    ll = max([len(d[1]) for d in deps])
    for d in deps:
        o += ['%*s %s' % (ll, d[1], d[0])]
    return o


def deps_str(deps):

    def print_node(deps, node, level=0, prefix='', indent=''):
        o = []
        if node != 'root':
            level += 1
            if level == 1:
                o += ['']
            o += [prefix + '+-- ' + node]
        if node in deps:
            prefix += indent
            for c, child in enumerate(deps[node], start=1):
                if c < len(deps[node]) and level > 1:
                    indent = '|    '
                else:
                    indent = '     '
                o += print_node(deps, child, level, prefix, indent)
        return o

    return print_node(deps, 'root')


def run(args=sys.argv):
    ec = 0
    output = []
    try:
        #
        # The RSB options support cannot be used because it loads the defaults
        # for the host which we cannot do here.
        #
        description = 'RTEMS Track Dependencies a build set has for all hosts.'

        argsp = argparse.ArgumentParser(prog='sb-track',
                                        description=description)
        argsp.add_argument('--rtems-version',
                           help='Set the RTEMS version.',
                           type=str,
                           default=version.version())
        argsp.add_argument('--list-hosts',
                           help='List the hosts.',
                           action='store_true')
        argsp.add_argument('--list-bsets',
                           help='List the hosts.',
                           action='store_true')
        argsp.add_argument('--output',
                           help='Output file.',
                           type=str,
                           default=None)
        argsp.add_argument('--log',
                           help='Log file.',
                           type=str,
                           default=simhost.log_default('track'))
        argsp.add_argument('--trace',
                           help='Enable trace logging for debugging.',
                           action='store_true')
        argsp.add_argument(
            '--not-referenced',
            help='Write out the list of config files not referenced.',
            action='store_true')
        argsp.add_argument('bsets', nargs='*', help='Build sets.')

        argopts = argsp.parse_args(args[1:])

        simhost.load_log(argopts.log)
        log.notice('RTEMS Source Builder - Track Dependencies, %s' %
                   (version.string()))
        log.tracing = argopts.trace

        opts = simhost.load_options(args, argopts, extras=['---keep-going'])
        configs = build.get_configs(opts)

        if argopts.list_hosts:
            simhost.list_hosts()
        elif argopts.list_bsets:
            simhost.list_bset_files(opts, configs)
        else:
            all_bsets = simhost.get_bset_files(configs)
            if len(argopts.bsets) == 0:
                bsets = all_bsets
            else:
                bsets = argopts.bsets
            includes = []
            errors = []
            for bset in bsets:
                b = None
                try:
                    if not bset.endswith('.bset'):
                        bset += '.bset'
                    includes += [bset + ':root']
                    for host in simhost.profiles:
                        b = simhost.buildset(bset, configs, opts)
                        b.build(host)
                        includes += b.includes()
                        errors += b.errors()
                        del b
                except error.general as gerr:
                    log.stderr(str(gerr))
                    log.stderr('Build FAILED')
                    if b:
                        includes += b.includes()
                        errors += b.errors()
                b = None
            root = simhost.get_root(configs)
            all_configs = simhost.get_config_files(configs, True)
            includes = normalise_paths(includes, root)
            bsets = filter_deps(includes, '.bset')
            configs = filter_deps(includes, '.cfg')
            deps_tree = deps_str(process_dependencies(bsets + configs))
            bsets = unique([b.split(':', 2)[0] for b in bsets])
            configs = unique([i.split(':', 2)[0] for i in configs])
            not_used_configs = [c for c in all_configs if c not in configs]
            if len(errors) > 0:
                errors = [
                    e.split(':', 2)[0] for e in normalise_paths(errors, root)
                ]
            output = [
                'RSB Dependency Tracker', '',
                'Total buildsets: %d' % (len(all_bsets)),
                'Total configs: %d' % (len(all_configs)), ''
            ]
            if len(errors) > 0:
                output += ['Errored File Set (%d):' % (len(errors)),
                           ''] + \
                           errors + \
                           ['']
            if len(configs) > 0:
                output += ['Include Tree(s):',
                           ''] + \
                           deps_tree + \
                           ['']
            if len(bsets) > 0:
                output += ['Buildsets (%d):' % (len(bsets)),
                           ''] + \
                           bsets + \
                           ['']
            if len(configs) > 0:
                output += ['Configurations (%d):' % (len(configs)),
                           ''] + \
                           configs + \
                           ['']
            if argopts.not_referenced and len(not_used_configs) > 0:
                output += ['Not referenced (%d): ' % (len(not_used_configs)),
                           ''] + \
                           not_used_configs
            output = os.linesep.join(output)
            if argopts.output:
                o = open(argopts.output, "w")
                o.write(output)
                o.close
            else:
                print()
                print(output)
    except error.general as gerr:
        log.stderr(str(gerr))
        log.stderr('Build FAILED')
        ec = 1
    except error.internal as ierr:
        log.stderr(str(ierr))
        log.stderr('Internal Build FAILED')
        ec = 1
    except error.exit as eerr:
        pass
    except KeyboardInterrupt:
        log.notice('abort: user terminated')
        ec = 1
    except:
        raise
        log.notice('abort: unknown error')
        ec = 1
    sys.exit(ec)


if __name__ == "__main__":
    run()
