# Copyright 2023 The Pigweed Authors
#
# 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
#
#     https://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.
"""Download all Python packages required for a pw_python_venv."""

import argparse
import logging
from pathlib import Path
import shlex
import subprocess
import sys


_LOG = logging.getLogger('pw_build.generate_python_wheel_cache')


def _parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        '--pip-download-log',
        type=Path,
        required=True,
        help='Path to the root gn build dir.',
    )
    parser.add_argument(
        '--wheel-dir',
        type=Path,
        required=True,
        help='Path to save wheel files.',
    )
    parser.add_argument(
        '--download-all-platforms',
        action='store_true',
        help='Download Python precompiled wheels for all platforms.',
    )
    parser.add_argument(
        '-r',
        '--requirement',
        type=Path,
        nargs='+',
        required=True,
        help='Requirements files',
    )
    return parser.parse_args()


def main(
    pip_download_log: Path,
    wheel_dir: Path,
    requirement: list[Path],
    download_all_platforms: bool = False,
) -> int:
    """Download all Python packages required for a pw_python_venv."""

    # Delete existing wheels from the out dir, there may be stale versions.
    # shutil.rmtree(wheel_dir, ignore_errors=True)
    wheel_dir.mkdir(parents=True, exist_ok=True)

    pip_download_args = [
        sys.executable,
        "-m",
        "pip",
        "--log",
        str(pip_download_log),
        "download",
        "--dest",
        str(wheel_dir),
    ]
    for req in requirement:
        pip_download_args.extend(["--requirement", str(req)])

    if not download_all_platforms:
        # Download for the current platform only.
        quoted_pip_download_args = ' '.join(
            shlex.quote(arg) for arg in pip_download_args
        )
        _LOG.info('Run ==> %s', quoted_pip_download_args)
        # Download packages
        subprocess.run(pip_download_args, check=True)
        return 0

    # DOCSTAG: [wheel-platform-args]
    # These platform args are derived from the cffi pypi package:
    #   https://pypi.org/project/cffi/#files
    # See also these pages on Python wheel filename format:
    #   https://peps.python.org/pep-0491/#file-name-convention
    # and Platform compatibility tags:
    #   https://packaging.python.org/en/latest/specifications/
    #      platform-compatibility-tags/
    platform_args = [
        '--platform=any',
        '--platform=macosx_10_9_universal2',
        '--platform=macosx_10_9_x86_64',
        '--platform=macosx_11_0_arm64',
        '--platform=manylinux2010_x86_64',
        '--platform=manylinux2014_aarch64',
        '--platform=manylinux2014_x86_64',
        '--platform=manylinux_2_17_aarch64',
        '--platform=manylinux_2_17_x86_64',
        '--platform=musllinux_1_1_x86_64',
        '--platform=win_amd64',
        # Note: These 32bit platforms are omitted
        # '--platform=manylinux2010_i686',
        # '--platform=manylinux2014_i686',
        # '--platform=manylinux_2_12_i686'
        # '--platform=musllinux_1_1_i686',
        # '--platform=win32',
    ]

    # Pigweed supports Python 3.8 and up.
    python_version_args = [
        [
            '--implementation=py3',
            '--abi=none',
        ],
        [
            '--implementation=cp',
            '--python-version=3.8',
            '--abi=cp38',
        ],
        [
            '--implementation=cp',
            '--python-version=3.9',
            '--abi=cp39',
        ],
        [
            '--implementation=cp',
            '--python-version=3.10',
            '--abi=cp310',
        ],
        [
            '--implementation=cp',
            '--python-version=3.11',
            '--abi=cp311',
        ],
    ]
    # DOCSTAG: [wheel-platform-args]

    # --no-deps is required when downloading binary packages for different
    # platforms other than the current one. The requirements.txt files already
    # has the fully expanded deps list using pip-compile so this is not a
    # problem.
    pip_download_args.append('--no-deps')

    # Run pip download once for each Python version. Multiple platform args can
    # be added to the same command.
    for py_version_args in python_version_args:
        for platform_arg in platform_args:
            final_pip_download_args = list(pip_download_args)
            final_pip_download_args.append(platform_arg)
            final_pip_download_args.extend(py_version_args)
            quoted_pip_download_args = ' '.join(
                shlex.quote(arg) for arg in final_pip_download_args
            )
            _LOG.info('')
            _LOG.info('Fetching packages for:')
            _LOG.info(
                'Python %s and Platforms: %s', py_version_args, platform_args
            )
            _LOG.info('Run ==> %s', quoted_pip_download_args)

            # Download packages
            subprocess.run(final_pip_download_args, check=True)

    return 0


if __name__ == '__main__':
    logging.basicConfig(format='%(message)s', level=logging.DEBUG)
    sys.exit(main(**vars(_parse_args())))
