"""
A Python script to install the extensions for CS 1110

Previously we did this with custom installers, but that is no longer necessary
and using a Python script guarantees this is cross-platform.

Author: Walker M. White (wmw2)
Date: August 14, 2025
"""
import os
import sys
import sysconfig
import subprocess


# The packages that we need for the semester
PACKAGES = [
    'requests',
    'python-dateutil',
    'numpy',
    'scipy',
    'matplotlib',
    'pillow',
    'pyx',
    'introcs',
    'kivy[base,sdl2]'
]


def system_prefix():
    """
    Returns the installation directory for pip

    This function prevents us from having to run this script as root.
    """
    scheme = sysconfig.get_preferred_scheme("prefix")
    paths = sysconfig.get_paths(
        scheme=scheme,
        vars={"base": sys.base_prefix, "platbase": sys.base_prefix},
    )
    return paths["data"]


def configure_pip():
    """
    Returns the command prefix for executing pip

    Per the pip documentation, we have to call pip from the command line using
    subprocess. But that means we need to add a lot of extra flags beyond the
    package name. This function gathers these together as a list to send to
    subprocess.run().
    """
    prefix = system_prefix()

    # Use the *system* interpreter when inside a venv (Python 3.8+)
    py = getattr(sys, "_base_executable", sys.executable)

    # Debian/Ubuntu with PEP 668 may require this flag for global installs
    need_break = any(
        os.path.exists(os.path.join(os.path.dirname(p), "EXTERNALLY-MANAGED"))
        for p in sysconfig.get_paths(scheme=sysconfig.get_preferred_scheme("prefix")).values()
    )

    cmd = [py, "-m", "pip", "install"]
    if not sys.platform.startswith("win"):
        cmd.extend([ "--prefix", prefix])
    if need_break and sys.prefix == sys.base_prefix:  # only when not in a venv
        cmd.append("--break-system-packages")

    return cmd


def parse_output(out,installed):
    """
    Prints the results of output in a concise format.

    This takes the output of pip and prints out the only two things we care
    about: whether a package was successfully installed or whether it was
    already installed. Note that this function assumes pip completed with
    return code 0 (successfully).

    The set installed is to keep us from repeating ourselves. Once a package
    is noted as installed, we never reference it again.

    Parameter out: The output of a successful pip installation
    Precondition: out is the stdout of a subprocess routine

    Parameter installed: The set of install packages
    Precondition: installed is a set of strings
    """
    lines = out.split('\n')
    oldinstall = 'Requirement already satisfied:'
    newinstall = 'Collecting'
    for line in lines:
        if line.startswith(oldinstall):
            pos0 = line.find(' ',len(oldinstall))
            pos1 = line.find(' ',pos0+1)
            pckg = line[pos0+1:pos1].strip()
            if '>' in pckg:
                pos0 = pckg.find('>')
                pckg = pckg[:pos0]
            if '<' in pckg:
                pos0 = pckg.find('<')
                pckg = pckg[:pos0]
            if not pckg in installed:
                print(f'{pckg} is already installed.')
            installed.add(pckg)
        elif line.startswith(newinstall):
            pos0 = line.find(' ',len(newinstall))
            pos1 = line.find(' ',pos0+1)
            pckg = line[pos0+1:pos1].strip()
            print(f'{pckg} successfully installed')
            installed.add(pckg)


def install(packages):
    """
    Installs a list of packages

    Parameter packages: The packages to install
    Precondition: packages is a list of strings
    """
    command = configure_pip()

    installed = set()
    for pckg in packages:
        try:
            result = subprocess.run(command+[pckg], check=True, capture_output=True, text=True)
            if result.returncode == 0:
                parse_output(result.stdout,installed)
            else:
                print(result.stdout)
        except subprocess.CalledProcessError as e:
            print(f"pip failed to install '{pckg}' with exit code {e.returncode}")


if __name__ == '__main__':
    install(PACKAGES)
