# -*- coding: utf-8 -*-
"""
libuv build utilities.
"""

from __future__ import print_function, absolute_import, division

import ast
import os
import platform
import subprocess
import sys

from _setuputils import WIN
from _setuputils import DEFINE_MACROS
from _setuputils import Extension
from _setuputils import dep_abspath
from _setuputils import system
from _setuputils import glob_many
from _setuputils import should_embed
from _setuputils import _parse_environ

from distutils import log # pylint:disable=no-name-in-module
from distutils.errors import DistutilsError # pylint: disable=no-name-in-module,import-error

# Inspired by code from https://github.com/saghul/pyuv
# Note that they as of June 2017, they now use setuptools to build libuv
# See https://github.com/saghul/pyuv/commit/2c398a9fe47deaebbf47f7bd37d9bb0c350656d1#diff-b30741570389940d7935f51cd0c084cb

LIBUV_EMBED = should_embed('libuv')

if WIN and not LIBUV_EMBED:
    raise DistutilsError('using a system provided libuv is unsupported on Windows')

LIBUV_INCLUDE_DIR = dep_abspath('libuv', 'include')
# Set this to force dynamic linking of libuv, even when building the
# embedded copy. This is most useful when upgrading/changing libuv
# in place.
LIBUV_DYNAMIC_EMBED = _parse_environ("GEVENT_LIBUV_DYNAMIC_EMBED")
if LIBUV_DYNAMIC_EMBED is None:
    # Not set in the environment. Are we developing?
    # This is convenient for my workflow
    # XXX Is there a better way to get this?
    # This probably doesn't work for 'pip install -e .'
    # or dev-requirments.txt
    if 'develop' in sys.argv:
        LIBUV_DYNAMIC_EMBED = LIBUV_EMBED

LIBUV_LIBRARIES = []
if sys.platform.startswith('linux'):
    LIBUV_LIBRARIES.append('rt')
elif WIN:
    LIBUV_LIBRARIES.append('advapi32')
    LIBUV_LIBRARIES.append('iphlpapi')
    LIBUV_LIBRARIES.append('psapi')
    LIBUV_LIBRARIES.append('shell32')
    LIBUV_LIBRARIES.append('userenv')
    LIBUV_LIBRARIES.append('ws2_32')
elif sys.platform.startswith('freebsd'):
    LIBUV_LIBRARIES.append('kvm')

if not LIBUV_EMBED or LIBUV_DYNAMIC_EMBED:
    LIBUV_LIBRARIES.append('uv')

def prepare_windows_env(env):
    env.pop('VS140COMNTOOLS', None)
    env.pop('VS120COMNTOOLS', None)
    env.pop('VS110COMNTOOLS', None)
    if sys.version_info < (3, 3):
        env.pop('VS100COMNTOOLS', None)
        env['GYP_MSVS_VERSION'] = '2008'
    else:
        env['GYP_MSVS_VERSION'] = '2010'

    if not env.get('PYTHON', '').endswith('.exe'):
        env.pop('PYTHON', None)

    if env.get('PYTHON'):
        log.info("Using python from env %s", env['PYTHON'])
        return  # Already manually set by user.

    if sys.version_info[:2] == (2, 7):
        env['PYTHON'] = sys.executable
        return  # The current executable is fine.

    # Try if `python` on PATH is the right one. If we would execute
    # `python` directly the current executable might be used so we
    # delegate this to cmd.
    cmd = ['cmd.exe', '/C', 'python', '-c', 'import sys; '
           'v = str(sys.version_info[:2]); sys.stdout.write(v); '
           'sys.stdout.flush()']
    try:
        sub = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        stdout, _ = sub.communicate()
        version = ast.literal_eval(stdout.decode(sys.stdout.encoding).strip()) # pylint:disable=no-member
        if version == (2, 7):
            return  # Python on PATH is fine
    except OSError:
        pass

    # Check default install locations
    path = os.path.join('%SYSTEMDRIVE%', 'Python27', 'python.exe')
    path = os.path.expandvars(path)
    if os.path.isfile(path):
        log.info('Using "%s" to build libuv...' % path)
        env['PYTHON'] = path
        # Things needed for run_with_env.cmd
        # What we're building for, not what we're building with
        # (because it's just the C library)
        env['PYTHON_VERSION'] = str(sys.version_info[0]) + '.' + str(sys.version_info[1]) + ".x"
        # XXX: Just guessing here. Is PYTHON_ARCH correct?
        if 'PYTHON_ARCH' not in env:
            env['PYTHON_ARCH'] = '64' if platform.architecture()[0] == '64bit' else '32'
        from distutils.msvc9compiler import query_vcvarsall # pylint:disable=import-error,no-name-in-module
        if sys.version_info[:2] >= (3, 5):
            version = 14
        else:
            version = 9 # for 2.7, but probably not right for 3.4?

        env.update(query_vcvarsall(version))
    else:
        raise DistutilsError('No appropriate Python version found. An '
                             'installation of 2.7 is required to '
                             'build libuv. You can set the environment '
                             'variable "PYTHON" to point to a custom '
                             'installation location.')

# This is a dummy extension that serves to let us hook into
# when we need to compile libuv
LIBUV = Extension(name='gevent.libuv._libuv',
                  sources=['src/gevent/libuv/_libuv.c'],
                  include_dirs=[LIBUV_INCLUDE_DIR],
                  libraries=LIBUV_LIBRARIES,
                  define_macros=list(DEFINE_MACROS),
                  depends=glob_many('deps/libuv/src/*.[ch]'))

if LIBUV_EMBED:
    libuv_dir = dep_abspath('libuv')
    if WIN:
        libuv_library_dir = os.path.join(libuv_dir, 'Release', 'lib')
        libuv_lib = os.path.join(libuv_library_dir, 'libuv.lib')
        LIBUV.extra_link_args.extend(['/NODEFAULTLIB:libcmt', '/LTCG'])
        LIBUV.extra_objects.append(libuv_lib)
    else:
        libuv_library_dir = os.path.join(libuv_dir, '.libs')
        libuv_lib = os.path.join(libuv_library_dir, 'libuv.a')
        if not LIBUV_DYNAMIC_EMBED:
            LIBUV.extra_objects.append(libuv_lib)
        else:
            LIBUV.library_dirs.append(libuv_library_dir)
            LIBUV.extra_link_args.extend(["-Wl,-rpath", libuv_library_dir])

def configure_libuv(_bext, _ext):
    def build_libuv():
        cflags = '-fPIC'
        env = os.environ.copy()
        env['CFLAGS'] = ' '.join(x for x in (cflags,
                                             env.get('CFLAGS', None),
                                             env.get('ARCHFLAGS', None))
                                 if x)
        # Since we're building a static library, if link-time-optimization is requested, it
        # results in failure to properly create the library archive. This goes unnoticed on
        # OS X until import time because of '-undefined dynamic_lookup'. On the raspberry
        # pi, it causes the linker to crash
        if '-flto' in env['CFLAGS']:
            log.info("Removing LTO")
            env['CFLAGS'] = env['CFLAGS'].replace('-flto', '')
        log.info('Building libuv with cflags %s', env['CFLAGS'])
        if WIN:
            prepare_windows_env(env)
            libuv_arch = {'32bit': 'x86', '64bit': 'x64'}[platform.architecture()[0]]
            system(["cmd", "/E:ON", "/V:ON", "/C",
                    #"..\\..\\appveyor\\run_with_env.cmd",
                    'vcbuild.bat', libuv_arch, 'release'],
                   cwd=libuv_dir,
                   env=env,
                   shell=False)
        else:
            # autogen: requires automake and libtool installed
            system(['./autogen.sh'],
                   cwd=libuv_dir,
                   env=env)
            # On OS X, the linker will link to the full path
            # of the library libuv *as encoded in the dylib it finds*.
            # So the libdir is important and must match the actual location
            # of the dynamic library if we want to dynamically link to it.
            # Otherwise, we wind up linking to /usr/local/lib/libuv.dylib by
            # default, which can't be found. `otool -D libuv.dylib` will show
            # this name, and `otool -L src/gevent/libuv/_corecffi.so` will show
            # what got linked. Note that this approach results in libuv.dylib
            # apparently linking to *itself*, which is weird, but not harmful
            system(['./configure', '--libdir=' + libuv_library_dir],
                   cwd=libuv_dir,
                   env=env)
            system(['make'],
                   cwd=libuv_dir,
                   env=env)

    if not os.path.exists(libuv_lib):
        log.info('libuv needs to be compiled.')
        build_libuv()
    else:
        log.info('No need to build libuv.')

if LIBUV_EMBED:
    LIBUV.configure = configure_libuv