# Copyright(c) 2016 Nippon Telegraph and Telephone Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Utilities and helper functions."""

import contextlib
import functools
import inspect
import pyclbr
import shutil
import sys
import tempfile

from oslo_concurrency import lockutils
from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_utils import importutils

import masakarimonitors.conf
from masakarimonitors.i18n import _
from masakarimonitors import privsep


CONF = masakarimonitors.conf.CONF

LOG = logging.getLogger(__name__)


def monkey_patch():
    """monkey_patch function.

    If the CONF.monkey_patch set as True,
    this function patches a decorator
    for all functions in specified modules.
    You can set decorators for each modules
    using CONF.monkey_patch_modules.
    The format is "Module path:Decorator function".
    name - name of the function
    function - object of the function
    """
    # If CONF.monkey_patch is not True, this function do nothing.
    if not CONF.monkey_patch:
        return

    def is_method(obj):
        return inspect.ismethod(obj) or inspect.isfunction(obj)
    # Get list of modules and decorators
    for module_and_decorator in CONF.monkey_patch_modules:
        md_value = module_and_decorator.split(':')
        if len(md_value) != 2:
            msg = _("'monkey_patch_modules' config option is not configured "
                    "correctly")
            raise Exception(msg)
        module, decorator_name = md_value
        # import decorator function
        decorator = importutils.import_class(decorator_name)
        __import__(module)
        # Retrieve module information using pyclbr
        module_data = pyclbr.readmodule_ex(module)
        for key, value in module_data.items():
            # set the decorator for the class methods
            if isinstance(value, pyclbr.Class):
                clz = importutils.import_class("%s.%s" % (module, key))
                for method, func in inspect.getmembers(clz, is_method):
                    setattr(clz, method,
                            decorator("%s.%s.%s" % (module, key,
                                                    method), func))
            # set the decorator for the function
            if isinstance(value, pyclbr.Function):
                func = importutils.import_class("%s.%s" % (module, key))
                setattr(sys.modules[module], key,
                        decorator("%s.%s" % (module, key), func))


@contextlib.contextmanager
def tempdir(**kwargs):
    argdict = kwargs.copy()
    if 'dir' not in argdict:
        argdict['dir'] = CONF.tempdir
    tmpdir = tempfile.mkdtemp(**argdict)
    try:
        yield tmpdir
    finally:
        try:
            shutil.rmtree(tmpdir)
        except OSError as e:
            LOG.error('Could not remove tmpdir: %s', e)


@privsep.monitors_priv.entrypoint
def privsep_execute(*cmd, **kwargs):
    return processutils.execute(*cmd, **kwargs)


def execute(*cmd, **kwargs):
    """Convenience wrapper around oslo's execute() method."""
    if 'run_as_root' in kwargs and kwargs.get('run_as_root'):
        return privsep_execute(*cmd, **kwargs)
    else:
        return processutils.execute(*cmd, **kwargs)


def synchronized(name, semaphores=None, blocking=False):
    def wrap(f):
        @functools.wraps(f)
        def inner(*args, **kwargs):
            lock_str = 'masakarimonitors-%s' % name
            int_lock = lockutils.internal_lock(lock_str,
                                               semaphores=semaphores)
            msg = "Lock blocking: %s on resource %s " % (lock_str, f.__name__)
            """Acquiring lock: %(lock_str)s on resource """
            if not int_lock.acquire(blocking=blocking):
                raise Exception(msg)
            try:
                return f(*args, **kwargs)
            finally:
                """Releasing lock: %(lock_str)s on resource """
                int_lock.release()
        return inner
    return wrap
