#
# Copyright 2013 ZHAW SoE
# Copyright 2014 Intel Corp.
#
# Authors: Lucas Graf <graflu0@students.zhaw.ch>
#          Toni Zehnder <zehndton@students.zhaw.ch>
#          Lianhao Lu <lianhao.lu@intel.com>
#
# 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.
"""Base class for plugins used by the hardware agent."""

import abc
import itertools

from oslo_utils import netutils
import six

from ceilometer.agent import plugin_base
from ceilometer.hardware import inspector as insloader
from ceilometer.i18n import _
from ceilometer.openstack.common import log

LOG = log.getLogger(__name__)


@six.add_metaclass(abc.ABCMeta)
class HardwarePollster(plugin_base.PollsterBase):
    """Base class for plugins that support the polling API."""

    CACHE_KEY = None
    IDENTIFIER = None

    def __init__(self):
        super(HardwarePollster, self).__init__()
        self.inspectors = {}

    @property
    def default_discovery(self):
        return 'tripleo_overcloud_nodes'

    @staticmethod
    def _parse_resource(res):
        """Parse resource from discovery.

        Either URL can be given or dict. Dict has to contain at least
        keys 'resource_id' and 'resource_url', all the dict keys will be stored
        as metadata.

        :param res: URL or dict containing all resource info.
        :return parsed_url, resource_id, metadata: Returns parsed URL used for
            SNMP query, unique identifier of the resource and metadata
            of the resource.
        """

        if isinstance(res, dict):
            if 'resource_url' not in res or 'resource_id' not in res:
                LOG.exception(_('Passed resource dict must contain keys '
                                'resource_id and resource_url.'))

            metadata = res
            parsed_url = netutils.urlsplit(res['resource_url'])
            resource_id = res['resource_id']
        else:
            metadata = {}
            parsed_url = netutils.urlsplit(res)
            resource_id = res

        return parsed_url, resource_id, metadata

    def get_samples(self, manager, cache, resources=None):
        """Return an iterable of Sample instances from polling the resources.

        :param manager: The service manager invoking the plugin
        :param cache: A dictionary for passing data between plugins
        :param resources: end point to poll data from
        """
        resources = resources or []
        h_cache = cache.setdefault(self.CACHE_KEY, {})
        sample_iters = []
        for resource in resources:
            parsed_url, res, extra_metadata = self._parse_resource(resource)
            ins = self._get_inspector(parsed_url)
            try:
                # Call hardware inspector to poll for the data
                i_cache = h_cache.setdefault(res, {})

                if self.IDENTIFIER not in i_cache:
                    i_cache[self.IDENTIFIER] = list(ins.inspect_generic(
                        parsed_url,
                        self.IDENTIFIER,
                        i_cache,
                        extra_metadata))
                # Generate samples
                if i_cache[self.IDENTIFIER]:
                    sample_iters.append(self.generate_samples(
                        parsed_url,
                        i_cache[self.IDENTIFIER]))
            except Exception as err:
                LOG.exception(_('inspector call failed for %(ident)s '
                                'host %(host)s: %(err)s'),
                              dict(ident=self.IDENTIFIER,
                                   host=parsed_url.hostname,
                                   err=err))
        return itertools.chain(*sample_iters)

    def generate_samples(self, host_url, data):
        """Generate an iterable Sample from the data returned by inspector

        :param host_url: host url of the endpoint
        :param data: list of data returned by the corresponding inspector

        """
        return (self.generate_one_sample(host_url, datum) for datum in data)

    @abc.abstractmethod
    def generate_one_sample(self, host_url, c_data):
        """Return one Sample.

        :param host_url: host url of the endpoint
        :param c_data: data returned by the inspector.inspect_generic,
                       tuple of (value, metadata, extra)
        """

    def _get_inspector(self, parsed_url):
        if parsed_url.scheme not in self.inspectors:
            try:
                driver = insloader.get_inspector(parsed_url)
                self.inspectors[parsed_url.scheme] = driver
            except Exception as err:
                LOG.exception(_("Can NOT load inspector %(name)s: %(err)s"),
                              dict(name=parsed_url.scheme,
                                   err=err))
                raise err
        return self.inspectors[parsed_url.scheme]
