#!/usr/bin/env python3

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

'Update CBMC starter kit.'

from pathlib import Path
import logging
import platform
import re
import shutil

from cbmc_starter_kit import arguments
from cbmc_starter_kit import util
from cbmc_starter_kit import version

################################################################

def parse_arguments():
    desc = 'Update CBMC starter kit in a CBMC proof repository.'
    options = [
        {'flag': '--cbmc-root',
         'type': Path,
         'metavar': 'CBMC',
         'help': 'Root of CBMC proof infrastructure (default: ".")'},
        {'flag': '--no-migrate',
         'action': 'store_true',
         'help': """
          Do not remove symlinks under CBMC.
          Normally remove symlinks under CBMC to files under STARTER_KIT."""},
        {'flag': '--no-test-removal',
         'action': 'store_true',
         'help': """
          Do not remove negative tests in CBMC/negative_tests.
          Normally remove the directory CBMC/negative_tests since most projects don't
          use these tests."""},
        {'flag': '--no-update',
         'action': 'store_true',
         'help': """
          Do not update Makefile.common and run-cbmc-proofs.py under CBMC/proofs.
          Normally update these files with the versions in the starter kit package."""}
    ]
    args = arguments.create_parser(
        options=options,
        description=desc).parse_args()
    arguments.configure_logging(args)
    return args

################################################################

def validate_cbmc_root(args):
    args.cbmc_root = args.cbmc_root or Path('.')
    args.cbmc_root = args.cbmc_root.resolve()
    if not args.cbmc_root.is_dir():
        raise UserWarning(f'CBMC root is not a directory: {args.cbmc_root}')
    if not (args.cbmc_root/util.PROOF_DIR).is_dir():
        raise UserWarning(f'CBMC root is missing a {util.PROOF_DIR} subdirectory: {args.cbmc_root}')
    logging.debug('CBMC root: %s', args.cbmc_root)
    return args

################################################################

def update_litani_makefile_variable(cbmc_root, path):
    """Update LITANI to LITANI?=litani in the makefile at the named path"""

    is_litani_line = lambda line: bool(re.match(r'^\s*LITANI\s*\??=', line))

    with open(cbmc_root/path, encoding='utf-8') as makefile:
        lines = makefile.read().splitlines()
    if not any(is_litani_line(line) for line in lines):
        logging.debug("Not updating LITANI in makefile: %s", path)
        return

    logging.info("Updating LITANI in makefile: %s", path)
    with open(cbmc_root/path, 'w', encoding='utf-8') as makefile:
        for line in lines:
            if is_litani_line(line):
                line = 'LITANI ?= litani'
            print(line, file=makefile)
    return

################################################################

def remove_negative_tests(cbmc_root):
    logging.debug('Removing CBMC starter kit negative tests')
    negative_tests = cbmc_root / util.NEGATIVE_TESTS
    if negative_tests.is_dir():
        logging.info('Removing: %s', negative_tests)
        shutil.rmtree(negative_tests)

def update(cbmc_root, quiet=False):
    logging.debug('Updating CBMC starter kit')
    for path in [util.COMMON_MAKEFILE, util.RUN_SCRIPT, util.LIB_MODULE]:
        src = util.package_repository_template_root() / util.PROOF_DIR / path
        dst = cbmc_root / util.PROOF_DIR / path
        (logging.debug if quiet else logging.info)('Copying: %s -> %s', src, dst)
        assert src.exists()
        if src.is_dir():
            shutil.copytree(src, dst, dirs_exist_ok=True)
        else:
            version.copy_with_version(src, dst)

def check_for_litani(cbmc_root):
    if shutil.which('litani'):
        for fyle in (util.TEMPLATE_DEFINES, util.PROJECT_DEFINES):
            update_litani_makefile_variable(cbmc_root, Path(util.PROOF_DIR) / fyle)
        return
    logging.debug("Found litani command is not installed")
    system = platform.system()
    if system == "Darwin":
        logging.warning("Consider installing the litani command available via "
                        "'brew install litani'")
    if system == "Linux":
        logging.warning("Consider installing the litani command available via "
                        "'apt install litani*.deb'")
        logging.warning("Download the litani Debian package from "
                        "https://github.com/awslabs/aws-build-accumulator/releases/latest")
    return

################################################################

def main():
    """Update files, that were previously added, during installation of the
    CBMC starter kit package."""

    args = parse_arguments()
    logging.debug('args: %s', args)

    args = validate_cbmc_root(args)

    if not args.no_test_removal:
        remove_negative_tests(args.cbmc_root)
    if not args.no_update:
        update(args.cbmc_root)
    check_for_litani(args.cbmc_root)

################################################################

if __name__ == '__main__':
    main()
