use num_traits::ToPrimitive;
use std::{env, mem, path};

use crate::builtins::{PyStr, PyStrRef, PyTypeRef};
use crate::common::hash::{PyHash, PyUHash};
use crate::frame::FrameRef;
use crate::function::{Args, FuncArgs, OptionalArg};
use crate::pyobject::{
    ItemProtocol, PyClassImpl, PyContext, PyObjectRef, PyRefExact, PyResult, PyStructSequence,
};
use crate::vm::{PySettings, VirtualMachine};
use crate::{builtins, exceptions, py_io, version};

/*
 * The magic sys module.
 */
const MAXSIZE: usize = std::isize::MAX as usize;
const MAXUNICODE: u32 = std::char::MAX as u32;

fn argv(vm: &VirtualMachine) -> PyObjectRef {
    vm.ctx.new_list(
        vm.state
            .settings
            .argv
            .iter()
            .map(|arg| vm.ctx.new_str(arg))
            .collect(),
    )
}

fn executable(ctx: &PyContext) -> PyObjectRef {
    if let Some(exec_path) = env::args().next() {
        let path = path::Path::new(&exec_path);
        if !path.exists() {
            return ctx.new_str("");
        }
        if path.is_absolute() {
            return ctx.new_str(exec_path);
        }
        if let Ok(dir) = env::current_dir() {
            if let Ok(dir) = dir.into_os_string().into_string() {
                return ctx.new_str(format!(
                    "{}/{}",
                    dir,
                    exec_path.strip_prefix("./").unwrap_or(&exec_path)
                ));
            }
        }
    }
    ctx.none()
}

fn _base_executable(ctx: &PyContext) -> PyObjectRef {
    if let Ok(var) = env::var("__PYVENV_LAUNCHER__") {
        ctx.new_str(var)
    } else {
        executable(ctx)
    }
}

#[allow(non_snake_case)] // it's the function sys._getframe -> sys__getframe
fn sys__getframe(offset: OptionalArg<usize>, vm: &VirtualMachine) -> PyResult<FrameRef> {
    let offset = offset.into_option().unwrap_or(0);
    if offset > vm.frames.borrow().len() - 1 {
        return Err(vm.new_value_error("call stack is not deep enough".to_owned()));
    }
    let idx = vm.frames.borrow().len() - offset - 1;
    let frame = &vm.frames.borrow()[idx];
    Ok(frame.clone())
}

/// sys.flags
///
/// Flags provided through command line arguments or environment vars.
#[pyclass(name = "flags", module = "sys")]
#[derive(Debug, PyStructSequence)]
struct SysFlags {
    /// -d
    debug: u8,
    /// -i
    inspect: u8,
    /// -i
    interactive: u8,
    /// -O or -OO
    optimize: u8,
    /// -B
    dont_write_bytecode: u8,
    /// -s
    no_user_site: u8,
    /// -S
    no_site: u8,
    /// -E
    ignore_environment: u8,
    /// -v
    verbose: u8,
    /// -b
    bytes_warning: u64,
    /// -q
    quiet: u8,
    /// -R
    hash_randomization: u8,
    /// -I
    isolated: u8,
    /// -X dev
    dev_mode: bool,
    /// -X utf8
    utf8_mode: u8,
}

#[pyimpl(with(PyStructSequence))]
impl SysFlags {
    fn from_settings(settings: &PySettings) -> Self {
        SysFlags {
            debug: settings.debug as u8,
            inspect: settings.inspect as u8,
            interactive: settings.interactive as u8,
            optimize: settings.optimize,
            dont_write_bytecode: settings.dont_write_bytecode as u8,
            no_user_site: settings.no_user_site as u8,
            no_site: settings.no_site as u8,
            ignore_environment: settings.ignore_environment as u8,
            verbose: settings.verbose,
            bytes_warning: settings.bytes_warning,
            quiet: settings.quiet as u8,
            hash_randomization: settings.hash_seed.is_none() as u8,
            isolated: settings.isolated as u8,
            dev_mode: settings.dev_mode,
            utf8_mode: 0,
        }
    }

    #[pyslot]
    fn tp_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
        Err(vm.new_type_error("cannot create 'sys.flags' instances".to_owned()))
    }
}

fn sys_getrefcount(obj: PyObjectRef) -> usize {
    PyObjectRef::strong_count(&obj)
}

fn sys_getsizeof(obj: PyObjectRef) -> usize {
    // TODO: implement default optional argument.
    mem::size_of_val(&obj)
}

fn sys_getfilesystemencoding(_vm: &VirtualMachine) -> String {
    // TODO: implement non-utf-8 mode.
    "utf-8".to_owned()
}

fn sys_getdefaultencoding(_vm: &VirtualMachine) -> String {
    "utf-8".to_owned()
}

#[cfg(not(windows))]
fn sys_getfilesystemencodeerrors(_vm: &VirtualMachine) -> String {
    "surrogateescape".to_owned()
}

#[cfg(windows)]
fn sys_getfilesystemencodeerrors(_vm: &VirtualMachine) -> String {
    "surrogatepass".to_owned()
}

fn sys_getprofile(vm: &VirtualMachine) -> PyObjectRef {
    vm.profile_func.borrow().clone()
}

fn sys_setprofile(profilefunc: PyObjectRef, vm: &VirtualMachine) {
    vm.profile_func.replace(profilefunc);
    update_use_tracing(vm);
}

fn sys_gettrace(vm: &VirtualMachine) -> PyObjectRef {
    vm.trace_func.borrow().clone()
}

fn sys_settrace(tracefunc: PyObjectRef, vm: &VirtualMachine) {
    vm.trace_func.replace(tracefunc);
    update_use_tracing(vm);
}

fn update_use_tracing(vm: &VirtualMachine) {
    let trace_is_none = vm.is_none(&vm.trace_func.borrow());
    let profile_is_none = vm.is_none(&vm.profile_func.borrow());
    let tracing = !(trace_is_none && profile_is_none);
    vm.use_tracing.set(tracing);
}

fn sys_getrecursionlimit(vm: &VirtualMachine) -> usize {
    vm.recursion_limit.get()
}

fn sys_setrecursionlimit(recursion_limit: i32, vm: &VirtualMachine) -> PyResult<()> {
    let recursion_limit = recursion_limit
        .to_usize()
        .filter(|&u| u >= 1)
        .ok_or_else(|| {
            vm.new_value_error("recursion limit must be greater than or equal to one".to_owned())
        })?;
    let recursion_depth = vm.frames.borrow().len();

    if recursion_limit > recursion_depth + 1 {
        vm.recursion_limit.set(recursion_limit);
        Ok(())
    } else {
        Err(vm.new_recursion_error(format!(
            "cannot set the recursion limit to {} at the recursion depth {}: the limit is too low",
            recursion_limit, recursion_depth
        )))
    }
}

fn sys_intern(s: PyRefExact<PyStr>, vm: &VirtualMachine) -> PyStrRef {
    vm.intern_string(s)
}

fn sys_exc_info(vm: &VirtualMachine) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
    match vm.current_exception() {
        Some(exception) => exceptions::split(exception, vm),
        None => (vm.ctx.none(), vm.ctx.none(), vm.ctx.none()),
    }
}

fn sys_git_info(vm: &VirtualMachine) -> PyObjectRef {
    vm.ctx.new_tuple(vec![
        vm.ctx.new_str("RustPython"),
        vm.ctx.new_str(version::get_git_identifier()),
        vm.ctx.new_str(version::get_git_revision()),
    ])
}

fn sys_exit(code: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
    let code = code.unwrap_or_none(vm);
    Err(vm.new_exception(vm.ctx.exceptions.system_exit.clone(), vec![code]))
}

fn sys_audit(_args: FuncArgs) {
    // TODO: sys.audit implementation
}

fn sys_displayhook(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
    // Save non-None values as "_"
    if vm.is_none(&obj) {
        return Ok(());
    }
    // set to none to avoid recursion while printing
    vm.set_attr(&vm.builtins, "_", vm.ctx.none())?;
    // TODO: catch encoding errors
    let repr = vm.to_repr(&obj)?.into_object();
    builtins::print(Args::new(vec![repr]), Default::default(), vm)?;
    vm.set_attr(&vm.builtins, "_", obj)?;
    Ok(())
}

#[pyclass(module = "sys", name = "getwindowsversion")]
#[derive(Default, Debug, PyStructSequence)]
#[cfg(windows)]
struct WindowsVersion {
    major: u32,
    minor: u32,
    build: u32,
    platform: u32,
    service_pack: String,
    service_pack_major: u16,
    service_pack_minor: u16,
    suite_mask: u16,
    product_type: u8,
    platform_version: (u32, u32, u32),
}
#[cfg(windows)]
#[pyimpl(with(PyStructSequence))]
impl WindowsVersion {}

#[cfg(windows)]
fn sys_getwindowsversion(vm: &VirtualMachine) -> PyResult<crate::builtins::tuple::PyTupleRef> {
    use std::ffi::OsString;
    use std::os::windows::ffi::OsStringExt;
    use winapi::um::{
        sysinfoapi::GetVersionExW,
        winnt::{LPOSVERSIONINFOEXW, LPOSVERSIONINFOW, OSVERSIONINFOEXW},
    };

    let mut version = OSVERSIONINFOEXW::default();
    version.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOEXW>() as u32;
    let result = unsafe {
        let osvi = &mut version as LPOSVERSIONINFOEXW as LPOSVERSIONINFOW;
        // SAFETY: GetVersionExW accepts a pointer of OSVERSIONINFOW, but winapi crate's type currently doesn't allow to do so.
        // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversionexw#parameters
        GetVersionExW(osvi)
    };

    if result == 0 {
        return Err(vm.new_os_error("failed to get windows version".to_owned()));
    }

    let service_pack = {
        let (last, _) = version
            .szCSDVersion
            .iter()
            .take_while(|&x| x != &0)
            .enumerate()
            .last()
            .unwrap_or((0, &0));
        let sp = OsString::from_wide(&version.szCSDVersion[..last]);
        sp.into_string()
            .map_err(|_| vm.new_os_error("service pack is not ASCII".to_owned()))?
    };
    WindowsVersion {
        major: version.dwMajorVersion,
        minor: version.dwMinorVersion,
        build: version.dwBuildNumber,
        platform: version.dwPlatformId,
        service_pack,
        service_pack_major: version.wServicePackMajor,
        service_pack_minor: version.wServicePackMinor,
        suite_mask: version.wSuiteMask,
        product_type: version.wProductType,
        platform_version: (
            version.dwMajorVersion,
            version.dwMinorVersion,
            version.dwBuildNumber,
        ), // TODO Provide accurate version, like CPython impl
    }
    .into_struct_sequence(vm)
}

pub fn get_stdin(vm: &VirtualMachine) -> PyResult {
    vm.get_attribute(vm.sys_module.clone(), "stdin")
        .map_err(|_| vm.new_runtime_error("lost sys.stdin".to_owned()))
}
pub fn get_stdout(vm: &VirtualMachine) -> PyResult {
    vm.get_attribute(vm.sys_module.clone(), "stdout")
        .map_err(|_| vm.new_runtime_error("lost sys.stdout".to_owned()))
}
pub fn get_stderr(vm: &VirtualMachine) -> PyResult {
    vm.get_attribute(vm.sys_module.clone(), "stderr")
        .map_err(|_| vm.new_runtime_error("lost sys.stderr".to_owned()))
}

/// Similar to PySys_WriteStderr in CPython.
///
/// # Usage
///
/// ```rust,ignore
/// writeln!(sysmodule::PyStderr(vm), "foo bar baz :)");
/// ```
///
/// Unlike writing to a `std::io::Write` with the `write[ln]!()` macro, there's no error condition here;
/// this is intended to be a replacement for the `eprint[ln]!()` macro, so `write!()`-ing to PyStderr just
/// returns `()`.
pub struct PyStderr<'vm>(pub &'vm VirtualMachine);

impl PyStderr<'_> {
    pub fn write_fmt(&self, args: std::fmt::Arguments<'_>) {
        use py_io::Write;

        let vm = self.0;
        if let Ok(stderr) = get_stderr(vm) {
            let mut stderr = py_io::PyWriter(stderr, vm);
            if let Ok(()) = stderr.write_fmt(args) {
                return;
            }
        }
        eprint!("{}", args)
    }
}

fn sys_excepthook(
    exc_type: PyObjectRef,
    exc_val: PyObjectRef,
    exc_tb: PyObjectRef,
    vm: &VirtualMachine,
) -> PyResult<()> {
    let exc = exceptions::normalize(exc_type, exc_val, exc_tb, vm)?;
    let stderr = get_stderr(vm)?;
    exceptions::write_exception(&mut py_io::PyWriter(stderr, vm), vm, &exc)
}

const PLATFORM: &str = {
    cfg_if::cfg_if! {
        if #[cfg(any(target_os = "linux", target_os = "android"))] {
            // Android is linux as well. see https://bugs.python.org/issue32637
            "linux"
        } else if #[cfg(target_os = "macos")] {
            "darwin"
        } else if #[cfg(windows)] {
            "win32"
        } else {
            "unknown"
        }
    }
};

const ABIFLAGS: &str = "";

// not the same as CPython (e.g. rust's x86_x64-unknown-linux-gnu is just x86_64-linux-gnu)
// but hopefully that's just an implementation detail? TODO: copy CPython's multiarch exactly,
// https://github.com/python/cpython/blob/3.8/configure.ac#L725
const MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE");

pub(crate) fn sysconfigdata_name() -> String {
    format!("_sysconfigdata_{}_{}_{}", ABIFLAGS, PLATFORM, MULTIARCH)
}

#[pyclass(module = "sys", name = "hash_info")]
#[derive(PyStructSequence)]
struct PyHashInfo {
    width: usize,
    modulus: PyUHash,
    inf: PyHash,
    nan: PyHash,
    imag: PyHash,
    algorithm: &'static str,
    hash_bits: usize,
    seed_bits: usize,
    cutoff: usize,
}
#[pyimpl(with(PyStructSequence))]
impl PyHashInfo {
    const INFO: Self = {
        use rustpython_common::hash::*;
        PyHashInfo {
            width: std::mem::size_of::<PyHash>() * 8,
            modulus: MODULUS,
            inf: INF,
            nan: NAN,
            imag: IMAG,
            algorithm: ALGO,
            hash_bits: HASH_BITS,
            seed_bits: SEED_BITS,
            cutoff: 0, // no small string optimizations
        }
    };
}

#[pyclass(module = "sys", name = "float_info")]
#[derive(PyStructSequence)]
struct PyFloatInfo {
    max: f64,
    max_exp: i32,
    max_10_exp: i32,
    min: f64,
    min_exp: i32,
    min_10_exp: i32,
    dig: u32,
    mant_dig: u32,
    epsilon: f64,
    radix: u32,
    rounds: i32,
}
#[pyimpl(with(PyStructSequence))]
impl PyFloatInfo {
    const INFO: Self = PyFloatInfo {
        max: f64::MAX,
        max_exp: f64::MAX_EXP,
        max_10_exp: f64::MAX_10_EXP,
        min: f64::MIN_POSITIVE,
        min_exp: f64::MIN_EXP,
        min_10_exp: f64::MIN_10_EXP,
        dig: f64::DIGITS,
        mant_dig: f64::MANTISSA_DIGITS,
        epsilon: f64::EPSILON,
        radix: f64::RADIX,
        rounds: 1, // FE_TONEAREST
    };
}

#[pyclass(module = "sys", name = "int_info")]
#[derive(PyStructSequence)]
struct PyIntInfo {
    bits_per_digit: usize,
    sizeof_digit: usize,
}
#[pyimpl(with(PyStructSequence))]
impl PyIntInfo {
    const INFO: Self = PyIntInfo {
        bits_per_digit: 30, //?
        sizeof_digit: std::mem::size_of::<u32>(),
    };
}

pub(crate) fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectRef) {
    let ctx = &vm.ctx;

    let _flags_type = SysFlags::make_class(ctx);
    let flags = SysFlags::from_settings(&vm.state.settings)
        .into_struct_sequence(vm)
        .unwrap();

    let _version_info_type = version::VersionInfo::make_class(ctx);
    let version_info = version::VersionInfo::VERSION
        .into_struct_sequence(vm)
        .unwrap();

    let _hash_info_type = PyHashInfo::make_class(ctx);
    let hash_info = PyHashInfo::INFO.into_struct_sequence(vm).unwrap();

    let _float_info_type = PyFloatInfo::make_class(ctx);
    let float_info = PyFloatInfo::INFO.into_struct_sequence(vm).unwrap();

    let _int_info_type = PyIntInfo::make_class(ctx);
    let int_info = PyIntInfo::INFO.into_struct_sequence(vm).unwrap();

    // TODO Add crate version to this namespace
    let implementation = py_namespace!(vm, {
        "name" => ctx.new_str("rustpython"),
        "cache_tag" => ctx.new_str("rustpython-01"),
        "_multiarch" => ctx.new_str(MULTIARCH.to_owned()),
        "version" => version_info.clone(),
        "hexversion" => ctx.new_int(version::VERSION_HEX),
    });

    let path = ctx.new_list(
        vm.state
            .settings
            .path_list
            .iter()
            .map(|path| ctx.new_str(path.clone()))
            .collect(),
    );

    let framework = "".to_owned();

    let xopts = ctx.new_dict();
    for (key, value) in &vm.state.settings.xopts {
        let value = value
            .as_ref()
            .map_or_else(|| ctx.new_bool(true), |s| ctx.new_str(s.clone()));
        xopts.set_item(&**key, value, vm).unwrap();
    }

    let warnopts = ctx.new_list(
        vm.state
            .settings
            .warnopts
            .iter()
            .map(|s| ctx.new_str(s.clone()))
            .collect(),
    );

    // https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian
    let bytorder = if cfg!(target_endian = "little") {
        "little".to_owned()
    } else if cfg!(target_endian = "big") {
        "big".to_owned()
    } else {
        "unknown".to_owned()
    };

    let copyright = "Copyright (c) 2019 RustPython Team";

    let sys_doc = "This module provides access to some objects used or maintained by the
interpreter and to functions that interact strongly with the interpreter.

Dynamic objects:

argv -- command line arguments; argv[0] is the script pathname if known
path -- module search path; path[0] is the script directory, else ''
modules -- dictionary of loaded modules

displayhook -- called to show results in an interactive session
excepthook -- called to handle any uncaught exception other than SystemExit
  To customize printing in an interactive session or to install a custom
  top-level exception handler, assign other functions to replace these.

stdin -- standard input file object; used by input()
stdout -- standard output file object; used by print()
stderr -- standard error object; used for error messages
  By assigning other file objects (or objects that behave like files)
  to these, it is possible to redirect all of the interpreter's I/O.

last_type -- type of last uncaught exception
last_value -- value of last uncaught exception
last_traceback -- traceback of last uncaught exception
  These three are only available in an interactive session after a
  traceback has been printed.

Static objects:

builtin_module_names -- tuple of module names built into this interpreter
copyright -- copyright notice pertaining to this interpreter
exec_prefix -- prefix used to find the machine-specific Python library
executable -- absolute path of the executable binary of the Python interpreter
float_info -- a struct sequence with information about the float implementation.
float_repr_style -- string indicating the style of repr() output for floats
hash_info -- a struct sequence with information about the hash algorithm.
hexversion -- version information encoded as a single integer
implementation -- Python implementation information.
int_info -- a struct sequence with information about the int implementation.
maxsize -- the largest supported length of containers.
maxunicode -- the value of the largest Unicode code point
platform -- platform identifier
prefix -- prefix used to find the Python library
thread_info -- a struct sequence with information about the thread implementation.
version -- the version of this interpreter as a string
version_info -- version information as a named tuple
_base_executable -- __PYVENV_LAUNCHER__ enviroment variable if defined, else sys.executable.

__stdin__ -- the original stdin; don't touch!
__stdout__ -- the original stdout; don't touch!
__stderr__ -- the original stderr; don't touch!
__displayhook__ -- the original displayhook; don't touch!
__excepthook__ -- the original excepthook; don't touch!

Functions:

displayhook() -- print an object to the screen, and save it in builtins._
excepthook() -- print an exception and its traceback to sys.stderr
exc_info() -- return thread-safe information about the current exception
exit() -- exit the interpreter by raising SystemExit
getdlopenflags() -- returns flags to be used for dlopen() calls
getprofile() -- get the global profiling function
getrefcount() -- return the reference count for an object (plus one :-)
getrecursionlimit() -- return the max recursion depth for the interpreter
getsizeof() -- return the size of an object in bytes
gettrace() -- get the global debug tracing function
setcheckinterval() -- control how often the interpreter checks for events
setdlopenflags() -- set the flags to be used for dlopen() calls
setprofile() -- set the global profiling function
setrecursionlimit() -- set the max recursion depth for the interpreter
settrace() -- set the global debug tracing function
";
    let mut module_names: Vec<String> = vm.state.stdlib_inits.keys().cloned().collect();
    module_names.push("sys".to_owned());
    module_names.push("builtins".to_owned());
    module_names.sort();
    let builtin_module_names =
        ctx.new_tuple(module_names.into_iter().map(|n| ctx.new_str(n)).collect());
    let modules = ctx.new_dict();

    let prefix = option_env!("RUSTPYTHON_PREFIX").unwrap_or("/usr/local");
    let base_prefix = option_env!("RUSTPYTHON_BASEPREFIX").unwrap_or(prefix);
    let exec_prefix = option_env!("RUSTPYTHON_EXECPREFIX").unwrap_or(prefix);
    let base_exec_prefix = option_env!("RUSTPYTHON_BASEEXECPREFIX").unwrap_or(exec_prefix);

    extend_module!(vm, module, {
      "__name__" => ctx.new_str(String::from("sys")),
      "argv" => argv(vm),
      "builtin_module_names" => builtin_module_names,
      "byteorder" => ctx.new_str(bytorder),
      "copyright" => ctx.new_str(copyright),
      "_base_executable" => _base_executable(ctx),
      "executable" => executable(ctx),
      "flags" => flags,
      "getrefcount" => named_function!(ctx, sys, getrefcount),
      "getrecursionlimit" => named_function!(ctx, sys, getrecursionlimit),
      "getsizeof" => named_function!(ctx, sys, getsizeof),
      "implementation" => implementation,
      "getfilesystemencoding" => named_function!(ctx, sys, getfilesystemencoding),
      "getfilesystemencodeerrors" => named_function!(ctx, sys, getfilesystemencodeerrors),
      "getdefaultencoding" => named_function!(ctx, sys, getdefaultencoding),
      "getprofile" => named_function!(ctx, sys, getprofile),
      "gettrace" => named_function!(ctx, sys, gettrace),
      "hash_info" => hash_info,
      "intern" => named_function!(ctx, sys, intern),
      "maxunicode" => ctx.new_int(MAXUNICODE),
      "maxsize" => ctx.new_int(MAXSIZE),
      "path" => path,
      "ps1" => ctx.new_str(">>>>> "),
      "ps2" => ctx.new_str("..... "),
      "__doc__" => ctx.new_str(sys_doc),
      "_getframe" => named_function!(ctx, sys, _getframe),
      "modules" => modules.clone(),
      "warnoptions" => ctx.new_list(vec![]),
      "platform" => ctx.new_str(PLATFORM.to_owned()),
      "_framework" => ctx.new_str(framework),
      "meta_path" => ctx.new_list(vec![]),
      "path_hooks" => ctx.new_list(vec![]),
      "path_importer_cache" => ctx.new_dict(),
      "pycache_prefix" => vm.ctx.none(),
      "dont_write_bytecode" => vm.ctx.new_bool(vm.state.settings.dont_write_bytecode),
      "setprofile" => named_function!(ctx, sys, setprofile),
      "setrecursionlimit" => named_function!(ctx, sys, setrecursionlimit),
      "settrace" => named_function!(ctx, sys, settrace),
      "version" => vm.ctx.new_str(version::get_version()),
      "version_info" => version_info,
      "_git" => sys_git_info(vm),
      "exc_info" => named_function!(ctx, sys, exc_info),
      "prefix" => ctx.new_str(prefix),
      "base_prefix" => ctx.new_str(base_prefix),
      "exec_prefix" => ctx.new_str(exec_prefix),
      "base_exec_prefix" => ctx.new_str(base_exec_prefix),
      "exit" => named_function!(ctx, sys, exit),
      "abiflags" => ctx.new_str(ABIFLAGS.to_owned()),
      "audit" => named_function!(ctx, sys, audit),
      "displayhook" => named_function!(ctx, sys, displayhook),
      "__displayhook__" => named_function!(ctx, sys, displayhook),
      "excepthook" => named_function!(ctx, sys, excepthook),
      "__excepthook__" => named_function!(ctx, sys, excepthook),
      "hexversion" => ctx.new_int(version::VERSION_HEX),
      "api_version" => ctx.new_int(0x0), // what C api?
      "float_info" => float_info,
      "int_info" => int_info,
      "float_repr_style" => ctx.new_str("short"),
      "_xoptions" => xopts,
      "warnoptions" => warnopts,
    });

    #[cfg(windows)]
    {
        WindowsVersion::make_class(ctx);
        extend_module!(vm, module, {
            "getwindowsversion" => named_function!(ctx, sys, getwindowsversion),
        })
    }

    modules.set_item("sys", module, vm).unwrap();
    modules.set_item("builtins", builtins, vm).unwrap();
}
