#!/usr/bin/env python
# Copyright (C) 2016 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.
#
# TODO(sop): Remove hack after Buck supports Eclipse

from __future__ import print_function
# TODO(davido): use Google style for importing instead:
# import optparse
# ...
# optparse.OptionParser
from optparse import OptionParser
from os import environ, path, makedirs
from subprocess import CalledProcessError, check_call, check_output
from xml.dom import minidom
import re
import sys

MAIN = '//tools/eclipse:classpath'
GWT = '//gerrit-gwtui:ui_module'
AUTO = '//lib/auto:auto-value'
JRE = '/'.join([
  'org.eclipse.jdt.launching.JRE_CONTAINER',
  'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType',
  'JavaSE-1.8',
])
# Map of targets to corresponding classpath collector rules
cp_targets = {
  AUTO: '//tools/eclipse:autovalue_classpath_collect',
  GWT: '//tools/eclipse:gwt_classpath_collect',
  MAIN: '//tools/eclipse:main_classpath_collect',
}

ROOT = path.abspath(__file__)
while not path.exists(path.join(ROOT, 'WORKSPACE')):
  ROOT = path.dirname(ROOT)

opts = OptionParser()
opts.add_option('--plugins', help='create eclipse projects for plugins',
                action='store_true')
opts.add_option('--name', help='name of the generated project',
                action='store', default='gerrit', dest='project_name')
args, _ = opts.parse_args()

def retrieve_ext_location():
  return check_output(['bazel', 'info', 'output_base']).strip()

def gen_bazel_path():
  bazel = check_output(['which', 'bazel']).strip()
  with open(path.join(ROOT, ".bazel_path"), 'w') as fd:
    fd.write("bazel=%s\n" % bazel)
    fd.write("PATH=%s\n" % environ["PATH"])

def _query_classpath(target):
  deps = []
  t = cp_targets[target]
  try:
    check_call(['bazel', 'build', t])
  except CalledProcessError:
    exit(1)
  name = 'bazel-bin/tools/eclipse/' + t.split(':')[1] + '.runtime_classpath'
  deps = [line.rstrip('\n') for line in open(name)]
  return deps

def gen_project(name='gerrit', root=ROOT):
  p = path.join(root, '.project')
  with open(p, 'w') as fd:
    print("""\
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
  <name>%(name)s</name>
  <buildSpec>
    <buildCommand>
      <name>org.eclipse.jdt.core.javabuilder</name>
    </buildCommand>
  </buildSpec>
  <natures>
    <nature>org.eclipse.jdt.core.javanature</nature>
  </natures>
</projectDescription>\
    """ % {"name": name}, file=fd)

def gen_plugin_classpath(root):
  p = path.join(root, '.classpath')
  with open(p, 'w') as fd:
    if path.exists(path.join(root, 'src', 'test', 'java')):
      testpath = """
  <classpathentry excluding="**/BUILD" kind="src" path="src/test/java"\
 out="eclipse-out/test"/>"""
    else:
      testpath = ""
    print("""\
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
  <classpathentry excluding="**/BUILD" kind="src" path="src/main/java"/>%(testpath)s
  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
  <classpathentry combineaccessrules="false" kind="src" path="/gerrit"/>
  <classpathentry kind="output" path="eclipse-out/classes"/>
</classpath>""" % {"testpath": testpath}, file=fd)

def gen_classpath(ext):
  def make_classpath():
    impl = minidom.getDOMImplementation()
    return impl.createDocument(None, 'classpath', None)

  def classpathentry(kind, path, src=None, out=None, exported=None):
    e = doc.createElement('classpathentry')
    e.setAttribute('kind', kind)
    # TODO(davido): Remove this and other exclude BUILD files hack
    # when this Bazel bug is fixed:
    # https://github.com/bazelbuild/bazel/issues/1083
    if kind == 'src':
      e.setAttribute('excluding', '**/BUILD')
    e.setAttribute('path', path)
    if src:
      e.setAttribute('sourcepath', src)
    if out:
      e.setAttribute('output', out)
    if exported:
      e.setAttribute('exported', 'true')
    doc.documentElement.appendChild(e)

  doc = make_classpath()
  src = set()
  lib = set()
  gwt_src = set()
  gwt_lib = set()
  plugins = set()

  # Classpath entries are absolute for cross-cell support
  java_library = re.compile('bazel-out/.*?-fastbuild/bin/(.*)/[^/]+[.]jar$')
  srcs = re.compile('(.*/external/[^/]+)/jar/(.*)[.]jar')
  for p in _query_classpath(MAIN):
    if p.endswith('-src.jar'):
      # gwt_module() depends on -src.jar for Java to JavaScript compiles.
      if p.startswith("external"):
        p = path.join(ext, p)
      gwt_lib.add(p)
      continue

    m = java_library.match(p)
    if m:
      src.add(m.group(1))
      # Exceptions: both source and lib
      if p.endswith('libquery_parser.jar') or \
         p.endswith('prolog/libcommon.jar'):
        lib.add(p)
      # JGit dependency from external repository
      if 'gerrit-' not in p and 'jgit' in p:
        lib.add(p)
    else:
      # Don't mess up with Bazel internal test runner dependencies.
      # When we use Eclipse we rely on it for running the tests
      if p.endswith("external/bazel_tools/tools/jdk/TestRunner_deploy.jar"):
        continue
      if p.startswith("external"):
        p = path.join(ext, p)
      lib.add(p)

  for p in _query_classpath(GWT):
    m = java_library.match(p)
    if m:
      gwt_src.add(m.group(1))

  for s in sorted(src):
    out = None

    if s.startswith('lib/'):
      out = 'eclipse-out/lib'
    elif s.startswith('plugins/'):
      if args.plugins:
        plugins.add(s)
        continue
      out = 'eclipse-out/' + s

    p = path.join(s, 'java')
    if path.exists(p):
      classpathentry('src', p, out=out)
      continue

    for env in ['main', 'test']:
      o = None
      if out:
        o = out + '/' + env
      elif env == 'test':
        o = 'eclipse-out/test'

      for srctype in ['java', 'resources']:
        p = path.join(s, 'src', env, srctype)
        if path.exists(p):
          classpathentry('src', p, out=o)

  for libs in [lib, gwt_lib]:
    for j in sorted(libs):
      s = None
      m = srcs.match(j)
      if m:
        prefix = m.group(1)
        suffix = m.group(2)
        p = path.join(prefix, "jar", "%s-src.jar" % suffix)
        if path.exists(p):
          s = p
      if args.plugins:
        classpathentry('lib', j, s, exported=True)
      else:
        # Filter out the source JARs that we pull through transitive closure of
        # GWT plugin API (we add source directories themself).  Exception is
        # libEdit-src.jar, that is needed for GWT SDM to work.
        m = java_library.match(j)
        if m:
          if m.group(1).startswith("gerrit-") and \
              j.endswith("-src.jar") and \
              not j.endswith("libEdit-src.jar"):
            continue
        classpathentry('lib', j, s)

  for s in sorted(gwt_src):
    p = path.join(ROOT, s, 'src', 'main', 'java')
    if path.exists(p):
      classpathentry('lib', p, out='eclipse-out/gwtsrc')

  classpathentry('con', JRE)
  classpathentry('output', 'eclipse-out/classes')

  p = path.join(ROOT, '.classpath')
  with open(p, 'w') as fd:
    doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8')

  if args.plugins:
    for plugin in plugins:
      plugindir = path.join(ROOT, plugin)
      try:
        gen_project(plugin.replace('plugins/', ""), plugindir)
        gen_plugin_classpath(plugindir)
      except (IOError, OSError) as err:
        print('error generating project for %s: %s' % (plugin, err),
              file=sys.stderr)

def gen_factorypath(ext):
  doc = minidom.getDOMImplementation().createDocument(None, 'factorypath', None)
  for jar in _query_classpath(AUTO):
    e = doc.createElement('factorypathentry')
    e.setAttribute('kind', 'EXTJAR')
    e.setAttribute('id', path.join(ext, jar))
    e.setAttribute('enabled', 'true')
    e.setAttribute('runInBatchMode', 'false')
    doc.documentElement.appendChild(e)

  p = path.join(ROOT, '.factorypath')
  with open(p, 'w') as fd:
    doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8')

try:
  ext_location = retrieve_ext_location()
  gen_project(args.project_name)
  gen_classpath(ext_location)
  gen_factorypath(ext_location)
  gen_bazel_path()

  # TODO(davido): Remove this when GWT gone
  gwt_working_dir = ".gwt_work_dir"
  if not path.isdir(gwt_working_dir):
    makedirs(path.join(ROOT, gwt_working_dir))

  try:
    check_call(['bazel', 'build', MAIN, GWT, '//gerrit-patch-jgit:libEdit-src.jar'])
  except CalledProcessError:
    exit(1)
except KeyboardInterrupt:
  print('Interrupted by user', file=sys.stderr)
  exit(1)