#! /usr/bin/env python
"""
Utility functions for loading components for The Landlab.
"""

import os
from landlab.framework.interfaces import BmiBase


_COMPONENT_PATH = [
    os.path.join(os.path.dirname(__file__), '..', 'components'),
]

try:
    paths = os.environ['LANDLAB_PATH'].split(os.pathsep)
except KeyError:
    pass
else:
    _COMPONENT_PATH = paths + _COMPONENT_PATH


def iscomponent(value, cls):
    """
    Check if *value* is a component for The Landlab. *value* is a component
    if it implements the *cls* or it is an instance of *cls*.

    Returns ``True`` if *value* is a component, otherwise ``False``.
    """
    try:
        return (cls in value.__implements__ or
                cls.__name__ in value.__implements__ or
                isinstance(value, cls))
    except AttributeError:
        return False


def load_components_from_dir(path, cls):
    """
    Look for components for Landlab in *path*. Identify components as being an
    instance of *cls*. Returns a dictionary of discovered component names as
    keys and component classes as values.
    """
    import sys
    import imp

    components = {}

    sys.path.insert(0, path)
    cwd = os.getcwd()

    os.chdir(path)
    for file_name in os.listdir('.'):
        if os.path.isfile(file_name) and file_name.endswith('.py'):
            (mod_name, _) = os.path.splitext(file_name)
            mod = imp.load_module(mod_name, *imp.find_module(mod_name))
            for (name, value) in mod.__dict__.items():
                if iscomponent(value, cls):
                    components[name] = value

    os.chdir(cwd)
    sys.path.pop(0)

    return components


def load_components(cls, paths=None):
    """
    Load components from a series of directories.

    Components found earlier in the search path order override those
    discovered later. Use the *paths* keyword to specify a list of paths to
    search for components.

    .. seealso::

        :func:`load_components_from_dir`
    """
    if not paths:
        paths = _COMPONENT_PATH

    components = {}
    for path in paths[::-1]:
        components.update(load_components_from_dir(path, cls))
    return components


def load_landlab_components(paths=None):
    """
    Load components for The Landlab. These are classes that implement BmiBase.
    See :func:`load_components_from_dir` for the meaning of *paths* keyword.

    .. seealso::

        :func:`load_components_from_dir`
    """
    return load_components(BmiBase, paths=paths)
