# Copyright (c) 2014 Red Hat, Inc.
# All Rights Reserved.
#
#    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.

import os
import pipes

from oslo_concurrency import processutils
from oslo_log import log

from manila import exception
from manila.i18n import _
from manila import utils

LOG = log.getLogger(__name__)


def patch(base, *overlays):
    """Recursive dictionary patching."""
    for ovl in overlays:
        for k, v in ovl.items():
            if isinstance(v, dict) and isinstance(base.get(k), dict):
                patch(base[k], v)
            else:
                base[k] = v
    return base


def walk(dct):
    """Recursive iteration over dictionary."""
    for k, v in dct.items():
        if isinstance(v, dict):
            for w in walk(v):
                yield w
        else:
            yield k, v


class RootExecutor(object):
    """Execute wrapper defaulting to root execution."""

    def __init__(self, execute=utils.execute):
        self.execute = execute

    def __call__(self, *args, **kwargs):
        exkwargs = {"run_as_root": True}
        exkwargs.update(kwargs)
        return self.execute(*args, **exkwargs)


class SSHExecutor(object):
    """Callable encapsulating exec through ssh."""

    def __init__(self, *args, **kwargs):
        self.pool = utils.SSHPool(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        # argument with identifier 'run_as_root=' is not accepted by
        # processutils's ssh_execute() method unlike processutils's execute()
        # method. So implement workaround to enable or disable 'run as root'
        # behavior.
        run_as_root = kwargs.pop('run_as_root', False)
        cmd = ' '.join(pipes.quote(a) for a in args)
        if run_as_root:
            cmd = ' '.join(['sudo', cmd])
        ssh = self.pool.get()
        try:
            ret = processutils.ssh_execute(ssh, cmd, **kwargs)
        finally:
            self.pool.put(ssh)
        return ret


def path_from(fpath, *rpath):
    """Return the join of the dir of fpath and rpath in absolute form."""
    return os.path.join(os.path.abspath(os.path.dirname(fpath)), *rpath)


def validate_access_rule(supported_access_types, supported_access_levels,
                         access_rule, abort=False):

    """Validate an access rule.

    :param access_rule: Access rules to be validated.
    :param supported_access_types: List of access types that are regarded
           valid.
    :param supported_access_levels: List of access levels that are
           regarded valid.
    :param abort: a boolean value that indicates if an exception should
                  be raised whether the rule is invalid.
    :return: Boolean.
    """

    errmsg = _("Unsupported access rule of 'type' %(access_type)s, "
               "'level' %(access_level)s, 'to' %(access_to)s: "
               "%(field)s should be one of %(supported)s.")
    access_param = access_rule.to_dict()

    def validate(field, supported_tokens, excinfo):
        if access_rule['access_%s' % field] in supported_tokens:
            return True

        access_param['field'] = field
        access_param['supported'] = ', '.join(
            "'%s'" % x for x in supported_tokens)
        if abort:
            LOG.error(errmsg, access_param)
            raise excinfo['type'](
                **{excinfo['about']: excinfo['details'] % access_param})
        else:
            LOG.warning(errmsg, access_param)
            return False

    valid = True
    valid &= validate(
        'type', supported_access_types,
        {'type': exception.InvalidShareAccess, 'about': "reason",
         'details': _(
             "%(access_type)s; only %(supported)s access type is allowed")})
    valid &= validate(
        'level', supported_access_levels,
        {'type': exception.InvalidShareAccessLevel, 'about': "level",
         'details': "%(access_level)s"})

    return valid
