#!/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 = {
  "es6-promise": "es6-promise",
  "fetch": "fetch",
  "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-input": "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",
  "polymer": "polymer",
  "promise-polyfill": "promise-polyfill",
  "web-animations-js": "Apache2.0",
  "webcomponentsjs": "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)
    assert len(fs) == 1, '%s: file not found or multiple files found: %s' % (path, fs)
    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 bower_command(args):
  base = subprocess.check_output(["bazel", "info", "output_base"]).strip()
  exp = os.path.join(base, "external", "bower", "*npm_binary.tgz")
  fs = sorted(glob.glob(exp))
  assert len(fs) == 1, "bower tarball not found or have multiple versions %s" % fs
  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 target_str.split('\n') if s]
  seeds = [s for s in 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(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

    # TODO(hanwen): bower packages can also have 'fully qualified'
    # names, ("PolymerElements/iron-ajax") as well as short names
    # ("iron-ajax").  It is possible for bower.json files to refer to
    # long names as their dependencies. If any package does this, we
    # will have to either 1) strip off the prefix (typically github
    # user?), or 2) build a map of short name <=> fully qualified
    # name. For now, we just ignore the problem.
    pkg["normalized-name"] = pkg["name"]
    data.append(pkg)

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


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