"""
Utility functions for pywbem testing
"""

from __future__ import absolute_import

import sys
import os
from packaging import version
import pytest
import ply


def skip_if_moftab_regenerated():
    """
    Skip the testcase if we run against an installed version of pywbem (as
    indicated by the TEST_INSTALLED environment variable), and the testcase
    would re-generate the MOF parsing table files.

    This function should be used by test cases that parse MOF files, so the
    test case is skipped in that case.

    Background:

    Pywbem uses the `ply` package to parse CIM MOF files and part of a pywbem
    installation are parsing table files named mofparsetab.py and moflextab.py
    that are generated by `ply`. Before pywbem 0.14.5, the version of `ply`
    module that was used to build the pywbem distribution archive needed to be
    compatible with the version of `ply` installed in the Python environment,
    otherwise `ply` attempted to re-generate these parse table files in the
    `pywbem` installation directory. Thus, if the `pywbem` installation
    directory is in the system Python, a normal user will typically encounter
    a permission denied error.

    If the installed version of `pywbem` is 0.14.5 or higher, it has tolerance
    against mismatches between these `ply` versions, by having `ply`
    re-generate the parsing tables in memory if needed, but no longer writing
    them out to the pywbem installation directory.
    """

    pywbem = import_installed('pywbem')  # noqa: E402

    try:
        # If you cannot write to the directory you can't make these files,
        # thus they don't exist and you cannot import them.
        from pywbem import mofparsetab, moflextab
    except ImportError:
        pytest.skip("We have no mofparsetab or moflextab to import!")

    test_installed = os.getenv('TEST_INSTALLED', False)

    pywbem_not_tolerant = version.parse(pywbem.__version__) <= \
        version.parse('0.14.4')  # This causes 0.14.5.devN to be tolerant
    mofparsetab_mismatch = version.parse(mofparsetab._tabversion) != \
        version.parse(ply.__version__)
    moflextab_mismatch = version.parse(moflextab._tabversion) != \
        version.parse(ply.__version__)

    if test_installed and pywbem_not_tolerant and \
            (mofparsetab_mismatch or moflextab_mismatch):
        pytest.skip("The installed pywbem version {0} does not tolerate ply "
                    "version mismatches, and there is a ply version mismatch: "
                    "ply: {1}, mofparsetab: {2}, moflextab: {3}".
                    format(pywbem.__version__, ply.__version__,
                           mofparsetab._tabversion, moflextab._tabversion))


def import_installed(module_name):
    """
    Import a Python module/package, controlling whether it is loaded from the
    normal Python module search path, or from an installed version (excluding
    the module in the current directory).

    The TEST_INSTALLED environment variable controls this as follows:

      * If not set or empty, the normal Python module search path is used.
        Because that search path contains the current directory in front of the
        list, this will cause a module directory in the current directory to
        have precedence over any installed versions of the module.

      * If non-empty, the current directory is removed from the Python module
        search path, and an installed version of the module is thus used, even
        when a module directory exists in the current directory. This can be
        used for testing an OS-installed version of the module.

    Example usage, e.g. in a pywbem test program::

        from ...utils import import_installed
        pywbem = import_installed('pywbem')  # noqa: E402
        from pywbem import ...

    The number of dots in `from ..utils` depends on where the test program
    containing this code is located, relative to the tests/utils.py file.
    """
    test_installed = os.getenv('TEST_INSTALLED', False)
    if test_installed:

        # Remove '' directory.
        dirpath = ''
        try:
            ix = sys.path.index(dirpath)
        except ValueError:
            ix = None
        if ix is not None:
            if test_installed == 'DEBUG':
                print("Debug: Removing {0} at index {1} from module search "
                      "path".format(dirpath, ix))
            del sys.path[ix]

        # Move CWD to end. Reason is that when testing with an editable
        # installation, the CWD is needed, but when testing with a non-editable
        # installation, the package should not be found inthe CWD.
        # Note that somehow the CWD gets inserted at the begin of the search
        # path every time, so we need a loop.
        dirpath = os.getcwd()
        while True:
            try:
                ix = sys.path.index(dirpath)
            except ValueError:
                if test_installed == 'DEBUG':
                    print("Debug: Appending {0} to end of module search "
                          "path".format(dirpath))
                sys.path.append(dirpath)
                break
            if ix == len(sys.path) - 1:
                # it exists once at the end
                break
            if test_installed == 'DEBUG':
                print("Debug: Removing {0} at index {1} from module search "
                      "path".format(dirpath, ix))
            del sys.path[ix]

    if module_name not in sys.modules:
        module = __import__(module_name, level=0)  # only absolute imports
        if test_installed == 'DEBUG':
            print("Debug: {0} module newly loaded from: {1}".
                  format(module_name, module.__file__))
    else:
        module = sys.modules[module_name]
        if test_installed == 'DEBUG':
            print("Debug: {0} module was already loaded from: {1}".
                  format(module_name, module.__file__))
    return module
