# -*- coding: utf-8 -*-
#
# Copyright © 2012 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see winpython/__init__.py for details)

"""
WinPython utilities

Created on Tue Aug 14 14:08:40 2012
"""

import os
from pathlib import Path
import subprocess
import re
import tarfile
import zipfile
import tempfile
import shutil
import atexit
import sys
import stat
import locale
import io

# Local imports
import winreg

def get_python_executable(path = None):
    """return the python executable"""
    my_path = sys.executable if path == None else path  # default = current one
    my_path = my_path if Path(my_path).is_dir() else str(Path(my_path).parent)
    exec_py = str(Path(my_path) / 'python.exe')
    exec_pypy = str(Path(my_path) / 'pypy3.exe')  # PyPy !
    # PyPy >=7.3.6 3.8 aligns to python.exe and Lib\site-packages
    python_executable = exec_py if Path(exec_py).is_file() else exec_pypy
    return python_executable

def get_site_packages_path(path = None):
    """return the python site-packages"""
    my_path = sys.executable if path == None else path  # default = current one
    my_path = my_path if Path(my_path).is_dir() else str(Path(my_path).parent)
    site_py = str(Path(my_path) / 'Lib' / 'site-packages')
    site_pypy = str(Path(my_path) / 'site-packages')  # PyPy !!
    site_packages_path = site_pypy if Path(site_pypy).is_dir() else site_py
    return site_packages_path

def onerror(function, path, excinfo):
    """Error handler for `shutil.rmtree`.

    If the error is due to an access error (read-only file), it
    attempts to add write permission and then retries.
    If the error is for another reason, it re-raises the error.

    Usage: `shutil.rmtree(path, onerror=onerror)"""
    if not os.access(path, os.W_OK):
        # Is the error an access error?
        os.chmod(path, stat.S_IWUSR)
        function(path)
    else:
        raise


# Exact copy of 'spyderlib.utils.programs.is_program_installed' function
def is_program_installed(basename):
    """Return program absolute path if installed in PATH
    Otherwise, return None"""
    for path in os.environ["PATH"].split(os.pathsep):
        abspath = str(Path(path) / basename)
        if Path(abspath).is_file():
            return abspath


# =============================================================================
# Environment variables
# =============================================================================
def get_env(name, current=True):
    """Return HKCU/HKLM environment variable name and value

    For example, get_user_env('PATH') may returns:
    ('Path', u'C:\\Program Files\\Intel\\WiFi\\bin\\')"""
    root = (
        winreg.HKEY_CURRENT_USER
        if current
        else winreg.HKEY_LOCAL_MACHINE
    )
    key = winreg.OpenKey(root, "Environment")
    for index in range(0, winreg.QueryInfoKey(key)[1]):
        try:
            value = winreg.EnumValue(key, index)
            if value[0].lower() == name.lower():
                # Return both value[0] and value[1] because value[0] could be
                # different from name (lowercase/uppercase)
                return value[0], value[1]
        except:
            break


def set_env(name, value, current=True):
    """Set HKCU/HKLM environment variables"""
    root = (
        winreg.HKEY_CURRENT_USER
        if current
        else winreg.HKEY_LOCAL_MACHINE
    )
    key = winreg.OpenKey(root, "Environment")
    try:
        _x, key_type = winreg.QueryValueEx(key, name)
    except WindowsError:
        key_type = winreg.REG_EXPAND_SZ
    key = winreg.OpenKey(
        root, "Environment", 0, winreg.KEY_SET_VALUE
    )
    winreg.SetValueEx(key, name, 0, key_type, value)
    from win32gui import SendMessageTimeout
    from win32con import (
        HWND_BROADCAST,
        WM_SETTINGCHANGE,
        SMTO_ABORTIFHUNG,
    )

    SendMessageTimeout(
        HWND_BROADCAST,
        WM_SETTINGCHANGE,
        0,
        "Environment",
        SMTO_ABORTIFHUNG,
        5000,
    )


#==============================================================================
# https://stackoverflow.com/questions/580924/how-to-access-a-files-properties-on-windows
def getFileProperties(fname):
#==============================================================================
    """
    Read all properties of the given file return them as a dictionary.
    """
    import win32api
    propNames = ('Comments', 'InternalName', 'ProductName',
        'CompanyName', 'LegalCopyright', 'ProductVersion',
        'FileDescription', 'LegalTrademarks', 'PrivateBuild',
        'FileVersion', 'OriginalFilename', 'SpecialBuild')

    props = {'FixedFileInfo': None, 'StringFileInfo': None, 'FileVersion': None}

    try:
        # backslash as parm returns dictionary of numeric info corresponding to VS_FIXEDFILEINFO struc
        fixedInfo = win32api.GetFileVersionInfo(fname, '\\')
        props['FixedFileInfo'] = fixedInfo
        props['FileVersion'] = "%d.%d.%d.%d" % (fixedInfo['FileVersionMS'] / 65536,
                fixedInfo['FileVersionMS'] % 65536, fixedInfo['FileVersionLS'] / 65536,
                fixedInfo['FileVersionLS'] % 65536)

        # \VarFileInfo\Translation returns list of available (language, codepage)
        # pairs that can be used to retreive string info. We are using only the first pair.
        lang, codepage = win32api.GetFileVersionInfo(fname, '\\VarFileInfo\\Translation')[0]

        # any other must be of the form \StringfileInfo\%04X%04X\parm_name, middle
        # two are language/codepage pair returned from above

        strInfo = {}
        for propName in propNames:
            strInfoPath = u'\\StringFileInfo\\%04X%04X\\%s' % (lang, codepage, propName)
            ## print str_info
            strInfo[propName] = win32api.GetFileVersionInfo(fname, strInfoPath)

        props['StringFileInfo'] = strInfo
    except:
        pass

    return props
# =============================================================================
# Shortcuts, start menu
# =============================================================================


def get_special_folder_path(path_name):
    """Return special folder path"""
    from win32com.shell import shell, shellcon

    for maybe in """
       CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA
       CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY
       CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP
       CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON
       CSIDL_PROGRAM_FILES CSIDL_FONTS""".split():
        if maybe == path_name:
            csidl = getattr(shellcon, maybe)
            return shell.SHGetSpecialFolderPath(
                0, csidl, False
            )
    raise ValueError(
        f"{path_name} is an unknown path ID"
    )


def get_winpython_start_menu_folder(current=True):
    """Return WinPython Start menu shortcuts folder"""
    if current:
        # non-admin install - always goes in this user's start menu.
        folder = get_special_folder_path("CSIDL_PROGRAMS")
    else:
        try:
            folder = get_special_folder_path(
                "CSIDL_COMMON_PROGRAMS"
            )
        except OSError:
            # No CSIDL_COMMON_PROGRAMS on this platform
            folder = get_special_folder_path(
                "CSIDL_PROGRAMS"
            )
    return str(Path(folder) / 'WinPython')

def remove_winpython_start_menu_folder(current=True):
    """Create WinPython Start menu folder -- remove it if it already exists"""
    path = get_winpython_start_menu_folder(current=current)
    if Path(path).is_dir():
        try:
            shutil.rmtree(path, onerror=onerror)
        except WindowsError:
            print(
                f"Directory {path} could not be removed",
                file=sys.stderr,
            )

def create_winpython_start_menu_folder(current=True):
    """Create WinPython Start menu folder -- remove it if it already exists"""
    path = get_winpython_start_menu_folder(current=current)
    if Path(path).is_dir():
        try:
            shutil.rmtree(path, onerror=onerror)
        except WindowsError:
            print(
                f"Directory {path} could not be removed",
                file=sys.stderr,
            )
    # create, or re-create !
    os.mkdir(path)
    return path


def create_shortcut(
    path,
    description,
    filename,
    arguments="",
    workdir="",
    iconpath="",
    iconindex=0,
):
    """Create Windows shortcut (.lnk file)"""
    import pythoncom
    from win32com.shell import shell

    ilink = pythoncom.CoCreateInstance(
        shell.CLSID_ShellLink,
        None,
        pythoncom.CLSCTX_INPROC_SERVER,
        shell.IID_IShellLink,
    )
    ilink.SetPath(path)
    ilink.SetDescription(description)
    if arguments:
        ilink.SetArguments(arguments)
    if workdir:
        ilink.SetWorkingDirectory(workdir)
    if iconpath or iconindex:
        ilink.SetIconLocation(iconpath, iconindex)
    # now save it.
    ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile)
    if not filename.endswith('.lnk'):
        filename += '.lnk'
    print(f'ipf.save *{filename}*')    
    try:
        ipf.Save(filename, 0)
    except:
        print ("a fail !")
        pass


# =============================================================================
# Misc.
# =============================================================================


def print_box(text):
    """Print text in a box"""
    line0 = "+" + ("-" * (len(text) + 2)) + "+"
    line1 = "| " + text + " |"
    print(
        ("\n\n" + "\n".join([line0, line1, line0]) + "\n")
    )


def is_python_distribution(path):
    """Return True if path is a Python distribution"""
    # XXX: This test could be improved but it seems to be sufficient
    has_exec = Path(get_python_executable(path)).is_file()
    has_site = Path(get_site_packages_path(path)).is_dir()    
    return has_exec and has_site


# =============================================================================
# Shell, Python queries
# =============================================================================


def decode_fs_string(string):
    """Convert string from file system charset to unicode"""
    charset = sys.getfilesystemencoding()
    if charset is None:
        charset = locale.getpreferredencoding()
    return string.decode(charset)


def exec_shell_cmd(args, path):
    """Execute shell command (*args* is a list of arguments) in *path*"""
    # print " ".join(args)
    process = subprocess.Popen(
        args,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        cwd=path,
        shell=True,
    )
    return decode_fs_string(process.stdout.read())

def exec_run_cmd(args, path=None):
    """run a single command (*args* is a list of arguments) in optional *path*"""
    # only applicable to Python-3.5+
    # python-3.7+ allows to replace "stdout and stderr ", per "capture_output=True"
    if path:
        process = subprocess.run(args,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                cwd=path)
    else:
        process = subprocess.run(args,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        return  decode_fs_string(process.stdout)


def get_r_version(path):
    """Return version of the R installed in *path*"""
    return (
        exec_shell_cmd('dir ..\README.R*', path)
        .splitlines()[-3]
        .split("-")[-1]
    )


def get_julia_version(path):
    """Return version of the Julia installed in *path*"""
    return (
        exec_shell_cmd('julia.exe -v', path)
        .splitlines()[0]
        .split(" ")[-1]
    )


def get_nodejs_version(path):
    """Return version of the Nodejs installed in *path*"""
    return exec_shell_cmd('node -v', path).splitlines()[0]


def get_npmjs_version(path):
    """Return version of the Nodejs installed in *path*"""
    return exec_shell_cmd('npm -v', path).splitlines()[0]


def get_pandoc_version(path):
    """Return version of the Pandoc executable in *path*"""
    return (
        exec_shell_cmd('pandoc -v', path)
        .splitlines()[0]
        .split(" ")[-1]
    )


def python_query(cmd, path):
    """Execute Python command using the Python interpreter located in *path*"""
    the_exe = get_python_executable(path)
    # debug2021-09-12
    print(f'"{the_exe}" -c "{cmd}"', ' * ',  path)
    return exec_shell_cmd(f'"{the_exe}" -c "{cmd}"', path).splitlines()[0]

def python_execmodule(cmd, path):
    """Execute Python command using the Python interpreter located in *path*"""
    the_exe = get_python_executable(path)
    exec_shell_cmd(f'{the_exe} -m {cmd}', path)


def get_python_infos(path):
    """Return (version, architecture) for the Python distribution located in
    *path*. The version number is limited to MAJOR.MINOR, the architecture is
    an integer: 32 or 64"""
    is_64 = python_query(
        'import sys; print(sys.maxsize > 2**32)', path
    )
    arch = {'True': 64, 'False': 32}.get(is_64, None)
    ver = python_query(
        "import sys;print(f'{sys.version_info.major}.{sys.version_info.minor}')"
        ,
        path,
    )
    if re.match(r'([0-9]*)\.([0-9]*)', ver) is None:
        ver = None
  
    return ver, arch


def get_python_long_version(path):
    """Return long version (X.Y.Z) for the Python distribution located in
    *path*"""
    ver = python_query(
        "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')"
        ,
        path,
    )
    if (
        re.match(r'([0-9]*)\.([0-9]*)\.([0-9]*)', ver)
        is None
    ):
        ver = None
    return ver


# =============================================================================
# Patch chebang line (courtesy of Christoph Gohlke)
# =============================================================================
def patch_shebang_line(
    fname, pad=b' ', to_movable=True, targetdir=""
):
    """Remove absolute path to python.exe in shebang lines, or re-add it"""

    import re
    import sys
    import os

    target_dir = targetdir  # movable option
    if to_movable == False:
        target_dir = os.path.abspath(os.path.dirname(fname))
        target_dir = (
            os.path.abspath(os.path.join(target_dir, r'..'))
            + '\\'
        )
    executable = sys.executable
    if sys.version_info[0] == 2:
        shebang_line = re.compile(
            r"(#!.*pythonw?\.exe)"
        )  # Python2.7
    else:
        shebang_line = re.compile(
            b"(#!.*pythonw?\.exe)"
        )  # Python3+
        if 'pypy3' in sys.executable:
            shebang_line = re.compile(
            b"(#!.*pypy3w?\.exe)"
        )  # Pypy3+
            
        target_dir = target_dir.encode('utf-8')
    with open(fname, 'rb') as fh:
        initial_content = fh.read()
        fh.close
        fh = None
    content = shebang_line.split(
        initial_content, maxsplit=1
    )
    if len(content) != 3:
        return
    exe = os.path.basename(content[1][2:])
    content[1] = (
        b'#!' + target_dir + exe
    )  # + (pad * (len(content[1]) - len(exe) - 2))
    final_content = b''.join(content)
    if initial_content == final_content:
        return
    try:
        with open(fname, 'wb') as fo:
            fo.write(final_content)
            fo.close
            fo = None
            print("patched", fname)
    except Exception:
        print("failed to patch", fname)


# =============================================================================
# Patch shebang line in .py files
# =============================================================================
def patch_shebang_line_py(
    fname, to_movable=True, targetdir=""
):
    """Changes shebang line in '.py' file to relative or absolue path"""
    import fileinput
    import re
    import sys

    if sys.version_info[0] == 2:
        # Python 2.x doesn't create .py files for .exe files. So, Moving
        # WinPython doesn't break running executable files.
        return
    if to_movable:
        exec_path = '#!.\python.exe'
        if 'pypy3' in sys.executable:  # PyPy !
            exec_path = '#!.\pypy3.exe'
    else:
        exec_path = '#!' + sys.executable
    for line in fileinput.input(fname, inplace=True):
        if re.match('^#\!.*python\.exe$', line) is not None:
            print(exec_path)
        elif re.match('^#\!.*pypy3\.exe$', line) is not None:# PyPy !
            print(exec_path)          
        else:
            print(line, end='')


# =============================================================================
# Guess encoding (shall rather be utf-8 per default)
# =============================================================================
def guess_encoding(csv_file):
    """guess the encoding of the given file"""
    # UTF_8_BOM = "\xEF\xBB\xBF"
    # Python behavior on UTF-16 not great on write, so we drop it
    with io.open(csv_file, "rb") as f:
        data = f.read(5)
    if data.startswith(b"\xEF\xBB\xBF"):  # UTF-8 with a "BOM" (normally no BOM in utf-8)
        return ["utf-8-sig"]
    else:  # in Windows, guessing utf-8 doesn't work, so we have to try
        try:
            with io.open(csv_file, encoding="utf-8") as f:
                preview = f.read(222222)
                return ["utf-8"]
        except:
            return [locale.getdefaultlocale()[1], "utf-8"]
            
# =============================================================================
# Patch sourcefile (instead of forking packages)
# =============================================================================
def patch_sourcefile(
    fname, in_text, out_text, silent_mode=False
):
    """Replace a string in a source file"""
    import io

    if Path(fname).is_file() and not in_text == out_text:
        the_encoding = guess_encoding(fname)[0]
        with io.open(fname, 'r', encoding=the_encoding) as fh:
            content = fh.read()
        new_content = content.replace(in_text, out_text)
        if not new_content == content:
            if not silent_mode:
                print(
                    "patching ",
                    fname,
                    "from",
                    in_text,
                    "to",
                    out_text,
                )
            with io.open(fname, 'wt', encoding=the_encoding) as fh:
                fh.write(new_content)


# =============================================================================
# Patch sourcelines (instead of forking packages)
# =============================================================================
def patch_sourcelines(
    fname,
    in_line_start,
    out_line,
    endline='\n',
    silent_mode=False,
):
    """Replace the middle of lines between in_line_start and endline """
    import io

    if Path(fname).is_file():
        the_encoding = guess_encoding(fname)[0]
        with io.open(fname, 'r', encoding=the_encoding) as fh:
            contents = fh.readlines()
            content = "".join(contents)
            for l in range(len(contents)):
                if contents[l].startswith(in_line_start):
                    begining, middle = (
                        in_line_start,
                        contents[l][len(in_line_start) :],
                    )
                    ending = ""
                    if middle.find(endline) > 0:
                        ending = endline + endline.join(
                            middle.split(endline)[1:]
                        )
                        middle = middle.split(endline)[0]
                    middle = out_line
                    new_line = begining + middle + ending
                    if not new_line == contents[l]:
                        if not silent_mode:
                            print(
                                "patching ",
                                fname,
                                " from\n",
                                contents[l],
                                "\nto\n",
                                new_line,
                            )
                    contents[l] = new_line
            new_content = "".join(contents)
        if not new_content == content:
            # if not silent_mode:
            #    print("patching ", fname, "from", content, "to", new_content)
            with io.open(fname, 'wt', encoding=the_encoding) as fh:
                try:
                    fh.write(new_content)
                except:
                    print(
                        "impossible to patch",
                        fname,
                        "from",
                        content,
                        "to",
                        new_content,
                    )


# =============================================================================
# Extract functions
# =============================================================================
def _create_temp_dir():
    """Create a temporary directory and remove it at exit"""
    tmpdir = tempfile.mkdtemp(prefix='wppm_')
    atexit.register(
        lambda path: shutil.rmtree(path, onerror=onerror),
        tmpdir,
    )
    return tmpdir


def extract_exe(fname, targetdir=None, verbose=False):
    """Extract .exe archive to a temporary directory (if targetdir
    is None). Return the temporary directory path"""
    if targetdir is None:
        targetdir = _create_temp_dir()
    extract = '7z.exe'
    assert is_program_installed(extract), (
        f"Required program '{extract}' was not found"
    )
    bname = Path(fname).name
    args = ['x', f'-o{targetdir}', '-aos', bname]
    if verbose:
        retcode = subprocess.call(
            [extract] + args, cwd=str(Path(fname).parent)
        )
    else:
        p = subprocess.Popen(
            [extract] + args,
            cwd=str(Path(fname).parent),
            stdout=subprocess.PIPE,
        )
        p.communicate()
        p.stdout.close()
        retcode = p.returncode
    if retcode != 0:
        raise RuntimeError(
            f"Failed to extract {fname} (return code: {retcode})"
        )
    return targetdir


def extract_archive(fname, targetdir=None, verbose=False):
    """Extract .zip, .exe (considered to be a zip archive) or .tar.gz archive
    to a temporary directory (if targetdir is None).
    Return the temporary directory path"""
    if targetdir is None:
        targetdir = _create_temp_dir()
    else:
        try:
            os.mkdir(targetdir)
        except:
            pass
    if Path(fname).suffix in ('.zip', '.exe'):
        obj = zipfile.ZipFile(fname, mode="r")
    elif fname.endswith('.tar.gz'):
        obj = tarfile.open(fname, mode='r:gz')
    else:
        raise RuntimeError(
            f"Unsupported archive filename {fname}"
        )
    obj.extractall(path=targetdir)
    return targetdir


WININST_PATTERN = r'([a-zA-Z0-9\-\_]*|[a-zA-Z\-\_\.]*)-([0-9\.\-]*[a-z]*[0-9]?)(-Qt-([0-9\.]+))?.(win32|win\-amd64)(-py([0-9\.]+))?(-setup)?\.exe'

# SOURCE_PATTERN defines what an acceptable source package name is
# As of 2014-09-08 :
#    - the wheel package format is accepte in source directory
#    - the tricky regexp is tuned also to support the odd jolib naming :
#         . joblib-0.8.3_r1-py2.py3-none-any.whl,
#         . joblib-0.8.3-r1.tar.gz

SOURCE_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z]*[\-]?[0-9]*)(\.zip|\.tar\.gz|\-(py[2-7]*|py[2-7]*\.py[2-7]*)\-none\-any\.whl)'

# WHEELBIN_PATTERN defines what an acceptable binary wheel package is
# "cp([0-9]*)" to replace per cp(34) for python3.4
# "win32|win\_amd64" to replace per "win\_amd64" for 64bit
WHEELBIN_PATTERN = r'([a-zA-Z0-9\-\_\.]*)-([0-9\.\_]*[a-z0-9\+]*[0-9]?)-cp([0-9]*)\-[0-9|c|o|n|e|p|m]*\-(win32|win\_amd64)\.whl'


def get_source_package_infos(fname):
    """Return a tuple (name, version) of the Python source package"""
    if fname[-4:] == '.whl':
        return Path(fname).name.split("-")[:2]
    match = re.match(SOURCE_PATTERN, Path(fname).name)
    if match is not None:
        return match.groups()[:2]


def build_wininst(
    root,
    python_exe=None,
    copy_to=None,
    architecture=None,
    verbose=False,
    installer='bdist_wininst',
):
    """Build wininst installer from Python package located in *root*
    and eventually copy it to *copy_to* folder.
    Return wininst installer full path."""
    if python_exe is None:
        python_exe = sys.executable
    assert Path(python_exe).is_file()
    cmd = [python_exe, 'setup.py', 'build']
    if architecture is not None:
        archstr = (
            'win32' if architecture == 32 else 'win-amd64'
        )
        cmd += [f'--plat-name={archstr}']
    cmd += [installer]
    # root = a tmp dir in windows\tmp,
    if verbose:
        subprocess.call(cmd, cwd=root)
    else:
        p = subprocess.Popen(
            cmd,
            cwd=root,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        p.communicate()
        p.stdout.close()
        p.stderr.close()
    distdir = str(Path(root) / 'dist')
    if not Path(distdir).is_dir():
        raise RuntimeError(
            "Build failed: see package README file for further"
            " details regarding installation requirements.\n\n"
            "For more concrete debugging infos, please try to build "
            "the package from the command line:\n"
            "1. Open a WinPython command prompt\n"
            "2. Change working directory to the appropriate folder\n"
            "3. Type `python setup.py build install`"
        )
    pattern = WININST_PATTERN.replace(
        r'(win32|win\-amd64)', archstr
    )
    for distname in os.listdir(distdir):
        match = re.match(pattern, distname)
        if match is not None:
            break
        # for wheels (winpython here)
        match = re.match(SOURCE_PATTERN, distname)
        if match is not None:
            break
        match = re.match(WHEELBIN_PATTERN, distname)
        if match is not None:
            break
    else:
        raise RuntimeError(
            f"Build failed: not a pure Python package? {distdir}"
        )
    src_fname = str(Path(distdir) / distname)
    if copy_to is None:
        return src_fname
    else:
        dst_fname = str(Path(copy_to) / distname)
        shutil.move(src_fname, dst_fname)
        if verbose:
            print(
                (
                    f"Move: {src_fname} --> {dst_fname}"
                )
            )
            # remove tempo dir 'root' no more needed
            shutil.rmtree(root, onerror=onerror)
        return dst_fname


def buildflit_wininst(
    root,
    python_exe=None,
    copy_to=None,
    architecture=None,  # shall be unused
    verbose=False,
    installer='bdist_wininst',  # unused
):
    """Build wininst installer from Python package located in *root*
    with flit"""
    if python_exe is None:
        python_exe = sys.executable
    assert Path(python_exe).is_file()
    cmd = [python_exe, '-m' ,'flit', 'build']
    if architecture is not None:
        archstr = (
            'win32' if architecture == 32 else 'win-amd64'
        )
    # root = a tmp dir in windows\tmp,
    if verbose:
        subprocess.call(cmd, cwd=root)
    else:
        p = subprocess.Popen(
            cmd,
            cwd=root,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        p.communicate()
        p.stdout.close()
        p.stderr.close()
    distdir = str(Path(root) / 'dist')
    if not Path(distdir).is_dir():
        raise RuntimeError(
            "Build failed: see package README file for further"
            " details regarding installation requirements.\n\n"
            "For more concrete debugging infos, please try to build "
            "the package from the command line:\n"
            "1. Open a WinPython command prompt\n"
            "2. Change working directory to the appropriate folder\n"
            "3. Type `python -m filt build`"
        )
    pattern = WININST_PATTERN.replace(
        r'(win32|win\-amd64)', archstr
    )
    for distname in os.listdir(distdir):
        match = re.match(pattern, distname)
        if match is not None:
            break
        # for wheels (winpython here)
        match = re.match(SOURCE_PATTERN, distname)
        if match is not None:
            break
        match = re.match(WHEELBIN_PATTERN, distname)
        if match is not None:
            break
    else:
        raise RuntimeError(
            f"Build failed: not a pure Python package? {distdir}"
        )
    src_fname = str(Path(distdir) / distname)
    if copy_to is None:
        return src_fname
    else:
        dst_fname = str(Path(copy_to) / distname)
        shutil.move(src_fname, dst_fname)
        if verbose:
            print(
                (
                    f"Move: {src_fname} --> {dst_fname}"
                )
            )
            # remove tempo dir 'root' no more needed
            shutil.rmtree(root, onerror=onerror)
        return dst_fname


def direct_pip_install(
    fname,
    python_exe=None,
    architecture=None,
    verbose=False,
    install_options=None,
):
    """Direct install via pip !"""
    copy_to = str(Path(fname).parent)

    if python_exe is None:
        python_exe = sys.executable
    assert Path(python_exe).is_file()
    myroot = str(Path(python_exe).parent)

    cmd = [python_exe, '-m', 'pip', 'install']
    if install_options:
        cmd += install_options  # typically ['--no-deps']
        print('pip install_options', install_options)
    cmd += [fname]

    if verbose:
        subprocess.call(cmd, cwd=myroot)
    else:
        p = subprocess.Popen(
            cmd,
            cwd=myroot,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        stdout, stderr = p.communicate()
        the_log = f"{stdout}" + f"\n {stderr}"

        if (
            ' not find ' in the_log
            or ' not found ' in the_log
        ):
            print(f"Failed to Install: \n {fname} \n")
            print(f"msg: {the_log}")
            raise RuntimeError
        p.stdout.close()
        p.stderr.close()
    src_fname = fname
    if copy_to is None:
        return src_fname
    else:
        if verbose:
            print(f"Installed {src_fname}")
        return src_fname


def do_script(
    this_script,
    python_exe=None,
    copy_to=None,
    architecture=None,
    verbose=False,
    install_options=None,
):
    """Execute a script (get-pip typically)"""
    if python_exe is None:
        python_exe = sys.executable
    myroot = os.path.dirname(python_exe)

    # cmd = [python_exe, myroot + r'\Scripts\pip-script.py', 'install']
    cmd = [python_exe]
    if install_options:
        cmd += install_options  # typically ['--no-deps']
        print('script install_options', install_options)
    if this_script:
        cmd += [this_script]
    # print('build_wheel', myroot, cmd)
    print("Executing ", cmd)

    if verbose:
        subprocess.call(cmd, cwd=myroot)
    else:
        p = subprocess.Popen(
            cmd,
            cwd=myroot,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        p.communicate()
        p.stdout.close()
        p.stderr.close()
    if verbose:
        print("Executed " , cmd)
    return 'ok'


if __name__ == '__main__':

    print_box("Test")
    dname = sys.prefix
    print((dname + ':', '\n', get_python_infos(dname)))
    # dname = r'E:\winpython\sandbox\python-2.7.3'
    # print dname+':', '\n', get_python_infos(dname)

    tmpdir = r'D:\Tests\winpython_tests'
    if not Path(tmpdir).is_dir():
        os.mkdir(tmpdir)
    print(
        (
            extract_archive(
                str(Path(r'D:\WinP\bd37') / 'packages.win-amd64' /
                    'python-3.7.3.amd64.zip'),        
                tmpdir,
            )    
        )
    )
