from setuptools import setup, Extension, find_packages
import subprocess
import errno
import re
import os
import shutil
import sys
import zipfile
from urllib.request import urlretrieve

import numpy
from Cython.Build import cythonize
   
isWindows = os.name == 'nt'
isMac = sys.platform == 'darwin'
is64Bit = sys.maxsize > 2**32

# adapted from cffi's setup.py
# the following may be overridden if pkg-config exists
libraries = ['lensfun']
include_dirs = []
library_dirs = []
extra_compile_args = []
extra_link_args = []

def _ask_pkg_config(resultlist, option, result_prefix='', sysroot=False):
    pkg_config = os.environ.get('PKG_CONFIG','pkg-config')
    try:
        p = subprocess.Popen([pkg_config, option, 'lensfun'],
                             stdout=subprocess.PIPE)
    except OSError as e:
        if e.errno != errno.ENOENT:
            raise
    else:
        t = p.stdout.read().decode().strip()
        if p.wait() == 0:
            res = t.split()
            # '-I/usr/...' -> '/usr/...'
            for x in res:
                assert x.startswith(result_prefix)
            res = [x[len(result_prefix):] for x in res]

            sysroot = sysroot and os.environ.get('PKG_CONFIG_SYSROOT_DIR', '')
            if sysroot:
                # old versions of pkg-config don't support this env var,
                # so here we emulate its effect if needed
                res = [path if path.startswith(sysroot)
                            else sysroot + path
                         for path in res]
            resultlist[:] = res

def use_pkg_config():
    _ask_pkg_config(include_dirs,       '--cflags-only-I', '-I', sysroot=True)
    _ask_pkg_config(extra_compile_args, '--cflags-only-other')
    _ask_pkg_config(library_dirs,       '--libs-only-L', '-L', sysroot=True)
    _ask_pkg_config(extra_link_args,    '--libs-only-other')
    _ask_pkg_config(libraries,          '--libs-only-l', '-l')

if isWindows or isMac:
    cmake_build = os.path.abspath('external/lensfun/build')
    install_dir = os.path.join(cmake_build, 'install')
    
    include_dirs += [os.path.join(install_dir, 'include', 'lensfun')]
    library_dirs += [os.path.join(install_dir, 'lib')]
else:
    use_pkg_config()
    
# this must be after use_pkg_config()!
include_dirs += [numpy.get_include()]

# for version_helper.h
include_dirs += [os.path.abspath('lensfunpy')]

def clone_submodules():
    if not os.path.exists('external/lensfun/README.md'):
        print('lensfun git submodule not cloned yet, will invoke "git submodule update --init" now')
        if os.system('git submodule update --init') != 0:
            raise Exception('git failed')

def windows_lensfun_compile():
    clone_submodules()

    cwd = os.getcwd()
    
    # Download cmake to build lensfun
    cmake_version = '3.13.4'
    cmake_url = 'https://github.com/Kitware/CMake/releases/download/v{v}/cmake-{v}-win32-x86.zip'.format(v=cmake_version)
    cmake = os.path.abspath('external/cmake-{}-win32-x86/bin/cmake.exe'.format(cmake_version))

    # Download vcpkg to build dependencies of lensfun
    vcpkg_commit = '2021.05.12'
    vcpkg_url = 'https://github.com/Microsoft/vcpkg/archive/{}.zip'.format(vcpkg_commit)
    vcpkg_dir = os.path.abspath('external/vcpkg-{}'.format(vcpkg_commit))
    vcpkg_bootstrap = os.path.join(vcpkg_dir, 'bootstrap-vcpkg.bat')
    vcpkg = os.path.join(vcpkg_dir, 'vcpkg.exe')
    
    files = [(cmake_url, 'external', cmake),
             (vcpkg_url, 'external', vcpkg_bootstrap)]

    for url, extractdir, extractcheck in files:
        if not os.path.exists(extractcheck):
            path = 'external/' + os.path.basename(url)
            if not os.path.exists(path):
                print('Downloading', url)
                try:
                    urlretrieve(url, path)
                except:
                    # repeat once in case of network issues
                    urlretrieve(url, path)
        
            with zipfile.ZipFile(path) as z:
                print('Extracting', path, 'into', extractdir)
                z.extractall(extractdir)
                
            if not os.path.exists(path):
                raise RuntimeError(path + ' not found!')

    # Bootstrap vcpkg
    os.chdir(vcpkg_dir)
    if not os.path.exists(vcpkg):
        code = os.system(vcpkg_bootstrap)
        if code != 0:
            sys.exit(code) 

    # lensfun depends on glib2, so let's build it with vcpkg
    vcpkg_arch = 'x64' if is64Bit else 'x86'
    vcpkg_triplet = '{}-windows'.format(vcpkg_arch)
    code = os.system(vcpkg + ' install glib:' + vcpkg_triplet)
    if code != 0:
        sys.exit(code)
    vcpkg_install_dir = os.path.join(vcpkg_dir, 'installed', vcpkg_triplet)
    
    # bundle runtime dlls
    vcpkg_bin_dir = os.path.join(vcpkg_install_dir, 'bin')
    glib2_dll = os.path.join(vcpkg_bin_dir, 'glib-2.0-0.dll')

    # configure and compile lensfun
    if not os.path.exists(cmake_build):
        os.mkdir(cmake_build)
    os.chdir(cmake_build)
    # temporary hack to avoid https://stackoverflow.com/a/53547931
    # (python module not needed here anyway)
    patch_path = '../apps/CMakeLists.txt'
    with open(patch_path) as f:
        content = f.read()
    content = content.replace('IF(PYTHON)', 'IF(FALSE)')
    with open(patch_path, 'w') as f:
        f.write(content)
    cmds = [cmake + ' .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release ' +\
                    '-DBUILD_TESTS=off -DINSTALL_HELPER_SCRIPTS=off ' +\
                    '-DCMAKE_TOOLCHAIN_FILE={}/scripts/buildsystems/vcpkg.cmake '.format(vcpkg_dir) +\
                    '-DGLIB2_BASE_DIR={} -DGLIB2_DLL={} -DCMAKE_INSTALL_PREFIX=install'.format(vcpkg_install_dir, glib2_dll),
            cmake + ' --build .',
            cmake + ' --build . --target install',
            ]
    for cmd in cmds:
        print(cmd)
        code = os.system(cmd)
        if code != 0:
            sys.exit(code) 
    os.chdir(cwd)

    dll_runtime_libs = [('lensfun.dll', os.path.join(install_dir, 'bin')),
                        ('glib-2.0-0.dll', vcpkg_bin_dir),
                        # dependencies of glib
                        ('pcre.dll', vcpkg_bin_dir),
                        ('iconv-2.dll', vcpkg_bin_dir),
                        ('charset-1.dll', vcpkg_bin_dir),
                        ('intl-8.dll', vcpkg_bin_dir),
                        ]
    
    for filename, folder in dll_runtime_libs:
        src = os.path.join(folder, filename)
        dest = 'lensfunpy/' + filename
        print('copying', src, '->', dest)
        shutil.copyfile(src, dest)


def mac_lensfun_compile():
    clone_submodules()
        
    # configure and compile lensfun
    cwd = os.getcwd()
    if not os.path.exists(cmake_build):
        os.mkdir(cmake_build)
    os.chdir(cmake_build)
    install_name_dir = os.path.join(install_dir, 'lib')
    cmds = ['cmake .. -DCMAKE_BUILD_TYPE=Release ' +\
                    '-DBUILD_TESTS=off -DINSTALL_HELPER_SCRIPTS=off ' +\
                    '-DCMAKE_INSTALL_PREFIX=install ' +\
                    '-DCMAKE_INSTALL_NAME_DIR=' + install_name_dir,
            'cmake --build .',
            'cmake --build . --target install',
            ]
    for cmd in cmds:
        print(cmd)
        code = os.system(cmd)
        if code != 0:
            sys.exit(code)
    os.chdir(cwd)

def bundle_db_files():
    import glob
    db_files = 'lensfunpy/db_files'
    if not os.path.exists(db_files):
        os.makedirs(db_files)
    for path in glob.glob('external/lensfun/data/db/*.xml'):
        dest = os.path.join(db_files, os.path.basename(path))
        print('copying', path, '->', dest)
        shutil.copyfile(path, dest)

package_data = {'lensfunpy': []}

# evil hack, check cmd line for relevant commands
# custom cmdclasses didn't work out in this case
cmdline = ''.join(sys.argv[1:])
needsCompile = any(s in cmdline for s in ['install', 'bdist', 'build_ext', 'wheel', 'nosetests'])
if isWindows and needsCompile:
    windows_lensfun_compile()
    package_data['lensfunpy'].append('*.dll')

elif isMac and needsCompile:
    mac_lensfun_compile()

if any(s in cmdline for s in ['clean', 'sdist']):
    # When running sdist after a previous run of bdist or build_ext
    # then even with the 'clean' command the .egg-info folder stays.
    # This folder contains SOURCES.txt which in turn is used by sdist
    # to include package data files, but we don't want .dll's and .xml
    # files in our source distribution. Therefore, to prevent accidents,
    # we help a little...
    egg_info = 'lensfunpy.egg-info'
    print('removing', egg_info)
    shutil.rmtree(egg_info, ignore_errors=True)

if 'sdist' not in cmdline:
    # This assumes that the lensfun version from external/lensfun was used.
    # If that's not the case, the bundled files may fail to load, for example,
    # if lensfunpy was linked against an older lensfun version already on
    # the system (Linux mostly) and the database format changed in an incompatible way.
    # In that case, loading of bundled files can still be disabled
    # with Database(load_bundled=False).
    package_data['lensfunpy'].append('db_files/*.xml')
    bundle_db_files()

# Support for optional Cython line tracing
# run the following to generate a test coverage report:
# $ export LINETRACE=1
# $ python setup.py build_ext --inplace
# $ nosetests --with-coverage --cover-html --cover-package=lensfunpy
compdirectives = {}
macros = []
if (os.environ.get('LINETRACE', False)):
    compdirectives['linetrace'] = True
    macros.append(('CYTHON_TRACE', '1'))

extensions = cythonize([Extension("lensfunpy._lensfun",
              include_dirs=include_dirs,
              sources=[os.path.join('lensfunpy', '_lensfun.pyx')],
              libraries=libraries,
              library_dirs=library_dirs,
              extra_compile_args=extra_compile_args,
              extra_link_args=extra_link_args,
              define_macros=macros
             )],
             compiler_directives=compdirectives)

# make __version__ available (https://stackoverflow.com/a/16084844)
exec(open('lensfunpy/_version.py').read())

setup(
      name = 'lensfunpy',
      version = __version__,
      description = 'Lens distortion correction for Python, a wrapper for lensfun',
      long_description = open('README.rst').read(),
      author = 'Maik Riechert',
      author_email = 'maik.riechert@arcor.de',
      url = 'https://github.com/letmaik/lensfunpy',
      classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
        'Natural Language :: English',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Cython',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.4',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Operating System :: MacOS',
        'Operating System :: Microsoft :: Windows',
        'Operating System :: POSIX',
        'Operating System :: Unix',
        'Topic :: Multimedia :: Graphics',
        'Topic :: Software Development :: Libraries',
      ],
      packages = find_packages(),
      ext_modules = extensions,
      package_data = package_data,
      install_requires=['numpy']
)

[((253, 4, 253, 38), 'os.environ.get', 'os.environ.get', ({(253, 19, 253, 30): '"""LINETRACE"""', (253, 32, 253, 37): '(False)'}, {}), "('LINETRACE', False)", False, 'import os\n'), ((27, 17, 27, 58), 'os.environ.get', 'os.environ.get', ({(27, 32, 27, 44): '"""PKG_CONFIG"""', (27, 45, 27, 57): '"""pkg-config"""'}, {}), "('PKG_CONFIG', 'pkg-config')", False, 'import os\n'), ((60, 18, 60, 59), 'os.path.abspath', 'os.path.abspath', ({(60, 34, 60, 58): '"""external/lensfun/build"""'}, {}), "('external/lensfun/build')", False, 'import os\n'), ((61, 18, 61, 54), 'os.path.join', 'os.path.join', ({(61, 31, 61, 42): 'cmake_build', (61, 44, 61, 53): '"""install"""'}, {}), "(cmake_build, 'install')", False, 'import os\n'), ((69, 17, 69, 36), 'numpy.get_include', 'numpy.get_include', ({}, {}), '()', False, 'import numpy\n'), ((72, 17, 72, 45), 'os.path.abspath', 'os.path.abspath', ({(72, 33, 72, 44): '"""lensfunpy"""'}, {}), "('lensfunpy')", False, 'import os\n'), ((83, 10, 83, 21), 'os.getcwd', 'os.getcwd', ({}, {}), '()', False, 'import os\n'), ((94, 22, 94, 68), 'os.path.join', 'os.path.join', ({(94, 35, 94, 44): 'vcpkg_dir', (94, 46, 94, 67): '"""bootstrap-vcpkg.bat"""'}, {}), "(vcpkg_dir, 'bootstrap-vcpkg.bat')", False, 'import os\n'), ((95, 12, 95, 48), 'os.path.join', 'os.path.join', ({(95, 25, 95, 34): 'vcpkg_dir', (95, 36, 95, 47): '"""vcpkg.exe"""'}, {}), "(vcpkg_dir, 'vcpkg.exe')", False, 'import os\n'), ((119, 4, 119, 23), 'os.chdir', 'os.chdir', ({(119, 13, 119, 22): 'vcpkg_dir'}, {}), '(vcpkg_dir)', False, 'import os\n'), ((128, 11, 128, 62), 'os.system', 'os.system', ({(128, 21, 128, 61): "vcpkg + ' install glib:' + vcpkg_triplet"}, {}), "(vcpkg + ' install glib:' + vcpkg_triplet)", False, 'import os\n'), ((131, 24, 131, 75), 'os.path.join', 'os.path.join', ({(131, 37, 131, 46): 'vcpkg_dir', (131, 48, 131, 59): '"""installed"""', (131, 61, 131, 74): 'vcpkg_triplet'}, {}), "(vcpkg_dir, 'installed', vcpkg_triplet)", False, 'import os\n'), ((134, 20, 134, 58), 'os.path.join', 'os.path.join', ({(134, 33, 134, 50): 'vcpkg_install_dir', (134, 52, 134, 57): '"""bin"""'}, {}), "(vcpkg_install_dir, 'bin')", False, 'import os\n'), ((135, 16, 135, 61), 'os.path.join', 'os.path.join', ({(135, 29, 135, 42): 'vcpkg_bin_dir', (135, 44, 135, 60): '"""glib-2.0-0.dll"""'}, {}), "(vcpkg_bin_dir, 'glib-2.0-0.dll')", False, 'import os\n'), ((140, 4, 140, 25), 'os.chdir', 'os.chdir', ({(140, 13, 140, 24): 'cmake_build'}, {}), '(cmake_build)', False, 'import os\n'), ((161, 4, 161, 17), 'os.chdir', 'os.chdir', ({(161, 13, 161, 16): 'cwd'}, {}), '(cwd)', False, 'import os\n'), ((183, 10, 183, 21), 'os.getcwd', 'os.getcwd', ({}, {}), '()', False, 'import os\n'), ((186, 4, 186, 25), 'os.chdir', 'os.chdir', ({(186, 13, 186, 24): 'cmake_build'}, {}), '(cmake_build)', False, 'import os\n'), ((187, 23, 187, 55), 'os.path.join', 'os.path.join', ({(187, 36, 187, 47): 'install_dir', (187, 49, 187, 54): '"""lib"""'}, {}), "(install_dir, 'lib')", False, 'import os\n'), ((200, 4, 200, 17), 'os.chdir', 'os.chdir', ({(200, 13, 200, 16): 'cwd'}, {}), '(cwd)', False, 'import os\n'), ((207, 16, 207, 59), 'glob.glob', 'glob.glob', ({(207, 26, 207, 58): '"""external/lensfun/data/db/*.xml"""'}, {}), "('external/lensfun/data/db/*.xml')", False, 'import glob\n'), ((234, 4, 234, 47), 'shutil.rmtree', 'shutil.rmtree', (), '', False, 'import shutil\n'), ((29, 12, 30, 52), 'subprocess.Popen', 'subprocess.Popen', (), '', False, 'import subprocess\n'), ((63, 21, 63, 68), 'os.path.join', 'os.path.join', ({(63, 34, 63, 45): 'install_dir', (63, 47, 63, 56): '"""include"""', (63, 58, 63, 67): '"""lensfun"""'}, {}), "(install_dir, 'include', 'lensfun')", False, 'import os\n'), ((64, 21, 64, 53), 'os.path.join', 'os.path.join', ({(64, 34, 64, 45): 'install_dir', (64, 47, 64, 52): '"""lib"""'}, {}), "(install_dir, 'lib')", False, 'import os\n'), ((75, 11, 75, 55), 'os.path.exists', 'os.path.exists', ({(75, 26, 75, 54): '"""external/lensfun/README.md"""'}, {}), "('external/lensfun/README.md')", False, 'import os\n'), ((120, 11, 120, 32), 'os.path.exists', 'os.path.exists', ({(120, 26, 120, 31): 'vcpkg'}, {}), '(vcpkg)', False, 'import os\n'), ((121, 15, 121, 41), 'os.system', 'os.system', ({(121, 25, 121, 40): 'vcpkg_bootstrap'}, {}), '(vcpkg_bootstrap)', False, 'import os\n'), ((130, 8, 130, 22), 'sys.exit', 'sys.exit', ({(130, 17, 130, 21): 'code'}, {}), '(code)', False, 'import sys\n'), ((138, 11, 138, 38), 'os.path.exists', 'os.path.exists', ({(138, 26, 138, 37): 'cmake_build'}, {}), '(cmake_build)', False, 'import os\n'), ((139, 8, 139, 29), 'os.mkdir', 'os.mkdir', ({(139, 17, 139, 28): 'cmake_build'}, {}), '(cmake_build)', False, 'import os\n'), ((158, 15, 158, 29), 'os.system', 'os.system', ({(158, 25, 158, 28): 'cmd'}, {}), '(cmd)', False, 'import os\n'), ((173, 14, 173, 44), 'os.path.join', 'os.path.join', ({(173, 27, 173, 33): 'folder', (173, 35, 173, 43): 'filename'}, {}), '(folder, filename)', False, 'import os\n'), ((176, 8, 176, 34), 'shutil.copyfile', 'shutil.copyfile', ({(176, 24, 176, 27): 'src', (176, 29, 176, 33): 'dest'}, {}), '(src, dest)', False, 'import shutil\n'), ((184, 11, 184, 38), 'os.path.exists', 'os.path.exists', ({(184, 26, 184, 37): 'cmake_build'}, {}), '(cmake_build)', False, 'import os\n'), ((185, 8, 185, 29), 'os.mkdir', 'os.mkdir', ({(185, 17, 185, 28): 'cmake_build'}, {}), '(cmake_build)', False, 'import os\n'), ((197, 15, 197, 29), 'os.system', 'os.system', ({(197, 25, 197, 28): 'cmd'}, {}), '(cmd)', False, 'import os\n'), ((205, 11, 205, 35), 'os.path.exists', 'os.path.exists', ({(205, 26, 205, 34): 'db_files'}, {}), '(db_files)', False, 'import os\n'), ((206, 8, 206, 29), 'os.makedirs', 'os.makedirs', ({(206, 20, 206, 28): 'db_files'}, {}), '(db_files)', False, 'import os\n'), ((210, 8, 210, 35), 'shutil.copyfile', 'shutil.copyfile', ({(210, 24, 210, 28): 'path', (210, 30, 210, 34): 'dest'}, {}), '(path, dest)', False, 'import shutil\n'), ((298, 17, 298, 32), 'setuptools.find_packages', 'find_packages', ({}, {}), '()', False, 'from setuptools import setup, Extension, find_packages\n'), ((77, 11, 77, 51), 'os.system', 'os.system', ({(77, 21, 77, 50): '"""git submodule update --init"""'}, {}), "('git submodule update --init')", False, 'import os\n'), ((101, 15, 101, 43), 'os.path.exists', 'os.path.exists', ({(101, 30, 101, 42): 'extractcheck'}, {}), '(extractcheck)', False, 'import os\n'), ((123, 12, 123, 26), 'sys.exit', 'sys.exit', ({(123, 21, 123, 25): 'code'}, {}), '(code)', False, 'import sys\n'), ((160, 12, 160, 26), 'sys.exit', 'sys.exit', ({(160, 21, 160, 25): 'code'}, {}), '(code)', False, 'import sys\n'), ((163, 40, 163, 72), 'os.path.join', 'os.path.join', ({(163, 53, 163, 64): 'install_dir', (163, 66, 163, 71): '"""bin"""'}, {}), "(install_dir, 'bin')", False, 'import os\n'), ((199, 12, 199, 26), 'sys.exit', 'sys.exit', ({(199, 21, 199, 25): 'code'}, {}), '(code)', False, 'import sys\n'), ((208, 38, 208, 60), 'os.path.basename', 'os.path.basename', ({(208, 55, 208, 59): 'path'}, {}), '(path)', False, 'import os\n'), ((43, 34, 43, 78), 'os.environ.get', 'os.environ.get', ({(43, 49, 43, 73): '"""PKG_CONFIG_SYSROOT_DIR"""', (43, 75, 43, 77): '""""""'}, {}), "('PKG_CONFIG_SYSROOT_DIR', '')", False, 'import os\n'), ((102, 33, 102, 54), 'os.path.basename', 'os.path.basename', ({(102, 50, 102, 53): 'url'}, {}), '(url)', False, 'import os\n'), ((103, 19, 103, 39), 'os.path.exists', 'os.path.exists', ({(103, 34, 103, 38): 'path'}, {}), '(path)', False, 'import os\n'), ((111, 17, 111, 38), 'zipfile.ZipFile', 'zipfile.ZipFile', ({(111, 33, 111, 37): 'path'}, {}), '(path)', False, 'import zipfile\n'), ((115, 19, 115, 39), 'os.path.exists', 'os.path.exists', ({(115, 34, 115, 38): 'path'}, {}), '(path)', False, 'import os\n'), ((106, 20, 106, 42), 'urllib.request.urlretrieve', 'urlretrieve', ({(106, 32, 106, 35): 'url', (106, 37, 106, 41): 'path'}, {}), '(url, path)', False, 'from urllib.request import urlretrieve\n'), ((259, 23, 259, 64), 'os.path.join', 'os.path.join', ({(259, 36, 259, 47): '"""lensfunpy"""', (259, 49, 259, 63): '"""_lensfun.pyx"""'}, {}), "('lensfunpy', '_lensfun.pyx')", False, 'import os\n'), ((109, 20, 109, 42), 'urllib.request.urlretrieve', 'urlretrieve', ({(109, 32, 109, 35): 'url', (109, 37, 109, 41): 'path'}, {}), '(url, path)', False, 'from urllib.request import urlretrieve\n')]