#! /usr/bin/env python
# encoding: utf-8

import os
import sys
import platform
import subprocess
from distutils.core import setup, Command
from distutils.extension import Extension
from distutils.command.build_ext import build_ext
from distutils.version import StrictVersion, LooseVersion
from efl import __version__, __version_info__ as vers

script_path = os.path.dirname(os.path.abspath(__file__))


# python-efl version (change in efl/__init__.py)
RELEASE = __version__
VERSION = "%d.%d" % (vers[0], vers[1] if vers[2] < 99 else vers[1] + 1)

# dependencies
CYTHON_MIN_VERSION = "0.23.5"
CYTHON_BLACKLIST = ()
EFL_MIN_VER = RELEASE
ELM_MIN_VER = RELEASE

# Add git commit count for dev builds
if vers[2] == 99:
    try:
        call = subprocess.Popen(["git", "rev-list", "--count", "HEAD"],
                                stdout=subprocess.PIPE)
        out, err = call.communicate()
        count = out.decode("utf-8").strip()
        RELEASE += "a" + count
    except Exception:
        RELEASE += "a0"


# XXX: Force default visibility. See phab T504
if os.getenv("CFLAGS") is not None and "-fvisibility=" in os.environ["CFLAGS"]:
    os.environ["CFLAGS"] += " -fvisibility=default"


# === Sphinx ===
try:
    from sphinx.setup_command import BuildDoc
except ImportError:
    class BuildDoc(Command):
        description = "build docs using sphinx, that must be installed."
        version = ""
        release = ""
        user_options = []

        def initialize_options(self):
            pass

        def finalize_options(self):
            pass

        def run(self):
            print("Error: sphinx not found")


# === pkg-config ===
def pkg_config(name, require, min_vers=None):
    try:
        sys.stdout.write("Checking for " + name + ": ")

        call = subprocess.Popen(["pkg-config", "--modversion", require],
                                stdout=subprocess.PIPE)
        out, err = call.communicate()
        if call.returncode != 0:
            raise SystemExit("Did not find " + name + " with 'pkg-config'.")

        ver = out.decode("utf-8").strip()
        if min_vers is not None:
            assert (LooseVersion(ver) >= LooseVersion(min_vers)) is True

        call = subprocess.Popen(["pkg-config", "--cflags", require],
                                stdout=subprocess.PIPE)
        out, err = call.communicate()
        cflags = out.decode("utf-8").split()

        call = subprocess.Popen(["pkg-config", "--libs", require],
                                stdout=subprocess.PIPE)
        out, err = call.communicate()
        libs = out.decode("utf-8").split()

        sys.stdout.write("OK, found " + ver + "\n")

        cflags = list(set(cflags))

        return (cflags, libs)
    except (OSError, subprocess.CalledProcessError):
        raise SystemExit("Did not find " + name + " with 'pkg-config'.")
    except (AssertionError):
        raise SystemExit("%s version mismatch. Found: %s  Needed %s" % (
                         name, ver, min_vers))


# === setup.py clean_generated_files command ===
class CleanGenerated(Command):
    description = "Clean C and html files generated by Cython"
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        for lib in ("eo", "evas", "ecore", "ecore_x", "ecore_input", "ecore_con",
                    "edje", "emotion", "elementary", "ethumb", "dbus_mainloop",
                    "utils"):
            lib_path = os.path.join(script_path, "efl", lib)
            for root, dirs, files in os.walk(lib_path):
                for f in files:
                    if f.endswith((".c", ".html")) and f != "e_dbus.c":
                        self.remove(os.path.join(root, f))

    def remove(self, fullpath):
        print("removing %s" % fullpath.replace(script_path, "").lstrip('/'))
        os.remove(fullpath)


# === setup.py uninstall command ===
RECORD_FILE = "installed_files-%d.%d.txt" % sys.version_info[:2]
class Uninstall(Command):
    description = 'remove all the installed files recorded at installation time'
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def remove_entry(self, entry):
        if os.path.isfile(entry):
            try:
                print("removing file %s" % entry)
                os.unlink(entry)
            except OSError as e:
                print(e)
                return

            directory = os.path.dirname(entry)
            while os.listdir(directory) == []:
                try:
                    print("removing empty directory %s" % directory)
                    os.rmdir(directory)
                except OSError as e:
                    print(e)
                    break
                directory = os.path.dirname(directory)

    def run(self):
        if not os.path.exists(RECORD_FILE):
            print('ERROR: No %s file found!' % RECORD_FILE)
        else:
            for entry in open(RECORD_FILE).read().split():
                self.remove_entry(entry)


# === setup.py test command ===
class Test(Command):
    description = 'Run all the available unit tests using efl in build/'
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        import unittest

        sys.path.insert(0, "build/lib.%s-%s-%d.%d" % (
                            platform.system().lower(), platform.machine(),
                            sys.version_info[0], sys.version_info[1]))
        if "efl" in sys.modules:
            del sys.modules["efl"]

        loader = unittest.TestLoader()
        suite = loader.discover('./tests')
        runner = unittest.TextTestRunner(verbosity=1, buffer=True)
        result = runner.run(suite)


# === use cython or pre-generated C files ===
USE_CYTHON = False
if os.getenv("DISABLE_CYTHON") is not None:
    if os.path.exists(os.path.join(script_path, "efl/eo/efl.eo.c")):
        USE_CYTHON = False
    else:
        sys.exit(
            "You have requested to use pregenerated files with DISABLE_CYTHON\n"
            "but the files are not available!\n"
            "Unset DISABLE_CYTHON from your build environment and try again.")
elif os.getenv("ENABLE_CYTHON") is not None:
    USE_CYTHON = True
elif not os.path.exists(os.path.join(script_path, "efl/eo/efl.eo.c")):
    USE_CYTHON = True
elif os.path.exists(os.path.join(script_path, "Makefile")):
    USE_CYTHON = True


ext_modules = []
py_modules = []
packages = ["efl"]
common_cflags = [
    "-fno-var-tracking-assignments", # seems to lower the mem used during build
    "-Wno-misleading-indentation", # not needed (we don't indent the C code)
    "-Wno-deprecated-declarations", # we bind deprecated functions
    "-Wno-unused-variable", # eo_instance_from_object() is unused
    "-Wno-format-security", # some cc don't like the way cython use EINA_LOG macros
    # "-Werror", "-Wfatal-errors" # use this to stop build on first warnings
]

if set(("build", "build_ext", "install", "bdist", "sdist")) & set(sys.argv):
    sys.stdout.write("Python-EFL: %s\n" % RELEASE)

    # === Python ===
    sys.stdout.write("Checking for Python: ")
    py_ver = sys.version_info
    py_ver = "%s.%s.%s" % (py_ver[0], py_ver[1], py_ver[2])
    if sys.hexversion < 0x020600f0:
        raise SystemExit("too old. Found: %s  Need at least 2.6.0" % py_ver)
    else:
        sys.stdout.write("OK, found %s\n" % py_ver)

    # === Cython ===
    sys.stdout.write("Checking for Cython: ")
    if USE_CYTHON:
        # check if cython is installed
        try:
            from Cython.Distutils import Extension, build_ext
            from Cython.Build import cythonize
            import Cython.Compiler.Options
        except ImportError:
            raise SystemExit("not found! Needed >= %s" % (CYTHON_MIN_VERSION))

        # check min version
        if LooseVersion(Cython.__version__) < LooseVersion(CYTHON_MIN_VERSION):
            raise SystemExit("too old! Found %s  Needed %s" % (
                             Cython.__version__, CYTHON_MIN_VERSION))

        # check black-listed releases
        if Cython.__version__.startswith(CYTHON_BLACKLIST):
            raise SystemExit("found %s, it's broken! Need another release" %
                             Cython.__version__)

        sys.stdout.write("OK, found %s\n" % Cython.__version__)
        module_suffix = ".pyx"
        # Stop compilation on first error
        Cython.Compiler.Options.fast_fail = True
        # Generates HTML files with annotated source
        Cython.Compiler.Options.annotate = False
        # Set to False to disable docstrings
        Cython.Compiler.Options.docstrings = True

    else:
        sys.stdout.write("not needed, using pre-generated C files\n")
        module_suffix = ".c"

    # === Eina ===
    eina_cflags, eina_libs = pkg_config('Eina', 'eina', EFL_MIN_VER)

    # === Eo ===
    eo_cflags, eo_libs = pkg_config('Eo', 'eo', EFL_MIN_VER)
    eo_ext = Extension("efl.eo",
                       ["efl/eo/efl.eo" + module_suffix],
                       define_macros=[
                            ('EFL_BETA_API_SUPPORT', 1),
                            ('EFL_EO_API_SUPPORT', 1)
                        ],
                       include_dirs=['include/'],
                       extra_compile_args=eo_cflags + common_cflags,
                       extra_link_args=eo_libs + eina_libs
                      )
    ext_modules.append(eo_ext)

    # === Utilities ===
    utils_ext = [
        Extension("efl.utils.deprecated",
                  ["efl/utils/deprecated" + module_suffix],
                  include_dirs=['include/'],
                  extra_compile_args=eina_cflags + common_cflags,
                  extra_link_args=eina_libs),
        Extension("efl.utils.conversions",
                 ["efl/utils/conversions" + module_suffix],
                  include_dirs=['include/'],
                  extra_compile_args=eo_cflags + common_cflags,
                  extra_link_args=eo_libs + eina_libs),
        Extension("efl.utils.logger",
                  ["efl/utils/logger" + module_suffix],
                  include_dirs=['include/'],
                  extra_compile_args=eina_cflags + common_cflags,
                  extra_link_args=eina_libs),
    ]
    ext_modules.extend(utils_ext)
    py_modules.append("efl.utils.setup")
    packages.append("efl.utils")

    # === Evas ===
    evas_cflags, evas_libs = pkg_config('Evas', 'evas', EFL_MIN_VER)
    evas_ext = Extension("efl.evas",
                         ["efl/evas/efl.evas" + module_suffix],
                         define_macros=[
                            ('EFL_BETA_API_SUPPORT', 1),
                            ('EFL_EO_API_SUPPORT', 1)
                         ],
                         include_dirs=['include/'],
                         extra_compile_args=evas_cflags + common_cflags,
                         extra_link_args=evas_libs + eina_libs + eo_libs)
    ext_modules.append(evas_ext)

    # === Ecore ===
    ecore_cflags, ecore_libs = pkg_config('Ecore', 'ecore', EFL_MIN_VER)
    ecore_file_cflags, ecore_file_libs = pkg_config('EcoreFile', 'ecore-file',
                                                    EFL_MIN_VER)
    ecore_ext = Extension("efl.ecore",
                          ["efl/ecore/efl.ecore" + module_suffix],
                          include_dirs=['include/'],
                          extra_compile_args=list(set(ecore_cflags +
                                                      ecore_file_cflags +
                                                      common_cflags)),
                          extra_link_args=ecore_libs + ecore_file_libs +
                                          eina_libs + evas_libs)
    ext_modules.append(ecore_ext)

    # === Ecore Input ===
    ecore_input_cflags, ecore_input_libs = pkg_config('EcoreInput',
                                                      'ecore-input',
                                                      EFL_MIN_VER)
    ecore_input_ext = Extension("efl.ecore_input",
                            ["efl/ecore_input/efl.ecore_input" + module_suffix],
                            include_dirs=['include/'],
                            extra_compile_args=list(set(ecore_cflags +
                                                        ecore_file_cflags +
                                                        ecore_input_cflags +
                                                        common_cflags)),
                            extra_link_args=ecore_libs + ecore_file_libs +
                                            ecore_input_libs)
    ext_modules.append(ecore_input_ext)

    # === Ecore Con ===
    ecore_con_cflags, ecore_con_libs = pkg_config('EcoreCon', 'ecore-con',
                                                  EFL_MIN_VER)
    ecore_con_ext = Extension("efl.ecore_con",
                              ["efl/ecore_con/efl.ecore_con" + module_suffix],
                              include_dirs=['include/'],
                              extra_compile_args=list(set(ecore_cflags +
                                                          ecore_file_cflags +
                                                          ecore_con_cflags +
                                                          common_cflags)),
                              extra_link_args=ecore_libs + ecore_file_libs +
                                              ecore_con_libs + eina_libs)
    ext_modules.append(ecore_con_ext)

    # === Ecore X ===
    try:
        ecore_x_cflags, ecore_x_libs = pkg_config('EcoreX', 'ecore-x',
                                                  EFL_MIN_VER)
    except SystemExit:
        print("Not found, will not be built")
    else:
        ecore_x_ext = Extension("efl.ecore_x",
                                ["efl/ecore_x/efl.ecore_x" + module_suffix],
                                include_dirs=['include/'],
                                extra_compile_args=list(set(ecore_cflags +
                                                            ecore_file_cflags +
                                                            ecore_x_cflags +
                                                            ecore_input_cflags +
                                                            common_cflags)),
                                extra_link_args=ecore_libs + ecore_file_libs +
                                                ecore_x_libs + ecore_input_libs +
                                                eina_libs + evas_libs)
        ext_modules.append(ecore_x_ext)

    # === Ethumb ===
    ethumb_cflags, ethumb_libs = pkg_config('Ethumb', 'ethumb', EFL_MIN_VER)
    ethumb_ext = Extension("efl.ethumb",
                           ["efl/ethumb/efl.ethumb" + module_suffix],
                           include_dirs=['include/'],
                           extra_compile_args=ethumb_cflags + common_cflags,
                           extra_link_args=ethumb_libs + eina_libs)
    ext_modules.append(ethumb_ext)

    # === Ethumb Client ===
    ethumb_client_cflags, ethumb_client_libs = pkg_config('Ethumb_Client',
                                                'ethumb_client', EFL_MIN_VER)
    ethumb_client_ext = Extension("efl.ethumb_client",
                                  ["efl/ethumb/efl.ethumb_client" + module_suffix],
                                  include_dirs=['include/'],
                                  extra_compile_args=ethumb_client_cflags +
                                                     common_cflags,
                                  extra_link_args=ethumb_client_libs + eina_libs)
    ext_modules.append(ethumb_client_ext)

    # === Edje ===
    edje_cflags, edje_libs = pkg_config('Edje', 'edje', EFL_MIN_VER)
    edje_ext = Extension("efl.edje",
                         ["efl/edje/efl.edje" + module_suffix],
                         include_dirs=['include/'],
                         extra_compile_args=edje_cflags + common_cflags,
                         extra_link_args=edje_libs + eina_libs + evas_libs)
    ext_modules.append(edje_ext)

    # === Edje_Edit ===
    edje_edit_ext = Extension("efl.edje_edit",
                              ["efl/edje_edit/efl.edje_edit" + module_suffix],
                              define_macros=[('EDJE_EDIT_IS_UNSTABLE_AND_I_KNOW_ABOUT_IT', None)],
                              include_dirs=['include/'],
                              extra_compile_args=edje_cflags + common_cflags,
                              extra_link_args=edje_libs + eina_libs + evas_libs)
    ext_modules.append(edje_edit_ext)

    # === Emotion ===
    emotion_cflags, emotion_libs = pkg_config('Emotion', 'emotion', EFL_MIN_VER)
    emotion_ext = Extension("efl.emotion",
                            ["efl/emotion/efl.emotion" + module_suffix],
                            include_dirs=['include/'],
                            extra_compile_args=emotion_cflags + common_cflags,
                            extra_link_args=emotion_libs + eina_libs + evas_libs)
    ext_modules.append(emotion_ext)

    # === dbus mainloop integration ===
    dbus_cflags, dbus_libs = pkg_config('DBus', 'dbus-python', "0.83.0")
    dbus_ml_ext = Extension("efl.dbus_mainloop",
                            ["efl/dbus_mainloop/efl.dbus_mainloop" + module_suffix,
                             "efl/dbus_mainloop/e_dbus.c"],
                            extra_compile_args=list(set(dbus_cflags +
                                                        ecore_cflags +
                                                        common_cflags)),
                            extra_link_args=dbus_libs + ecore_libs)
    ext_modules.append(dbus_ml_ext)

    # === Elementary ===
    elm_cflags, elm_libs = pkg_config('Elementary', 'elementary', ELM_MIN_VER)
    e = Extension("efl.elementary.__init__",
                  ["efl/elementary/__init__" + module_suffix],
                  define_macros=[
                    ('EFL_BETA_API_SUPPORT', 1),
                    ('EFL_EO_API_SUPPORT', 1)
                  ],
                  include_dirs=["include/"],
                  extra_compile_args=elm_cflags + common_cflags,
                  extra_link_args=elm_libs + eina_libs + eo_libs + evas_libs)
    ext_modules.append(e)

    packages.append("efl.elementary")

    # Cythonize all the external modules (if needed)
    if USE_CYTHON:
        ext_modules = cythonize(ext_modules,
                                include_path=["include"],
                                compiler_directives={
                                    #"c_string_type": "unicode",
                                    #"c_string_encoding": "utf-8",
                                    "embedsignature": True,
                                    "binding": True,
                                    "language_level": 2,
                                })


setup(
    name="python-efl",
    fullname="Python bindings for Enlightenment Foundation Libraries",
    description="Python bindings for Enlightenment Foundation Libraries",
    long_description=open(os.path.join(script_path, 'README')).read(),
    version=RELEASE,
    author='Davide Andreoli, Kai Huuhko, and others',
    author_email="dave@gurumeditation.it, kai.huuhko@gmail.com",
    contact="Enlightenment developer mailing list",
    contact_email="enlightenment-devel@lists.sourceforge.net",
    url="http://www.enlightenment.org",
    license="GNU Lesser General Public License (LGPL)",
    keywords="efl wrapper binding enlightenment eo evas ecore edje emotion elementary ethumb",
    classifiers=[
        'Development Status :: 5 - Production/Stable',
        'Environment :: X11 Applications',
        'Environment :: Console :: Framebuffer',
        'Intended Audience :: End Users/Desktop',
        'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
        'Operating System :: POSIX',
        'Programming Language :: C',
        'Programming Language :: Cython',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 3',
        'Topic :: Software Development :: Libraries :: Python Modules',
        'Topic :: Software Development :: User Interfaces',
        'Topic :: Software Development :: Widget Sets',
    ],
    cmdclass={
        'test': Test,
        'build_doc': BuildDoc,
        'clean_generated_files': CleanGenerated,
        'uninstall': Uninstall,
        'build_ext': build_ext,
    },
    command_options={
        'build_doc': {
            'version': ('setup.py', VERSION),
            'release': ('setup.py', RELEASE),
        },
        'install': {
            'record': ('setup.py', RECORD_FILE),
        },
    },
    packages=packages,
    ext_modules=ext_modules,
    py_modules=py_modules,
)
