#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""setup.py file for @PYPACKAGE_NAME@

"""
from setuptools import find_packages
from numpy.distutils.core import setup, Extension
from numpy.distutils.misc_util import Configuration
from distutils.spawn import find_executable

import os
import re
import glob
import fnmatch

# tools to deal with fortran dependencies.
import sys
sys.path.append('@CMAKE_BINARY_DIR@/')
import sort_f90

# --- Check if c++ interface is enabled ---
enable_cpp = "@USE_CXX@"
if enable_cpp:
    swig_executable = find_executable("@SWIG_EXECUTABLE@")


def parseCMakeVar(var):
    """post-process cmake list-like variable

    Example::

        a = parseCMakeVar("var1;var2;var3;")
        # --> a = ['var', 'var2', 'var3']
    """
    if var != "":
        res = list(set(var.split(';')))
        # list/set stuff to avoid duplicates
        # remove empty strings to avoid '-I -I' things leading to bugs
        if res.count(''):
            res.remove('')
        return res
    else:
        return []


def parseCMakeDefines(var):
    """post-process cmake variables, corresponding
    to pre-processor definitions.

    Example::

        res = parseCMakeDefines("-DVAR=VAL1:-DVAR2=VAL2:-DVAR3=VAL3")
        # --> res =  [('VAR', 'VAL1'), ('VAR2', 'VAL2'), ('VAR3', 'VAL3')]
    """
    defines = parseCMakeVar(var)
    if defines is None:
        return None
    # regex to match compiler defines like -DMACRO_NAME or
    # -DMACRO_NAME = MACRO_VALUE
    p = re.compile('\s*(?:-D)?\s*(\w+)(?:\s*=\s*(\w+))?\s*')

    res = list()
    for d in defines:
        m = p.match(d)
        if m:
            res.append(m.group(1,2))
        else:
            print "\tWarning: Could extract cmake define from '", d, "'."
    return res

# --- external libraries to be linked with ---
hysop_link_libraries = parseCMakeVar("@HYSOP_LINK_LIBRARIES@")
#hysop_link_libraries_names = set([])
#hysop_link_libraries_dirs = set([])
# use set to avoid dupl.
#link_list = ""
# for lib in hysop_link_libraries:
#     hysop_link_libraries_names.add(os.path.basename(lib))
#     hysop_link_libraries_dirs.add(os.path.dirname(lib))
#     link_list += lib + " "
# hysop_link_libraries_names = list(hysop_link_libraries_names)
# hysop_link_libraries_dirs = list(hysop_link_libraries_dirs)


def create_fortran_extension(name, pyf_file=None, src_dirs=None, sources=None,
                             libdir=None, libs=None, debug_mode=0):
    """Create a new f2py module from fortran files

    Parameters
    ----------
    name: string
        python module name
    pyf_file: string, optional
        f2py interface file (absolute path)
    src_dirs: list, optional
         all directories containing fortran sources (absolute path)
         that must be taken into account. Warning: dir. scan is not
         recursive.
    sources: list, optional
         all fortran files that must be taken into account
    libs: list of strings, optional
         names of all libraries to be linked with to build f2py module
         --> for -l option
    libdir: list of strings, optional
         list of directories, absolute path to libraries listed in libs
         --> for -L... option
    debug_mode: boolean, optional
         if true enable f2py debug mode, default=False

    Notes
    -----
    * either sources or src_dirs is required but both
    can be used simultaneously.
    * only .f90 and .f95 files are taken into account.
    """

    # --- Collect list of fortran files ---
    if sources is None:
        sources = []
        assert src_dirs is not None

    for sdir in src_dirs:
        sources += glob.glob(os.path.join(sdir, '*.f95'))
        sources += glob.glob(os.path.join(sdir, '*.f90'))
    # Reorder source list with fortran modules
    # dependencies. It seems that this is not taken into
    # account in f2py or distutils.
    if pyf_file is not None:
        sources.append(pyf_file)
    sources = sort_f90.sort(sources)
    # --- set f2py options ---
    f2py_options = ['--no-lower', '--no-wrap-functions']
    options = []
    if debug_mode == 0:
        options.append(('F2PY_REPORT_ON_ARRAY_COPY', '1'))
        if os.uname()[0] == 'Linux':
            options.append(('F2PY_REPORT_ATEXIT', '1'))

    # --- set include dir ---
    inc_dir = parseCMakeVar("@MPI_Fortran_INCLUDE_PATH@")
    inc_dir.append('@CMAKE_Fortran_MODULE_DIRECTORY@')
    # --- set compilation flags ---
    fortran_flags = ['@Fortran_FLAGS@']
    #includes = parseCMakeVar("@FORTRAN_INCLUDE_DIRS@")
    #if(includes != None):
        # for exti in includes:
            #i nc_dir.append(exti)
    #libs += hysop_link_libraries_names
    #libdir += hysop_link_libraries_dirs
    # we trust cmake for external libraries and
    # add them to linker, without using libraries option
    extra_link_args = hysop_link_libraries
    ext_fort = Extension(name=name,
                         sources=sources,
                         f2py_options=f2py_options,
                         include_dirs=inc_dir,
                         define_macros=options,
                         library_dirs=libdir,
                         libraries=libs,
                         extra_f90_compile_args=fortran_flags,
                         extra_link_args=extra_link_args
                         )
    return ext_fort


def create_swig_extension(name, inc_dirs, src_dirs=None, sources=None):
    """Create a python module from C++ files, using swig

    Parameters
    ----------
    name: string
        module name
    inc_dirs: list
        list of directories for includes (-I ...), absolute path
        containing headers (.hpp) and swig files (.i)
    sources: list, optional
         all c++ files that must be taken into account
    src_dirs: list, optional
         all directories containing fortran sources (absolute path)
         that must be taken into account. Warning: no recursive scan
         in src_dirs.

    Notes
    -----
    * either sources, src_dirs or inc_dirs is required but all
    can be used simultaneously.
    * only .cpp files are taken into account.
    * main config file for swig is assumed to be
      name.i in CMAKE_SOURCE_DIR/swig/

    """
    swig_dir = os.path.join('@CMAKE_SOURCE_DIR@', 'swig')
    swig_config_file = os.path.join(swig_dir, name + '.i')

    include_dirs = set(inc_dirs)
    if sources is None:
        sources = []
        assert src_dirs is not None or inc_dirs is not None
    if(src_dirs is None):
        assert(inc_dirs is not None)
        for idir in inc_dirs:
            #sources += glob.glob(os.path.join(idir, '**/*.cpp'), recursive=True)
            for root, dirnames, filenames in os.walk(idir):
                for filename in fnmatch.filter(filenames, '*.cpp'):
                    sources.append(os.path.join(root, filename))
    else:
        for sdir in src_dirs:
            sources += glob.glob(os.path.join(sdir, '*.cpp'))

    sources.insert(0, swig_config_file)
    include_dirs = list(include_dirs)

    name = 'hysop._' + name
    swig_opts = ['-I' + swig_dir,
                 '-O', '-Wextra', '-Werror',
                 '-c++', '-extranative', '-safecstrings']

    extern_includes = parseCMakeVar("@CXX_EXT_INCLUDES@")
    if(extern_includes is not None):
        for exti in extern_includes:
            include_dirs.append(exti)

    libraries = parseCMakeVar("@CXX_EXT_LIBS@")
    library_dirs = parseCMakeVar("@CXX_EXT_LIB_DIRS@")
    extra_compile_args = parseCMakeVar("@CXX_FLAGS@")
    extra_link_args = parseCMakeVar("@CXX_LINKER_FLAGS@")
    define_macros = parseCMakeDefines("@CXX_EXTRA_DEFINES@")
    swig_ext = Extension(name, sources=sources, language='c++',
                         swig_opts=swig_opts,
                         include_dirs=include_dirs,
                         library_dirs=library_dirs,
                         libraries=libraries,
                         define_macros=define_macros,
                         runtime_library_dirs=library_dirs,
                         extra_compile_args=extra_compile_args,
                         extra_link_args=extra_link_args)

    return swig_ext


# ------------ Set list of packages required to build the module -------------
# List of modules (directories) to be included
with_test = "@WITH_TESTS@" is "ON"
if with_test:
    packages = find_packages(exclude=["*gpu*"], where="@CMAKE_SOURCE_DIR@")
else:
    packages = find_packages(exclude=["*tests*", "*gpu*"],
                             where="@CMAKE_SOURCE_DIR@")

if "@WITH_GPU@" is "ON":
    packages.append('hysop.gpu')
    if with_test:
        packages.append('hysop.gpu.tests')

# Enable this to get debug info
DISTUTILS_DEBUG = 1

# ------------ Extensions (f2py, cython, ...) setup ------------

ext_modules = []

# Check if fortran interface is enabled
enable_fortran = "@USE_FORTRAN@"
ext = {}

if enable_fortran is "ON":
    fortran_root = \
        '@CMAKE_SOURCE_DIR@/hysop'
    hysop_libdir = []  ##### ['@CMAKE_BINARY_DIR@/src']
    #####hysoplib = ['@HYSOP_LIBRARY_NAME@']
    f2py_options = ['--no-lower']
    fortran_src = set([])
    # -- list of directories which contains fortran sources --
    # path must be relative to hysop package directory
    subdirs = ['common_f', ]

    ####fortran_src.add('f2py/parameters.f90')
    # -- fftw fortran sources --
    withfftw = "@WITH_FFTW@"
    if withfftw is "ON":
        #        fortran_src.add('f2py/fftw2py.f90')
        subdirs.append(os.path.join('numerics/fftw_f'))
        #fortran_src.add('f2py/fftw2py.f90')
        fftwdir = '@FFTWLIB@'
        #hysoplib.append('fftw3')
        #hysoplib.append('fftw3_mpi')
        hysop_libdir.append(fftwdir)

    # -- scales sources --
    withscales = '@WITH_SCALES@'
    if withscales is "ON":
        fortran_src.add('f2py/scales2py.f90')

    # -- set full path to fortran sources --
    fortran_src = list(fortran_src)
    for i in xrange(len(fortran_src)):
        fortran_src[i] = os.path.join(fortran_root, fortran_src[i])

    precision_file = os.path.join('@GENERATED_FORTRAN_FILES_DIR@',
                                  'precision.f95')
    fortran_src.insert(0, precision_file)

    # === Draft for future implementation of fortran interface ===
    # -- f2py signature file --
    pyf_file = os.path.join('@GENERATED_FORTRAN_FILES_DIR@', 'f2hysop.pyf')
    # change from relative path in subdirs to absolute path in num_dirs
    num_dirs = []
    for sd in subdirs:
        num_dirs.append(os.path.join(fortran_root, sd))

    num_dirs.append('@GENERATED_FORTRAN_FILES_DIR@')
    # create python interface to fortran sources
    # For the moment, it includes the 'old' interface
    # to scales and fftw (in sources) and the new
    # interface, in src_dirs
    ext['f2hysop'] = create_fortran_extension(
        name='hysop.f2hysop',
        sources=fortran_src,
        #libdir=hysop_libdir,
        #libs=hysoplib,
        pyf_file=pyf_file,
        src_dirs=num_dirs)

    for ex in ext:
        ext_modules.append(ext[ex])

# --- C++ files and swig interface --

if enable_cpp is "ON":
    # path to .i files
    swig_include_dirs = [os.path.join('@CMAKE_SOURCE_DIR@','swig')]

    cpp_include_dirs = ['src/fftw','src/hysop++/src']
    for d in cpp_include_dirs:
        swig_include_dirs.append(os.path.join('@CMAKE_SOURCE_DIR@', d))

    ext = {}
    cpp2hysop = "@CPP_2_HYSOP@"
    ext[cpp2hysop] = create_swig_extension(name=cpp2hysop,
                                           inc_dirs=swig_include_dirs)
    for ex in ext:
        ext_modules.append(ext[ex])

data_files = []
if "@WITH_GPU@" is "ON":
    cl_src_dirs = ["cl_src", "cl_src/kernels",
                   "cl_src/advection", "cl_src/remeshing"]
    for cl_dir in cl_src_dirs:
        data_files.append(
            ('./hysop/gpu/' + cl_dir,
             ['@CMAKE_SOURCE_DIR@/hysop/gpu/' + cl_dir + '/'
              + cl_file
              for cl_file in os.listdir(
                  '@CMAKE_SOURCE_DIR@/hysop/gpu/' + cl_dir + '/')
              if cl_file[0] != '.' and cl_file[0] != '#' and cl_file[-3:] == '.cl']))

descr = 'Hybrid Computation with Particles.'
authors = 'G.H Cottet, J.M Etancelin, J.B Keck, C.Mimeau, F.Pérignon, C. Picard'
# authors = 'HySoP development team'
config = Configuration(
    name='@PACKAGE_NAME@',
    version='@HYSOP_VERSION@',
    description=descr,
    author=authors,
    author_email='hysop-members@lists.forge.imag.fr',
    url='https://forge.imag.fr/projects/hysop/',
    license='GNU General Public License (GPLv3)',
    package_dir={'': '@CMAKE_SOURCE_DIR@'},
    ext_modules=ext_modules,
    packages=packages,
    data_files=data_files,
)

setup(**config.todict())
