import sys
import warnings

from ...config import kc

__all__ = ['get_minimizer']

AVAILABLE_MINIMIZERS = dict()

_MINIMIZER_NAME_ALIASES = dict()

_catch_error_class = ImportError
if sys.version_info >= (3, 6):
    # python version 3.6+ throws a different exception type on import fail...
    _catch_error_class = ModuleNotFoundError

try:
    from .scipy_optimize_minimizer import MinimizerScipyOptimize
    __all__.append('MinimizerScipyOptimize')
    AVAILABLE_MINIMIZERS.update({
        'scipy': MinimizerScipyOptimize,
    })
    _MINIMIZER_NAME_ALIASES['scipy.optimize'] = 'scipy'
except _catch_error_class:
    pass

try:
    from .iminuit_minimizer import MinimizerIMinuit
    __all__.append('MinimizerIMinuit')
    AVAILABLE_MINIMIZERS.update({
        'iminuit': MinimizerIMinuit,
    })
except _catch_error_class:
    warnings.warn(
        "iminuit is not installed. While not strictly needed its use is "
        "heavily recommended for better performance and convergence."
    )
except SyntaxError:  # Newer versions of iminuit do not support Python 2.
    pass

try:
    from .root_tminuit_minimizer import MinimizerROOTTMinuit
    __all__.append('MinimizerROOTTMinuit')
    AVAILABLE_MINIMIZERS.update({
        'root.tminuit': MinimizerROOTTMinuit,
    })
    _MINIMIZER_NAME_ALIASES['minuit'] = 'root.tminuit'
    _MINIMIZER_NAME_ALIASES['root'] = 'root.tminuit'
except (_catch_error_class, ImportError):  # Python 2.7 ROOT bindings cause ImportError in Python 3.6
    pass

# raise if no minimizers can be imported
if not AVAILABLE_MINIMIZERS:
    raise RuntimeError("Fatal error: no minimizers found! Please ensure that "
                       "at least one of the following Python packages is installed: "
                       "['scipy', 'iminuit', 'ROOT']")


def get_minimizer(minimizer_spec=None):
    """Creates a MinimizerBase object from a given minimizer name.

    :param minimizer_spec: Name of the minimizer to return.
    :type minimizer_spec: str or None
    :return: MinimizerBase-derived kafe2 minimizer object
    """
    global AVAILABLE_MINIMIZERS
    # for 'None', return the default minimizer
    if minimizer_spec is None:
        # go through the default minimizers in the order specified in config
        _minimizer_specs = kc('core', 'minimizers', 'default_minimizer_list')

        # try every spec until a minimizer is found
        for _minimizer_spec in _minimizer_specs:
            _minimizer_spec = _minimizer_spec.lower()
            _minimizer_spec = _MINIMIZER_NAME_ALIASES.get(_minimizer_spec, _minimizer_spec)
            _minimizer = AVAILABLE_MINIMIZERS.get(_minimizer_spec, None)
            if _minimizer is not None:
                return _minimizer

        raise ValueError(
            "Could not find any minimizer in default list: {}! "
            "Available: {}".format(_minimizer_specs, list(AVAILABLE_MINIMIZERS.keys())))
    _minimizer_spec = minimizer_spec.lower()
    _minimizer_spec = _MINIMIZER_NAME_ALIASES.get(_minimizer_spec, _minimizer_spec)
    _minimizer = AVAILABLE_MINIMIZERS.get(_minimizer_spec, None)
    if _minimizer is not None:
        return _minimizer

    raise ValueError("Unknown minimizer '{}'! "
                     "Available: {}".format(minimizer_spec, list(AVAILABLE_MINIMIZERS.keys())))
