#!/usr/bin/env python3
#
# Copyright 2017, Sam Thursfield <sam@afuera.me.uk>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA  02110-1301, USA.


'''g-ir-merge: combine multiple GObject Introspection namespaces together.

This tool exists to solve a problem in Tracker: we have code for
libtracker-sparql written in both C and Vala. It's not possible to generate
a single .gir for all of this code as `g-ir-scanner` can't deal with Vala's
generated .c code, and `valac` only handles .vala code.

The only workable solution seems to be to generate two different .gir files
and manually combine them. Thankfully it's not too difficult to do this.

For more discussion, see: https://bugzilla.gnome.org/show_bug.cgi?id=782091

'''


import argparse
import sys
from xml.etree import ElementTree

CORE_NAMESPACE = 'http://www.gtk.org/introspection/core/1.0'
C_NAMESPACE = 'http://www.gtk.org/introspection/c/1.0'
GLIB_NAMESPACE = 'http://www.gtk.org/introspection/glib/1.0'

def argument_parser():
    parser = argparse.ArgumentParser(
        description="Merge .gir repositories together.")

    parser.add_argument('--namespace', '-n', metavar='NAMESPACE_NAME', type=str, required=True,
                        help="name for combined .gir namespace")
    parser.add_argument('--nsversion', metavar='NAMESPACE_VERSION', type=str, required=True,
                        help="version for combined .gir namespace")

    parser.add_argument('--c-include', metavar='C_INCLUDES', type=list,
                        help="override list of C includes")

    parser.add_argument('files', metavar="GIR_FILE", type=str, nargs='+',
                        help="input .gir file")
    return parser


def parse_inputs(files):
    '''Read the important contents from one or more .gir files and return all.

    This does no post-processing of the data, it simply returns everything
    including any duplicate or contradictory info.

    '''
    ns = {
        'core': CORE_NAMESPACE,
        'c': C_NAMESPACE
    }

    includes = []
    namespaces = []
    c_includes = []

    for file in files:
        gi_repository = ElementTree.parse(file).getroot()

        for gi_include in gi_repository.findall('core:include', ns):
            includes.append(gi_include)

        for gi_namespace in gi_repository.findall('core:namespace', ns):
            namespaces.append(gi_namespace)

        for gi_c_include in gi_repository.findall('c:include', ns):
            c_includes.append(gi_c_include)

    return includes, namespaces, c_includes


def merge_includes(all_includes):
    merged = {}
    for element in all_includes:
        name = element.get('name')
        version = element.get('version')
        if name not in merged:
            merged[name] = element
    return merged.values()


def merge_namespaces(all_namespaces):
    identifier_prefixes = set()
    symbol_prefixes = set()
    shared_libraries = set()

    contents = []

    for element in all_namespaces:
        if element.get('c:identifier_prefixes', None):
            identifier_prefixes.update(element.get('c:identifier_prefixes').split(','))
        if element.get('c:symbol_prefixes', None):
            symbol_prefixes.update(element.get('c:symbol_prefixes').split(','))
        identifier_prefixes.update(element.get('c:prefix', []))
        symbol_prefixes.update(element.get('c:prefix', []))

        if element.get('shared-library', None):
            shared_libraries.update(element.get('shared-library', '').split(','))

        contents.extend(element)

    return (contents,
            ','.join(sorted(identifier_prefixes)),
            ','.join(sorted(symbol_prefixes)),
            ','.join(sorted(shared_libraries)))


def create_repository(namespace_name, namespace_version, shared_libraries,
                      c_identifier_prefixes, c_symbol_prefixes, includes, namespace_contents):
    '''Create a new GI repository with a single namespace.'''
    ElementTree.register_namespace('', CORE_NAMESPACE)
    ElementTree.register_namespace('c', C_NAMESPACE)
    ElementTree.register_namespace('glib', GLIB_NAMESPACE)
    gir_version = '1.2'

    repository = ElementTree.Element('repository', attrib={'version': gir_version})
    repository.extend(includes)

    namespace = ElementTree.SubElement(repository, 'namespace', attrib={
            'name': namespace_name,
            'version': namespace_version,
            'shared-library': shared_libraries,
            'c:identifier-prefixes': c_identifier_prefixes,
            'c:symbol-prefixes': c_symbol_prefixes,
        })
    namespace.extend(namespace_contents)

    return repository


def main():
    args = argument_parser().parse_args()

    all_includes, all_namespaces, all_c_includes = parse_inputs(args.files)

    includes = merge_includes(all_includes)

    namespace_contents, identifier_prefixes, symbol_prefixes, shared_libraries \
        = merge_namespaces(all_namespaces)

    repository = create_repository(args.namespace, args.nsversion, shared_libraries,
                                   identifier_prefixes, symbol_prefixes,
                                   includes, namespace_contents)

    print(ElementTree.tostring(repository, encoding="unicode"))


try:
    main()
except RuntimeError as e:
    sys.stderr.write("{}\n".format(e))
    sys.exit(1)
