#!/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.

from __future__ import print_function
import argparse
import os
import subprocess
import xml.dom.minidom
import re
import sys

MAIN = '//tools/eclipse:classpath'
AUTO = '//lib/auto:auto-value'

def JRE(java_vers = '11'):
    return '/'.join([
        'org.eclipse.jdt.launching.JRE_CONTAINER',
        'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType',
        "JavaSE-%s" % java_vers,
    ])

# Map of targets to corresponding classpath collector rules
cp_targets = {
    AUTO: '//tools/eclipse:autovalue_classpath_collect',
    MAIN: '//tools/eclipse:main_classpath_collect',
}

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

opts = argparse.ArgumentParser("Create Eclipse Project")
opts.add_argument('--plugins', help='create eclipse projects for plugins',
                  action='store_true')
opts.add_argument('--name', help='name of the generated project',
                  action='store', default='gerrit', dest='project_name')
opts.add_argument('-b', '--batch', action='store_true',
                  dest='batch', help='Bazel batch option')
opts.add_argument('-j', '--java', action='store',
                  dest='java', help='Legacy Java 1.8 or post Java 11')
opts.add_argument('-e', '--edge_java', action='store',
                  dest='edge_java', help='Post Java 11 support (14|...)')
opts.add_argument('--bazel',
                  help=('name of the bazel executable. Defaults to using'
                        ' bazelisk if found, or bazel if bazelisk is not'
                        ' found.'),
                  action='store', default=None, dest='bazel_exe')

args = opts.parse_args()


def find_bazel():
    if args.bazel_exe:
        try:
            return subprocess.check_output(
                ['which', args.bazel_exe]).strip().decode('UTF-8')
        except subprocess.CalledProcessError:
            print('Bazel command: %s not found' % args.bazel_exe, file=sys.stderr)
            sys.exit(1)
    try:
        return subprocess.check_output(
            ['which', 'bazelisk']).strip().decode('UTF-8')
    except subprocess.CalledProcessError:
        try:
            return subprocess.check_output(
                ['which', 'bazel']).strip().decode('UTF-8')
        except subprocess.CalledProcessError:
            print("Neither bazelisk nor bazel found. Please see"
                  " Documentation/dev-bazel for instructions on installing"
                  " one of them.")
            sys.exit(1)


batch_option = '--batch' if args.batch else None
custom_java = args.java
edge_java = args.edge_java
bazel_exe = find_bazel()


def _build_bazel_cmd(*args):
    build = False
    cmd = [bazel_exe]
    if batch_option:
        cmd.append('--batch')
    for arg in args:
        if arg == "build":
            build = True
        cmd.append(arg)
    if custom_java == '1.8':
        cmd.append('--java_toolchain=//tools:error_prone_warnings_toolchain')
    elif custom_java and not edge_java:
        cmd.append('--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java%s' % custom_java)
        cmd.append('--java_toolchain=@bazel_tools//tools/jdk:toolchain_java%s' % custom_java)
        if edge_java and build:
            cmd.append(edge_java)
    return cmd


def retrieve_ext_location():
    return subprocess.check_output(_build_bazel_cmd('info', 'output_base')).strip()


def gen_bazel_path(ext_location):
    bazel = subprocess.check_output(['which', bazel_exe]).strip().decode('UTF-8')
    with open(os.path.join(ROOT, ".bazel_path"), 'w') as fd:
        fd.write("output_base=%s\n" % ext_location)
        fd.write("bazel=%s\n" % bazel)
        fd.write("PATH=%s\n" % os.environ["PATH"])


def _query_classpath(target):
    deps = []
    t = cp_targets[target]
    try:
        subprocess.check_call(_build_bazel_cmd('build', t))
    except subprocess.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 = os.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 = os.path.join(root, '.classpath')
    with open(p, 'w') as fd:
        if os.path.exists(os.path.join(root, 'src', 'test', 'java')):
            testpath = """
  <classpathentry excluding="**/BUILD" kind="src" path="src/test/java"\
 out="eclipse-out/test">
    <attributes><attribute name="test" value="true"/></attributes>
  </classpathentry>"""
        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 = xml.dom.minidom.getDOMImplementation()
        return impl.createDocument(None, 'classpath', None)

    def import_jgit_sources():
        classpathentry('src', 'modules/jgit/org.eclipse.jgit/src')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit/resources')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.archive/src',
            excluding='org/eclipse/jgit/archive/FormatActivator.java')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.archive/resources')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.http.server/src')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.http.server/resources')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.junit/src')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.ssh.jsch/src')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.ssh.jsch/resources')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.ssh.apache/src')
        classpathentry('src', 'modules/jgit/org.eclipse.jgit.ssh.apache/resources')

    def classpathentry(kind, path, src=None, out=None, exported=None, excluding=None):
        e = doc.createElement('classpathentry')
        e.setAttribute('kind', kind)
        # Excluding the BUILD file, to avoid the Eclipse warnings:
        # "The resource is a duplicate of ..."
        if kind == 'src':
            e.setAttribute('excluding', '**/BUILD' if not excluding else excluding)
        e.setAttribute('path', path)
        if src:
            e.setAttribute('sourcepath', src)
        if out:
            e.setAttribute('output', out)
        if exported:
            e.setAttribute('exported', 'true')
        atts = None
        if out and "test" in out:
            atts = doc.createElement('attributes')
            testAtt = doc.createElement('attribute')
            testAtt.setAttribute('name', 'test')
            testAtt.setAttribute('value', 'true')
            atts.appendChild(testAtt)
        if "apt_generated" in path or "modules/jgit" in path:
            if not atts:
                atts = doc.createElement('attributes')
            ignoreOptionalProblems = doc.createElement('attribute')
            ignoreOptionalProblems.setAttribute('name', 'ignore_optional_problems')
            ignoreOptionalProblems.setAttribute('value', 'true')
            atts.appendChild(ignoreOptionalProblems)
            optional = doc.createElement('attribute')
            optional.setAttribute('name', 'optional')
            optional.setAttribute('value', 'true')
            atts.appendChild(optional)
        if atts:
            e.appendChild(atts)
        doc.documentElement.appendChild(e)

    doc = make_classpath()
    src = set()
    lib = set()
    proto = set()
    plugins = set()

    # Classpath entries are absolute for cross-cell support
    java_library = re.compile('bazel-out/.*?-fastbuild/bin/(.*)/[^/]+[.]jar$')
    proto_library = re.compile('bazel-out/.*?-fastbuild/bin/(.*)proto/(.*)_proto-speed[.]jar$')
    srcs = re.compile('(.*/external/[^/]+)/jar/(.*)[.]jar')
    for p in _query_classpath(MAIN):
        if p.endswith('-src.jar'):
            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('libgerrit-prolog-common.jar') or \
               p.endswith('external/com_google_protobuf/java/core/libcore.jar') or \
               p.endswith('external/com_google_protobuf/java/core/liblite.jar') or \
               p.endswith('lucene-core-and-backward-codecs-merged_deploy.jar'):
                lib.add(p)
            if proto_library.match(p) :
                proto.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 = os.path.join(ext, p)
            lib.add(p)

    classpathentry('src', 'java')
    classpathentry('src', 'javatests', out='eclipse-out/test')
    classpathentry('src', 'resources')
    import_jgit_sources()
    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 = os.path.join(s, 'java')
        if os.path.exists(p):
            classpathentry('src', p, out=out + '/main')
            p = os.path.join(s, 'javatests')
            if os.path.exists(p):
                classpathentry('src', p, out=out + '/test')
            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 = os.path.join(s, 'src', env, srctype)
                if os.path.exists(p):
                    classpathentry('src', p, out=o)

    for libs in [lib]:
        for j in sorted(libs):
            s = None
            m = srcs.match(j)
            if m:
                prefix = m.group(1)
                suffix = m.group(2)
                p = os.path.join(prefix, "jar", "%s-src.jar" % suffix)
                if os.path.exists(p):
                    s = p
            if args.plugins:
                classpathentry('lib', j, s, exported=True)
            else:
                classpathentry('lib', j, s)

    for p in sorted(proto):
        s = p.replace('/proto/lib', '/proto/')
        s = s.replace('/proto/testing/lib', '/proto/testing/')
        s = s.replace('.jar', '-src.jar')
        classpathentry('lib', p, s)

    classpathentry('con', JRE(custom_java) if custom_java else JRE())
    classpathentry('output', 'eclipse-out/classes')
    classpathentry('src', '.apt_generated')
    classpathentry('src', '.apt_generated_tests', out="eclipse-out/test")

    p = os.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 = os.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 = xml.dom.minidom.getDOMImplementation().createDocument(
        None, 'factorypath', None)
    for jar in _query_classpath(AUTO):
        e = doc.createElement('factorypathentry')
        e.setAttribute('kind', 'EXTJAR')
        e.setAttribute('id', os.path.join(ext, jar))
        e.setAttribute('enabled', 'true')
        e.setAttribute('runInBatchMode', 'false')
        doc.documentElement.appendChild(e)

    p = os.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().decode("utf-8")
    gen_project(args.project_name)
    gen_classpath(ext_location)
    gen_factorypath(ext_location)
    gen_bazel_path(ext_location)

    try:
        subprocess.check_call(_build_bazel_cmd('build', MAIN))
    except subprocess.CalledProcessError:
        exit(1)
except KeyboardInterrupt:
    print('Interrupted by user', file=sys.stderr)
    exit(1)
