# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this,
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import platform as platform_mod
import sys

# Base url for pulling the rustup installer.
RUSTUP_URL_BASE = "https://static.rust-lang.org/rustup"

# Pull this to get the lastest stable version number.
RUSTUP_MANIFEST = RUSTUP_URL_BASE + "/release-stable.toml"

# We bake in a known version number so we can verify a checksum.
RUSTUP_VERSION = "1.23.1"

# SHA-256 checksums of the installers, per platform.
RUSTUP_HASHES = {
    "x86_64-unknown-freebsd": "3fb56018ec6009c5a3e345f07d7ea2fbc67d4c6768e528c6d990c7ebe2388d09",
    "aarch64-apple-darwin": "6d56735284181b2eb804ed7f57f76cf5ff924251e8ab69d9b5822c3be1ca1dc7",
    "x86_64-apple-darwin": "39101feb178a7e3e4443b09b36338e794a9e00385e5f44a2f7789aefb91354a9",
    "x86_64-unknown-linux-gnu": "ed7773edaf1d289656bdec2aacad12413b38ad0193fff54b2231f5140a4b07c5",
    "aarch64-unknown-linux-gnu": "f80a0a792b3ab905ab4919474daf4d3f60e574fc6987e69bfba2fd877241a8de",
    "x86_64-pc-windows-msvc": "a586cf9de3e4aa791fd5796b6a5f99ca05591ccef8bb94e53af5b69f0261fb03",
    "x86_64-unknown-netbsd": "8b29918e765f2cec3b81a911652b164471c42f8f31241f7401bb89582d6a3ed5",
}

NO_PLATFORM = """
Sorry, we have no installer configured for your platform.

Please try installing rust for your system from https://rustup.rs/
or from https://rust-lang.org/ or from your package manager.
"""


def rustup_url(host, version=RUSTUP_VERSION):
    """Download url for a particular version of the installer."""
    return "%(base)s/archive/%(version)s/%(host)s/rustup-init%(ext)s" % {
        "base": RUSTUP_URL_BASE,
        "version": version,
        "host": host,
        "ext": exe_suffix(host),
    }


def rustup_hash(host):
    """Look up the checksum for the given installer."""
    return RUSTUP_HASHES.get(host, None)


def platform():
    """Determine the appropriate rust platform string for the current host"""
    if sys.platform.startswith("darwin"):
        if platform_mod.machine() == "arm64":
            return "aarch64-apple-darwin"
        return "x86_64-apple-darwin"
    elif sys.platform.startswith(("win32", "msys")):
        # Bravely assume we'll be building 64-bit Firefox.
        return "x86_64-pc-windows-msvc"
    elif sys.platform.startswith("linux"):
        if platform_mod.machine() == "aarch64":
            return "aarch64-unknown-linux-gnu"
        return "x86_64-unknown-linux-gnu"
    elif sys.platform.startswith("freebsd"):
        return "x86_64-unknown-freebsd"
    elif sys.platform.startswith("netbsd"):
        return "x86_64-unknown-netbsd"

    return None


def exe_suffix(host=None):
    if not host:
        host = platform()
    if "windows" in host:
        return ".exe"
    return ""


USAGE = """
python rust.py [--update]

Pass the --update option print info for the latest release of rustup-init.

When invoked without the --update option, it queries the latest version
and verifies the current stored checksums against the distribution server,
but doesn't update the version installed by `mach bootstrap`.
"""


def unquote(s):
    """Strip outer quotation marks from a string."""
    return s.strip("'").strip('"')


def rustup_latest_version():
    """Query the latest version of the rustup installer."""
    import requests

    r = requests.get(RUSTUP_MANIFEST)
    # The manifest is toml, but we might not have the toml4 python module
    # available, so use ad-hoc parsing to obtain the current release version.
    #
    # The manifest looks like:
    #
    # schema-version = '1'
    # version = '0.6.5'
    #
    for line in r.iter_lines():
        line = line.decode("utf-8")
        key, value = map(str.strip, line.split("=", 2))
        if key == "schema-version":
            schema = int(unquote(value))
            if schema != 1:
                print("ERROR: Unknown manifest schema %s" % value)
                sys.exit(1)
        elif key == "version":
            return unquote(value)
    return None


def http_download_and_hash(url):
    import hashlib

    import requests

    h = hashlib.sha256()
    r = requests.get(url, stream=True)
    for data in r.iter_content(4096):
        h.update(data)
    return h.hexdigest()


def make_checksums(version, validate=False):
    hashes = []
    for platform in RUSTUP_HASHES.keys():
        if validate:
            print("Checking %s... " % platform, end="", flush=True)
        else:
            print("Fetching %s... " % platform, end="", flush=True)
        checksum = http_download_and_hash(rustup_url(platform, version))
        if validate and checksum != rustup_hash(platform):
            print(
                "mismatch:\n  script: %s\n  server: %s"
                % (RUSTUP_HASHES[platform], checksum)
            )
        else:
            print("OK")
        hashes.append((platform, checksum))
    return hashes


if __name__ == "__main__":
    """Allow invoking the module as a utility to update checksums."""

    update = False
    if len(sys.argv) > 1:
        if sys.argv[1] == "--update":
            update = True
        else:
            print(USAGE)
            sys.exit(1)

    print("Checking latest installer version... ", end="", flush=True)
    version = rustup_latest_version()
    if not version:
        print("ERROR: Could not query current rustup installer version.")
        sys.exit(1)
    print(version)

    if version == RUSTUP_VERSION:
        print("We're up to date. Validating checksums.")
        make_checksums(version, validate=True)
        sys.exit()

    if not update:
        print("Out of date. We use %s. Validating checksums." % RUSTUP_VERSION)
        make_checksums(RUSTUP_VERSION, validate=True)
        sys.exit()

    print("Out of date. We use %s. Calculating checksums." % RUSTUP_VERSION)
    hashes = make_checksums(version)
    print("")
    print("RUSTUP_VERSION = '%s'" % version)
    print("RUSTUP_HASHES = {")
    for item in hashes:
        print("    '%s':\n        '%s'," % item)
    print("}")
