"""
To update the scan Cython code in Theano you must
- update the version in this file and scan_perform.py
- call "cd theano/scan_module/; cython scan_perform.pyx; patch scan_perform.c numpy_api_changes.diff"

"""


import errno
import logging
import os
import sys
import warnings

import numpy as np

import theano
from theano import config
from theano.compat import reload
from theano.gof.compilelock import get_lock, release_lock
from theano.gof import cmodule


_logger = logging.getLogger("theano.scan_module.scan_perform")


version = 0.296  # must match constant returned in function get_version()

need_reload = False


def try_import():
    global scan_perform
    sys.path[0:0] = [config.compiledir]
    import scan_perform

    del sys.path[0]


def try_reload():
    sys.path[0:0] = [config.compiledir]
    reload(scan_perform)
    del sys.path[0]


try:
    try_import()
    need_reload = True
    if version != getattr(scan_perform, "_version", None):
        raise ImportError()
except ImportError:
    get_lock()
    try:
        # Maybe someone else already finished compiling it while we were
        # waiting for the lock?
        try:
            if need_reload:
                # The module was successfully imported earlier: we need to
                # reload it to check if the version was updated.
                try_reload()
            else:
                try_import()
                need_reload = True
            if version != getattr(scan_perform, "_version", None):
                raise ImportError()
        except ImportError:
            if not theano.config.cxx:
                raise ImportError("no c compiler, can't compile cython code")
            _logger.info("Compiling C code for scan")
            dirname = "scan_perform"
            cfile = os.path.join(
                theano.__path__[0], "scan_module", "c_code", "scan_perform.c"
            )
            if not os.path.exists(cfile):
                # This can happen in not normal case. We just
                # disable the cython code. If we are here the user
                # didn't disable the compiler, so print a warning.
                warnings.warn(
                    "The file scan_perform.c is not available. This do"
                    "not happen normally. You are probably in a strange"
                    "setup. This mean Theano can not use the cython code for "
                    "scan. If you"
                    "want to remove this warning, use the Theano flag"
                    "'cxx=' (set to an empty string) to disable all c"
                    "code generation."
                )
                raise ImportError("The file lazylinker_c.c is not available.")

            with open(cfile) as f:
                code = f.read()
            loc = os.path.join(config.compiledir, dirname)
            if not os.path.exists(loc):
                try:
                    os.mkdir(loc)
                except OSError as e:
                    assert e.errno == errno.EEXIST
                    assert os.path.exists(loc)

            preargs = ["-fwrapv", "-O2", "-fno-strict-aliasing"]
            preargs += cmodule.GCC_compiler.compile_args()
            # Cython 19.1 always use the old NumPy interface.  So we
            # need to manually modify the .c file to get it compiled
            # by Theano. As by default, we tell NumPy to don't import
            # the old interface.
            if False:
                # During scan cython development, it is helpful to keep the old interface, to don't manually edit the c file each time.
                preargs.remove("-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION")
            else:
                numpy_ver = [int(n) for n in np.__version__.split(".")[:2]]
                # Add add some macro to lower the number of edit
                # needed to the c file.
                if bool(numpy_ver >= [1, 7]):
                    # Needed when we disable the old API, as cython
                    # use the old interface
                    preargs.append("-DNPY_ENSUREARRAY=NPY_ARRAY_ENSUREARRAY")
                    preargs.append("-DNPY_ENSURECOPY=NPY_ARRAY_ENSURECOPY")
                    preargs.append("-DNPY_ALIGNED=NPY_ARRAY_ALIGNED")
                    preargs.append("-DNPY_WRITEABLE=NPY_ARRAY_WRITEABLE")
                    preargs.append("-DNPY_UPDATE_ALL=NPY_ARRAY_UPDATE_ALL")
                    preargs.append("-DNPY_C_CONTIGUOUS=NPY_ARRAY_C_CONTIGUOUS")
                    preargs.append("-DNPY_F_CONTIGUOUS=NPY_ARRAY_F_CONTIGUOUS")

            cmodule.GCC_compiler.compile_str(
                dirname, code, location=loc, preargs=preargs, hide_symbols=False
            )
            # Save version into the __init__.py file.
            init_py = os.path.join(loc, "__init__.py")
            with open(init_py, "w") as f:
                f.write("_version = %s\n" % version)
            # If we just compiled the module for the first time, then it was
            # imported at the same time: we need to make sure we do not
            # reload the now outdated __init__.pyc below.
            init_pyc = os.path.join(loc, "__init__.pyc")
            if os.path.isfile(init_pyc):
                os.remove(init_pyc)
            try_import()

            try_reload()
            from scan_perform import scan_perform as scan_c

            assert scan_perform._version == scan_c.get_version()
            _logger.info("New version %s", scan_perform._version)
    finally:
        # Release lock on compilation directory.
        release_lock()

# This is caused as cython use the old NumPy C-API but we use the new one.
# To fix it completly, we would need to modify Cython to use the new API.
with warnings.catch_warnings():
    warnings.filterwarnings("ignore", message="numpy.ndarray size changed")
    from scan_perform.scan_perform import perform, get_version  # noqa: F401
assert version == get_version()
