from __future__ import print_function

import logging
import os
import sys
import re
from os.path import abspath, basename, join, isdir, isfile, islink

from egginst.exe_data import cli, gui
from egginst.utils import on_win, rm_rf

hashbang_pat = re.compile(r'#!.+$', re.M)
executable = sys.executable
logger = logging.getLogger(__name__)


def get_executable(pythonw=False, with_quotes=False):
    res = executable
    if on_win:
        # sys.executable may actually be pythonw.exe in order to avoid
        # popping up a cmd shell during install.
        p = re.compile(r'pythonw?\.exe', re.I)
        res = p.sub('pythonw.exe' if pythonw else 'python.exe', res)
    if with_quotes:
        res = '"%s"' % res
    return res


def write_exe(dst, script_type='console_scripts'):
    """
    This function is only used on Windows.   It either writes cli.exe or
    gui.exe to the destination specified, depending on script_type.

    The binary content of these files are read from the module exe_data,
    which may be generated by the following small script (run from the
    setuptools directory which contains the file cli.exe and gui.exe:
    fo = open('exe_data.py', 'w')
    for name in ['cli', 'gui']:
        data = open('%s.exe' % name, 'rb').read()
        fo.write('%s = %r\n' % (name, data))
    fo.close()
    """
    if script_type == 'console_scripts':
        data = cli
    elif script_type == 'gui_scripts':
        data = gui
    else:
        raise Exception("Did not except script_type=%r" % script_type)

    rm_rf(dst)
    try:
        with open(dst, 'wb') as fp:
            fp.write(data)
    except IOError:
        # When bootstrapping, the file egginst.exe is in use and can therefore
        # not be rewritten, which is OK since its content is always the same.
        pass
    os.chmod(dst, 0o755)


def create_proxy(src, bin_dir):
    """
    create a proxy of src in bin_dir (Windows only)
    """
    logger.info("Creating proxy executable to: %r", src)
    assert src.endswith('.exe')

    dst_name = basename(src)
    if dst_name.startswith('epd-'):
        dst_name = dst_name[4:]

    dst = join(bin_dir, dst_name)
    write_exe(dst)

    dst_script = dst[:-4] + '-script.py'
    rm_rf(dst_script)

    with open(dst_script, 'w') as fo:
        fo.write('''\
#!"%(python)s"
# This proxy was created by egginst from an egg with special instructions
#
import sys
import subprocess

src = %(src)r

sys.exit(subprocess.call([src] + sys.argv[1:]))
''' % dict(python=get_executable(), src=src))
    return dst, dst_script


def create_proxies(egg):
    # This function is called on Windows only
    if not isdir(egg.bin_dir):
        os.makedirs(egg.bin_dir)

    for line in egg.iter_files_to_install():
        arcname, action = line.split()
        logger.info("arcname=%r    action=%r", arcname, action)

        if action == 'PROXY':
            ei = 'EGG-INFO/'
            if arcname.startswith(ei):
                src = abspath(join(egg.meta_dir, arcname[len(ei):]))
            else:
                src = abspath(join(egg.prefix, arcname))
            logger.info("     src: %r", src)
            egg.files.extend(create_proxy(src, egg.bin_dir))
        else:
            data = egg.z.read(arcname)
            dst = abspath(join(egg.prefix, action, basename(arcname)))
            logger.info("     dst: %r", dst)
            rm_rf(dst)
            with open(dst, 'wb') as fo:
                fo.write(data)
            egg.files.append(dst)


def write_script(path, entry_pt, egg_name):
    """
    Write an entry point script to path.
    """
    logger.info('Creating script: %s', path)

    assert entry_pt.count(':') == 1
    module, func = entry_pt.strip().split(':')

    rm_rf(path)
    with open(path, 'w') as fo:
        fo.write('''\
#!%(python)s
# This script was created by egginst when installing:
#
#   %(egg_name)s
#
if __name__ == '__main__':
    import sys
    from %(module)s import %(func)s

    sys.exit(%(func)s())
''' % dict(python=get_executable(pythonw=path.endswith('.pyw'),
                                 with_quotes=on_win),
           egg_name=egg_name, module=module, func=func))
    os.chmod(path, 0o755)


def create(egg, conf):
    if not isdir(egg.bin_dir):
        os.makedirs(egg.bin_dir)

    for script_type in ['gui_scripts', 'console_scripts']:
        if script_type not in conf.sections():
            continue
        for name, entry_pt in conf.items(script_type):
            fname = name
            if on_win:
                exe_path = join(egg.bin_dir, '%s.exe' % name)
                write_exe(exe_path, script_type)
                egg.files.append(exe_path)
                fname += '-script.py'
                if script_type == 'gui_scripts':
                    fname += 'w'
            path = join(egg.bin_dir, fname)
            write_script(path, entry_pt, egg.fn)
            egg.files.append(path)


def fix_script(path):
    """
    Fixes a single located at path.
    """
    if islink(path) or not isfile(path):
        return

    with open(path, "rb") as fi:
        data = fi.read()

    try:
        data = data.decode("utf8")
    except UnicodeDecodeError:
        return

    if ' egginst ' in data:
        # This string is in the comment when write_script() creates
        # the script, so there is no need to fix anything.
        return

    m = hashbang_pat.match(data)
    if not (m and 'python' in m.group().lower()):
        return

    python = get_executable(with_quotes=on_win)
    new_data = hashbang_pat.sub('#!' + python.replace('\\', '\\\\'),
                                data, count=1)
    if new_data == data:
        return
    logger.info("Updating: %r", path)

    with open(path, 'wb') as fo:
        fo.write(new_data.encode("utf8"))

    os.chmod(path, 0o755)


def fix_scripts(egg):
    for path in egg.files:
        if path.startswith(egg.bin_dir):
            fix_script(path)
