#!/usr/bin/env python3
#
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Creates an Android .aar file."""

import argparse
import os
import posixpath
import shutil
import sys
import tempfile
import zipfile

import filter_zip
from util import build_utils
import action_helpers  # build_utils adds //build to sys.path.
import zip_helpers


_ANDROID_BUILD_DIR = os.path.dirname(os.path.dirname(__file__))


def _MergeRTxt(r_paths, include_globs, exclude_globs):
    """Merging the given R.txt files and returns them as a string."""
    all_lines = set()
    for r_path in r_paths:
        if (
            include_globs
            and not build_utils.MatchesGlob(r_path, include_globs)
            or exclude_globs
            and build_utils.MatchesGlob(r_path, exclude_globs)
        ):
            continue
        with open(r_path, encoding='utf-8') as f:
            all_lines.update(f.readlines())
    return ''.join(sorted(all_lines))


def _MergeProguardConfigs(proguard_configs):
    """Merging the given proguard config files and returns them as a string."""
    ret = []
    for config in proguard_configs:
        ret.append('# FROM: {}'.format(config))
        with open(config, encoding='utf-8') as f:
            ret.append(f.read())
    return '\n'.join(ret)


def _AddResources(aar_zip, resource_zips, include_globs, exclude_globs):
    """Adds all resource zips to the given aar_zip.

    Ensures all res/values/* files have unique names by prefixing them.
    """
    for i, path in enumerate(resource_zips):
        if (
            include_globs
            and not build_utils.MatchesGlob(path, include_globs)
            or exclude_globs
            and build_utils.MatchesGlob(path, exclude_globs)
        ):
            continue
        with zipfile.ZipFile(path) as res_zip:
            for info in res_zip.infolist():
                data = res_zip.read(info)
                dirname, basename = posixpath.split(info.filename)
                if 'values' in dirname:
                    root, ext = os.path.splitext(basename)
                    basename = '{}_{}{}'.format(root, i, ext)
                    info.filename = posixpath.join(dirname, basename)
                info.filename = posixpath.join('res', info.filename)
                aar_zip.writestr(info, data)


def main(args):
    args = build_utils.ExpandFileArgs(args)
    parser = argparse.ArgumentParser()
    action_helpers.add_depfile_arg(parser)
    parser.add_argument('--output', required=True, help='Path to output aar.')
    parser.add_argument('--jars', required=True, help='GN list of jar inputs.')
    parser.add_argument(
        '--dependencies-res-zips',
        required=True,
        action='append',
        help='GN list of resource zips',
    )
    parser.add_argument(
        '--r-text-files', required=True, help='GN list of R.txt files to merge'
    )
    parser.add_argument(
        '--proguard-configs',
        required=True,
        help='GN list of ProGuard flag files to merge.',
    )
    parser.add_argument(
        '--android-manifest',
        help='Path to AndroidManifest.xml to include.',
        default=os.path.join(_ANDROID_BUILD_DIR, 'AndroidManifest.xml'),
    )
    parser.add_argument(
        '--native-libraries',
        default='',
        help='GN list of native libraries. If non-empty then '
        'ABI must be specified.',
    )
    parser.add_argument(
        '--abi', help='ABI (e.g. armeabi-v7a) for native libraries.'
    )
    parser.add_argument(
        '--jar-excluded-globs',
        help='GN-list of globs for paths to exclude in jar.',
    )
    parser.add_argument(
        '--jar-included-globs',
        help='GN-list of globs for paths to include in jar.',
    )
    parser.add_argument(
        '--resource-included-globs',
        help='GN-list of globs for paths to include in R.txt and resources zips.',
    )
    parser.add_argument(
        '--resource-excluded-globs',
        help='GN-list of globs for paths to exclude in R.txt and resources zips.',
    )

    options = parser.parse_args(args)

    if options.native_libraries and not options.abi:
        parser.error('You must provide --abi if you have native libs')

    options.jars = action_helpers.parse_gn_list(options.jars)
    options.dependencies_res_zips = action_helpers.parse_gn_list(
        options.dependencies_res_zips
    )
    options.r_text_files = action_helpers.parse_gn_list(options.r_text_files)
    options.proguard_configs = action_helpers.parse_gn_list(
        options.proguard_configs
    )
    options.native_libraries = action_helpers.parse_gn_list(
        options.native_libraries
    )
    options.jar_excluded_globs = action_helpers.parse_gn_list(
        options.jar_excluded_globs
    )
    options.jar_included_globs = action_helpers.parse_gn_list(
        options.jar_included_globs
    )
    options.resource_included_globs = action_helpers.parse_gn_list(
        options.resource_included_globs
    )
    options.resource_excluded_globs = action_helpers.parse_gn_list(
        options.resource_excluded_globs
    )

    with tempfile.NamedTemporaryFile(delete=False) as staging_file:
        try:
            with zipfile.ZipFile(staging_file.name, 'w') as z:
                zip_helpers.add_to_zip_hermetic(
                    z, 'AndroidManifest.xml', src_path=options.android_manifest
                )

                path_transform = filter_zip.CreatePathTransform(
                    options.jar_excluded_globs, options.jar_included_globs
                )
                with tempfile.NamedTemporaryFile() as jar_file:
                    zip_helpers.merge_zips(
                        jar_file.name,
                        options.jars,
                        path_transform=path_transform,
                    )
                    zip_helpers.add_to_zip_hermetic(
                        z, 'classes.jar', src_path=jar_file.name
                    )
                zip_helpers.add_to_zip_hermetic(
                    z,
                    'R.txt',
                    data=_MergeRTxt(
                        options.r_text_files,
                        options.resource_included_globs,
                        options.resource_excluded_globs,
                    ),
                )
                zip_helpers.add_to_zip_hermetic(z, 'public.txt', data='')

                if options.proguard_configs:
                    zip_helpers.add_to_zip_hermetic(
                        z,
                        'proguard.txt',
                        data=_MergeProguardConfigs(options.proguard_configs),
                    )

                _AddResources(
                    z,
                    options.dependencies_res_zips,
                    options.resource_included_globs,
                    options.resource_excluded_globs,
                )

                for native_library in options.native_libraries:
                    libname = os.path.basename(native_library)
                    zip_helpers.add_to_zip_hermetic(
                        z,
                        os.path.join('jni', options.abi, libname),
                        src_path=native_library,
                    )
        except:
            os.unlink(staging_file.name)
            raise
        shutil.move(staging_file.name, options.output)

    if options.depfile:
        all_inputs = (
            options.jars
            + options.dependencies_res_zips
            + options.r_text_files
            + options.proguard_configs
        )
        action_helpers.write_depfile(
            options.depfile, options.output, all_inputs
        )


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