#    Copyright 2018 ARM Limited
#
# 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 logging

from wa.framework import pluginloader
from wa.framework.exception import ConfigError
from wa.framework.instrument import is_installed
from wa.framework.plugin import Plugin
from wa.utils.log import log_error, indentcontext
from wa.utils.misc import isiterable
from wa.utils.types import identifier


class OutputProcessor(Plugin):

    kind = 'output_processor'
    requires = []

    def __init__(self, **kwargs):
        super(OutputProcessor, self).__init__(**kwargs)
        self.is_enabled = True

    def validate(self):
        super(OutputProcessor, self).validate()
        for instrument in self.requires:
            if not is_installed(instrument):
                msg = 'Instrument "{}" is required by {}, but is not installed.'
                raise ConfigError(msg.format(instrument, self.name))

    def initialize(self, context):
        pass

    def finalize(self, context):
        pass


class ProcessorManager(object):

    def __init__(self, loader=pluginloader):
        self.loader = loader
        self.logger = logging.getLogger('processor')
        self.processors = []

    def install(self, processor, context):
        if not isinstance(processor, OutputProcessor):
            processor = self.loader.get_output_processor(processor)
        self.logger.debug('Installing {}'.format(processor.name))
        processor.logger.context = context
        self.processors.append(processor)
        context.add_augmentation(processor)

    def disable_all(self):
        for output_processor in self.processors:
            self._disable_output_processor(output_processor)

    def enable_all(self):
        for output_processor in self.processors:
            self._enable_output_processor(output_processor)

    def enable(self, to_enable):
        if isiterable(to_enable):
            for inst in to_enable:
                self._enable_output_processor(inst)
        else:
            self._enable_output_processor(to_enable)

    def disable(self, to_disable):
        if isiterable(to_disable):
            for inst in to_disable:
                self._disable_output_processor(inst)
        else:
            self._disable_output_processor(to_disable)

    def get_output_processor(self, processor):
        if isinstance(processor, OutputProcessor):
            return processor

        processor = identifier(processor)
        for p in self.processors:
            if processor == p.name:
                return p
        raise ValueError('Output processor {} is not installed'.format(processor))

    def get_enabled(self):
        return [p for p in self.processors if p.is_enabled]

    def get_disabled(self):
        return [p for p in self.processors if not p.is_enabled]

    def validate(self):
        for proc in self.processors:
            proc.validate()

    def initialize(self, context):
        for proc in self.processors:
            proc.initialize(context)

    def finalize(self, context):
        for proc in self.processors:
            proc.finalize(context)

    def process_job_output(self, context):
        self.do_for_each_proc('process_job_output', 'Processing using "{}"',
                              context.job_output, context.target_info,
                              context.run_output)

    def export_job_output(self, context):
        self.do_for_each_proc('export_job_output', 'Exporting using "{}"',
                              context.job_output, context.target_info,
                              context.run_output)

    def process_run_output(self, context):
        self.do_for_each_proc('process_run_output', 'Processing using "{}"',
                              context.run_output, context.target_info)

    def export_run_output(self, context):
        self.do_for_each_proc('export_run_output', 'Exporting using "{}"',
                              context.run_output, context.target_info)

    def do_for_each_proc(self, method_name, message, *args):
        with indentcontext():
            for proc in self.processors:
                if proc.is_enabled:
                    proc_func = getattr(proc, method_name, None)
                    if proc_func is None:
                        continue
                    try:
                        self.logger.info(message.format(proc.name))
                        proc_func(*args)
                    except Exception as e:  # pylint: disable=broad-except
                        if isinstance(e, KeyboardInterrupt):
                            raise
                        log_error(e, self.logger)

    def _enable_output_processor(self, inst):
        inst = self.get_output_processor(inst)
        self.logger.debug('Enabling output processor {}'.format(inst.name))
        if not inst.is_enabled:
            inst.is_enabled = True

    def _disable_output_processor(self, inst):
        inst = self.get_output_processor(inst)
        self.logger.debug('Disabling output processor {}'.format(inst.name))
        if inst.is_enabled:
            inst.is_enabled = False