#!/usr/bin/env python
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Suggested call sequence:

python tools/js/bower2bazel.py -w lib/js/bower_archives.bzl \
  -b lib/js/bower_components.bzl
"""

from __future__ import print_function

import collections
import json
import hashlib
import optparse
import os
import subprocess
import sys
import tempfile
import glob
import bowerutil

# list of licenses for packages that don't specify one in their bower.json file
package_licenses = {
    "codemirror-minified": "codemirror-minified",
    "es6-promise": "es6-promise",
    "fetch": "fetch",
    "font-roboto": "polymer",
    "iron-a11y-announcer": "polymer",
    "iron-a11y-keys-behavior": "polymer",
    "iron-autogrow-textarea": "polymer",
    "iron-behaviors": "polymer",
    "iron-dropdown": "polymer",
    "iron-fit-behavior": "polymer",
    "iron-flex-layout": "polymer",
    "iron-form-element-behavior": "polymer",
    "iron-icon": "polymer",
    "iron-iconset-svg": "polymer",
    "iron-input": "polymer",
    "iron-menu-behavior": "polymer",
    "iron-meta": "polymer",
    "iron-overlay-behavior": "polymer",
    "iron-resizable-behavior": "polymer",
    "iron-selector": "polymer",
    "iron-validatable-behavior": "polymer",
    "moment": "moment",
    "neon-animation": "polymer",
    "page": "page.js",
    "paper-button": "polymer",
    "paper-icon-button": "polymer",
    "paper-input": "polymer",
    "paper-item": "polymer",
    "paper-listbox": "polymer",
    "paper-toggle-button": "polymer",
    "paper-styles": "polymer",
    "paper-tabs": "polymer",
    "polymer": "polymer",
    "polymer-resin": "polymer",
    "promise-polyfill": "promise-polyfill",
    "web-animations-js": "Apache2.0",
    "webcomponentsjs": "polymer",
    "paper-material": "polymer",
    "paper-styles": "polymer",
    "paper-behaviors": "polymer",
    "paper-ripple": "polymer",
    "iron-checked-element-behavior": "polymer",
    "font-roboto": "polymer",
}


def build_bower_json(version_targets, seeds):
    """Generate bower JSON file, return its path.

    Args:
      version_targets: bazel target names of the versions.json file.
      seeds: an iterable of bower package names of the seed packages, ie.
        the packages whose versions we control manually.
    """
    bower_json = collections.OrderedDict()
    bower_json['name'] = 'bower2bazel-output'
    bower_json['version'] = '0.0.0'
    bower_json['description'] = 'Auto-generated bower.json for dependency ' + \
                                'management'
    bower_json['private'] = True
    bower_json['dependencies'] = {}

    seeds = set(seeds)
    for v in version_targets:
        path = os.path.join("bazel-out/*-fastbuild/bin",
                            v.lstrip("/").replace(":", "/"))
        fs = glob.glob(path)
        err_msg = '%s: file not found or multiple files found: %s' % (path, fs)
        assert len(fs) == 1, err_msg
        with open(fs[0]) as f:
            j = json.load(f)
            if "" in j:
                # drop dummy entries.
                del j[""]

            trimmed = {}
            for k, v in j.items():
                if k in seeds:
                    trimmed[k] = v

            bower_json['dependencies'].update(trimmed)

    tmpdir = tempfile.mkdtemp()
    ret = os.path.join(tmpdir, 'bower.json')
    with open(ret, 'w') as f:
        json.dump(bower_json, f, indent=2)
    return ret


def decode(input):
    try:
        return input.decode("utf-8")
    except TypeError:
        return input


def bower_command(args):
    base = subprocess.check_output(["bazel", "info", "output_base"]).strip()
    exp = os.path.join(decode(base), "external", "bower", "*npm_binary.tgz")
    fs = sorted(glob.glob(exp))
    err_msg = "bower tarball not found or have multiple versions %s" % fs
    assert len(fs) == 1, err_msg
    return ["python",
            os.getcwd() + "/tools/js/run_npm_binary.py", sorted(fs)[0]] + args


def main(args):
    opts = optparse.OptionParser()
    opts.add_option('-w', help='.bzl output for WORKSPACE')
    opts.add_option('-b', help='.bzl output for //lib:BUILD')
    opts, args = opts.parse_args()

    target_str = subprocess.check_output([
        "bazel", "query", "kind(bower_component_bundle, //polygerrit-ui/...)"])
    seed_str = subprocess.check_output(
        ["bazel", "query",
         "attr(seed, 1, kind(bower_component, deps(//polygerrit-ui/...)))"])
    targets = [s for s in decode(target_str).split('\n') if s]
    seeds = [s for s in decode(seed_str).split('\n') if s]
    prefix = "//lib/js:"
    non_seeds = [s for s in seeds if not s.startswith(prefix)]
    assert not non_seeds, non_seeds
    seeds = set([s[len(prefix):] for s in seeds])

    version_targets = [t + "-versions.json" for t in targets]
    subprocess.check_call(['bazel', 'build'] + version_targets)
    bower_json_path = build_bower_json(version_targets, seeds)
    dir = os.path.dirname(bower_json_path)
    cmd = bower_command(["install"])

    build_out = sys.stdout
    if opts.b:
        build_out = open(opts.b + ".tmp", 'w')

    ws_out = sys.stdout
    if opts.b:
        ws_out = open(opts.w + ".tmp", 'w')

    header = """# DO NOT EDIT
# generated with the following command:
#
#   %s
#

""" % ' '.join(sys.argv)

    ws_out.write(header)
    build_out.write(header)

    oldwd = os.getcwd()
    os.chdir(dir)
    subprocess.check_call(cmd)

    interpret_bower_json(seeds, ws_out, build_out)
    ws_out.close()
    build_out.close()

    os.chdir(oldwd)
    os.rename(opts.w + ".tmp", opts.w)
    os.rename(opts.b + ".tmp", opts.b)


def dump_workspace(data, seeds, out):
    out.write('load("//tools/bzl:js.bzl", "bower_archive")\n\n')
    out.write('def load_bower_archives():\n')

    for d in data:
        if d["name"] in seeds:
            continue
        out.write("""  bower_archive(
    name = "%(name)s",
    package = "%(normalized-name)s",
    version = "%(version)s",
    sha1 = "%(bazel-sha1)s")
""" % d)


def dump_build(data, seeds, out):
    out.write('load("//tools/bzl:js.bzl", "bower_component")\n\n')
    out.write('def define_bower_components():\n')
    for d in data:
        out.write("  bower_component(\n")
        out.write("    name = \"%s\",\n" % d["name"])
        out.write("    license = \"//lib:LICENSE-%s\",\n" % d["bazel-license"])
        deps = sorted(d.get("dependencies", {}).keys())
        if deps:
            if len(deps) == 1:
                out.write("    deps = [ \":%s\" ],\n" % deps[0])
            else:
                out.write("    deps = [\n")
                for dep in deps:
                    out.write("      \":%s\",\n" % dep)
                out.write("    ],\n")
        if d["name"] in seeds:
            out.write("    seed = True,\n")
        out.write("  )\n")
    # done


def interpret_bower_json(seeds, ws_out, build_out):
    out = subprocess.check_output(["find", "bower_components/", "-name",
                                   ".bower.json"])

    data = []
    for f in sorted(decode(out).split('\n')):
        if not f:
            continue
        pkg = json.load(open(f))
        pkg_name = pkg["name"]

        pkg["bazel-sha1"] = bowerutil.hash_bower_component(
            hashlib.sha1(), os.path.dirname(f)).hexdigest()
        license = package_licenses.get(pkg_name, "DO_NOT_DISTRIBUTE")

        pkg["bazel-license"] = license
        pkg["normalized-name"] = pkg["_originalSource"]
        data.append(pkg)

    dump_workspace(data, seeds, ws_out)
    dump_build(data, seeds, build_out)


if __name__ == '__main__':
    main(sys.argv[1:])
