#!/usr/bin/env python3
#  Copyright  2019 Alexis Lopez Zubieta
#
#  Permission is hereby granted, free of charge, to any person obtaining a
#  copy of this software and associated documentation files (the "Software"),
#  to deal in the Software without restriction, including without limitation the
#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
#  sell copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
import os
import logging
import argparse

from AppImageBuilder.AppDir2 import AppDir2

from AppImageBuilder.tools import Linker

from AppImageBuilder.tools import Dpkg


class Node:
    path = ""
    children = []

    def __init__(self, path):
        self.path = path


class Graph:
    relations = {}
    _dependencies_count = {}

    def add(self, path, dependencies_paths):
        if path in self.relations:
            self.relations[path].update(dependencies_paths)
        else:
            self.relations[path] = set(dependencies_paths)

        if path not in self._dependencies_count:
            self._dependencies_count[path] = 0

        for dependency_path in dependencies_paths:
            if dependency_path in self._dependencies_count:
                self._dependencies_count[dependency_path] = self._dependencies_count[dependency_path] + 1
            else:
                self._dependencies_count[dependency_path] = 1

    def roots(self):
        root_list = []
        for path, count in self._dependencies_count.items():
            if count == 0:
                root_list.append(path)

        return root_list

    def known(self, path):
        return path in self.relations

    def print_dependencies(self, node, app_dir_path, indentation=0, visited=None):
        if not visited:
            visited = set()

        visited.add(node)

        print('%s%s' % ('----' * indentation, node.replace(app_dir_path, '')))
        children_indentation = indentation + 1

        for child in self.relations[node]:
            if child not in visited:
                self.print_dependencies(child, app_dir_path, children_indentation, visited)


def main():
    parser = argparse.ArgumentParser(description='AppImage crafting tool')
    parser.add_argument('--log', dest='loglevel', default="INFO",
                        help='logging level (default: INFO)')
    parser.add_argument('AppDir', type=str, help='target AppDir to inspect')
    parser.add_argument('--print-roots', dest='do_print_roots', action='store_true',
                        help='print the dependency graph roots')
    parser.add_argument('--print-roots-packages', dest='do_print_roots_packages', action='store_true',
                        help='print the dependency graph roots packages')
    parser.add_argument('--print-map', dest='do_print_map', action='store_true',
                        help='print the dependency graph packages')

    args = parser.parse_args()
    configure_logger(args.loglevel)

    app_dir = AppDir2(args.AppDir)

    graph = build_dependencies_graph(app_dir)

    if args.do_print_roots:
        for key in graph.roots():
            print(key)

    if args.do_print_map:
        for node in graph.roots():
            graph.print_dependencies(node, app_dir.path)

    if args.do_print_roots_packages:
        root_packages = set()
        dpkg = Dpkg()
        for path in graph.roots():
            original_path = path.replace(app_dir.path, '')
            logging.debug("Looking for file package : %s" % path)
            packages = dpkg.find_owner_packages(original_path)
            logging.debug("File package: %s" % ' '.join(packages))
            root_packages.update(packages)

        for pkg in root_packages:
            print(pkg)


def configure_logger(loglevel):
    numeric_level = getattr(logging, loglevel.upper())
    if not isinstance(numeric_level, int):
        logging.error('Invalid log level: %s' % loglevel)
    logging.basicConfig(level=numeric_level)


def build_dependencies_graph(app_dir):
    linker_path = Linker.find_binary_path(app_dir.path)
    linker = Linker(linker_path)

    linkable_files = linker.list_linkable_files(app_dir.path)

    lib_dirs = get_library_dirs(linkable_files)

    graph = Graph()

    for file in linkable_files:
        logging.debug("Inspecting dependencies of: %s" % file)
        if not graph.known(file):
            dependencies = linker.list_link_dependencies(file, ignore_cache=True, library_dirs=lib_dirs)
            if not dependencies:
                dependencies = set()
            else:
                dependencies = set(dependencies.values())

            dependencies.discard(None)
            logging.debug("%s" % " ".join(dependencies))
            graph.add(file, list(dependencies))
        else:
            logging.debug("Skipped")

    return graph


def get_library_dirs(linkable_files):
    lib_dirs = set()
    for file in linkable_files:
        lib_dirs.add(os.path.dirname(file))
    return lib_dirs


if __name__ == '__main__':
    main()
