#!/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
# 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')
opts.add_option('-b', '--batch', action='store_true',
                dest='batch', help='Bazel batch option')
opts.add_option('-j', '--java', action='store',
                dest='java', help='Post Java 8 support (9)')
opts.add_option('-e', '--edge_java', action='store',
                dest='edge_java', help='Post Java 9 support (10|11|...)')
args, _ = opts.parse_args()

batch_option = '--batch' if args.batch else None
custom_java = args.java
edge_java = args.edge_java

def _build_bazel_cmd(*args):
    build = False
    cmd = ['bazel']
    if batch_option:
        cmd.append('--batch')
    for arg in args:
        if arg == "build":
            build = True
        cmd.append(arg)
    if 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 check_output(_build_bazel_cmd('info', 'output_base')).strip()


def gen_bazel_path(ext_location):
    bazel = check_output(['which', 'bazel']).strip().decode('UTF-8')
    with open(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" % environ["PATH"])


def _query_classpath(target):
    deps = []
    t = cp_targets[target]
    try:
        check_call(_build_bazel_cmd('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">
    <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 = 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')
        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)
            e.appendChild(atts)
        doc.documentElement.appendChild(e)

    doc = make_classpath()
    src = set()
    lib = set()
    proto = 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('libgerrit-prolog-common.jar'):
                lib.add(p)
            # JGit dependency from external repository
            if 'gerrit-' not in p and 'jgit' in p:
                lib.add(p)
            # Assume any jars in /proto/ are from java_proto_library rules
            if '/bin/proto/' in 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 = 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))

    classpathentry('src', 'java')
    classpathentry('src', 'javatests', out='eclipse-out/test')
    classpathentry('src', 'resources')
    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
                # themselves).  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 p in sorted(proto):
        s = p.replace('-fastbuild/bin/proto/lib', '-fastbuild/genfiles/proto/')
        s = s.replace('.jar', '-src.jar')
        classpathentry('lib', p, 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().decode("utf-8")
    gen_project(args.project_name)
    gen_classpath(ext_location)
    gen_factorypath(ext_location)
    gen_bazel_path(ext_location)

    # 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(_build_bazel_cmd('build', MAIN, GWT,
                                    '//java/org/eclipse/jgit:libEdit-src.jar'))
    except CalledProcessError:
        exit(1)
except KeyboardInterrupt:
    print('Interrupted by user', file=sys.stderr)
    exit(1)
