#!/usr/bin/env python2

from __future__ import print_function


import argparse
import pprint
import sys


def die(msg):
    print(msg, file=sys.stderr)
    sys.exit(1)


def do_unbundle(gypdata, targets):
    gyptargets = {t['target_name']: t for t in gypdata['targets']}
    dropped_deps = set()

    def _unbundle_in_block(gypblock):
        gypdeps = gypblock.get('dependencies') or {}

        for dep, (libs, defines) in unbundlings.items():
            if dep not in gypdeps:
                continue

            gypdeps.remove(dep)

            try:
                ls = gyptarget['link_settings']
            except KeyError:
                ls = gyptarget['link_settings'] = {}

            try:
                gyplibs = ls['libraries']
            except KeyError:
                gyplibs = ls['libraries'] = []

            gyplibs.extend('-l{}'.format(lib) for lib in libs)

            if defines:
                try:
                    dd = gyptarget['defines']
                except KeyError:
                    dd = gyptarget['defines'] = []

                dd.extend(defines)

            dropped_deps.add(dep)

        gypconds = gypblock.get('conditions') or []
        for cond in gypconds:
            condblocks = cond[1:]
            for condblock in condblocks:
                _unbundle_in_block(condblock)

    for target, unbundlings in targets.items():
        if target not in gyptargets:
            die('There is no {} target in gyp file'.format(target))

        gyptarget = gyptargets[target]

        _unbundle_in_block(gyptarget)

    for gyptarget in gypdata['targets']:
        if gyptarget['target_name'] in dropped_deps:
            if gyptarget.get('dependencies'):
                dropped_deps.update(gyptarget.get('dependencies'))

    new_targets = []
    for gyptarget in gypdata['targets']:
        if gyptarget['target_name'] not in dropped_deps:
            new_targets.append(gyptarget)

    gypdata['targets'] = new_targets

    gypconds = gypdata.get('conditions')
    if gypconds:
        for cond in gypconds:
            condblocks = cond[1:]
            for condblock in condblocks:
                new_targets = []
                blocktargets = condblock.get('targets')
                if blocktargets:
                    for blocktarget in blocktargets:
                        if blocktarget['target_name'] not in dropped_deps:
                            new_targets.append(blocktarget)
                    condblock['targets'] = new_targets


def main():
    parser = argparse.ArgumentParser(description='Unbundle libs in gyp files')
    parser.add_argument('gypfile', type=str, help='input gyp file')
    parser.add_argument(
        '--unbundle', type=str, action='append',
        help='unbundle rule in the format '
             '<target>;<dep>;<lib>[;lib][;-DMACRO]')
    parser.add_argument(
        '-i', '--inplace', action='store_true',
        help='modify gyp file in-place')

    args = parser.parse_args()

    targets = {}

    for unbundle in args.unbundle:
        rule = list(filter(None, (i.strip() for i in unbundle.split(';'))))
        if len(rule) < 3:
            die('Invalid unbundle rule: {!r}'.format(unbundle))
        target, dep = rule[:2]

        defines = []
        libs = []

        for item in rule[2:]:
            if item.startswith('-D'):
                defines.append(item[2:])
            else:
                libs.append(item)

        try:
            target_unbundlings = targets[target]
        except KeyError:
            target_unbundlings = targets[target] = {}

        target_unbundlings[dep] = libs, defines

    with open(args.gypfile, 'rt') as f:
        gypdata = eval(f.read())

    do_unbundle(gypdata, targets)

    if args.inplace:
        with open(args.gypfile, 'wt') as f:
            pprint.pprint(gypdata, stream=f)
    else:
        pprint.pprint(gypdata)


if __name__ == '__main__':
    main()
