# Copyright (c) 2013 Rackspace, Inc.
#
# 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.

from oslo_log import log as logging
from osprofiler import profiler
from stevedore import driver
from stevedore import extension

from zaqar import common
from zaqar.common import decorators
from zaqar.conf import storage
from zaqar.storage import base

LOG = logging.getLogger(__name__)


def _get_storage_pipeline(resource_name, conf, *args, **kwargs):
    """Constructs and returns a storage resource pipeline.

    This is a helper function for any service supporting
    pipelines for the storage layer. The function returns
    a pipeline based on the `{resource_name}_pipeline`
    config option.

    Stages in the pipeline implement controller methods
    that they want to hook. A stage can halt the
    pipeline immediate by returning a value that is
    not None; otherwise, processing will continue
    to the next stage, ending with the actual storage
    controller.

    :param conf: Configuration instance.
    :type conf: `cfg.ConfigOpts`

    :returns: A pipeline to use.
    :rtype: `Pipeline`
    """
    conf.register_opts(storage.ALL_OPTS,
                       group=storage.GROUP_NAME)

    storage_conf = conf[storage.GROUP_NAME]

    pipeline = []
    for ns in storage_conf[resource_name + '_pipeline']:
        try:
            mgr = driver.DriverManager('zaqar.storage.stages',
                                       ns,
                                       invoke_args=args,
                                       invoke_kwds=kwargs,
                                       invoke_on_load=True)
            pipeline.append(mgr.driver)
        except RuntimeError as exc:
            LOG.warning(u'Stage %(stage)s could not be imported: %(ex)s',
                        {'stage': ns, 'ex': str(exc)})
            continue

    return pipeline


def _get_builtin_entry_points(resource_name, storage, control_driver, conf):
    # Load builtin stages
    builtin_entry_points = []

    # NOTE(flaper87): The namespace will look like:
    # `zaqar.storage.$STORAGE.driver.stages`. For now,
    # the builtin stages are bound to a single store and
    # are not applied to every store.
    namespace = '%s.%s.stages' % (storage.__module__, resource_name)
    extensions = extension.ExtensionManager(namespace,
                                            invoke_on_load=True,
                                            invoke_args=[storage,
                                                         control_driver])

    if len(extensions.extensions) == 0:
        return []

    for ext in extensions.extensions:
        builtin_entry_points.append(ext.obj)
    if conf.profiler.enabled and conf.profiler.trace_message_store:
        return (profiler.trace_cls("stages_controller")
                (builtin_entry_points))
    return builtin_entry_points


class DataDriver(base.DataDriverBase):
    """Meta-driver for injecting pipelines in front of controllers.

    :param conf: Configuration from which to load pipeline settings
    :param storage: Storage driver that will service requests as the
        last step in the pipeline
    """

    def __init__(self, conf, storage, control_driver):
        # NOTE(kgriffs): Pass None for cache since it won't ever
        # be referenced.
        super(DataDriver, self).__init__(conf, None, control_driver)
        self._storage = storage

    @property
    def capabilities(self):
        return self._storage.capabilities()

    def close(self):
        self._storage.close()

    def is_alive(self):
        return self._storage.is_alive()

    def _health(self):
        return self._storage._health()

    @decorators.lazy_property(write=False)
    def queue_controller(self):
        stages = _get_builtin_entry_points('queue', self._storage,
                                           self.control_driver, self.conf)
        stages.extend(_get_storage_pipeline('queue', self.conf))
        stages.append(self._storage.queue_controller)
        return common.Pipeline(stages)

    @decorators.lazy_property(write=False)
    def message_controller(self):
        stages = _get_builtin_entry_points('message', self._storage,
                                           self.control_driver, self.conf)
        kwargs = {'subscription_controller':
                  self._storage.subscription_controller,
                  'max_notifier_workers':
                  self.conf.notification.max_notifier_workers,
                  'require_confirmation':
                  self.conf.notification.require_confirmation,
                  'queue_controller':
                  self._storage.queue_controller}
        stages.extend(_get_storage_pipeline('message', self.conf, **kwargs))
        stages.append(self._storage.message_controller)
        return common.Pipeline(stages)

    @decorators.lazy_property(write=False)
    def claim_controller(self):
        stages = _get_builtin_entry_points('claim', self._storage,
                                           self.control_driver, self.conf)
        stages.extend(_get_storage_pipeline('claim', self.conf))
        stages.append(self._storage.claim_controller)
        return common.Pipeline(stages)

    @decorators.lazy_property(write=False)
    def subscription_controller(self):
        stages = _get_builtin_entry_points('subscription', self._storage,
                                           self.control_driver, self.conf)
        stages.extend(_get_storage_pipeline('subscription', self.conf))
        stages.append(self._storage.subscription_controller)
        return common.Pipeline(stages)

    @decorators.lazy_property(write=False)
    def topic_controller(self):
        stages = _get_builtin_entry_points('topic', self._storage,
                                           self.control_driver, self.conf)
        stages.extend(_get_storage_pipeline('topic', self.conf))
        stages.append(self._storage.topic_controller)
        return common.Pipeline(stages)
