# Copyright French Alternative Energies and Atomic Energy Commission
# Contributors: valjean developers
# valjean-support@cea.fr
#
# This software is a computer program whose purpose is to analyze and
# post-process numerical simulation results.
#
# This software is governed by the CeCILL license under French law and abiding
# by the rules of distribution of free software. You can use, modify and/ or
# redistribute the software under the terms of the CeCILL license as circulated
# by CEA, CNRS and INRIA at the following URL: http://www.cecill.info.
#
# As a counterpart to the access to the source code and rights to copy, modify
# and redistribute granted by the license, users are provided only with a
# limited warranty and the software's author, the holder of the economic
# rights, and the successive licensors have only limited liability.
#
# In this respect, the user's attention is drawn to the risks associated with
# loading, using, modifying and/or developing or reproducing the software by
# the user in light of its specific status of free software, that may mean that
# it is complicated to manipulate, and that also therefore means that it is
# reserved for developers and experienced professionals having in-depth
# computer knowledge. Users are therefore encouraged to load and test the
# software's suitability as regards their requirements in conditions enabling
# the security of their systems and/or data to be ensured and, more generally,
# to use and operate it in the same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.

'''The purpose of this module is to define the :class:`EvalTestTask` class, a
task that evaluates a collection of :class:`~.gavroche.test.Test` objects and
transforms them into :class:`~.gavroche.test.TestResult` objects, which can be
subsequently processed for inclusion in a test report.
'''

import logging
from pathlib import Path

from ..path import ensure, sanitize_filename
from ..cosette.task import TaskStatus
from ..cosette.use import from_env
from ..cosette.pythontask import PythonTask
from ..gavroche.test import Test, TestResultFailed


LOGGER = logging.getLogger(__name__)


def actually_eval_test(test):
    '''Actually perform test evaluation.

    :returns: the result of test evaluation.
    :rtype: TestResult
    :raises TestResultFailed: if the underlying test raises any exception.
    '''
    try:
        res = test.evaluate()
    except Exception as ex:  # pylint: disable=broad-except
        LOGGER.error('The test failed with error: \n%s', ex)
        return TestResultFailed(test, ex)
    return res


class EvalTestTask(PythonTask):
    '''Class that evaluates a list of tests and stores the resulting
    :class:`~.TestResult` objects in the environment.'''

    @classmethod
    def from_test_task(cls, test_task, name=None):
        '''This method instantiates an :class:`EvalTestTask` that will evaluate
        all the tests generated by a given task.

        :param Task test_task: a task that is expected to generate a list of
                               tests as a result.
        '''
        test_task_name = test_task.name
        if name is None:
            eval_task_name = test_task_name + '.eval'
        else:
            eval_task_name = name
        return cls(eval_task_name, test_task_name, deps=[test_task])

    def __init__(self, name, test_task_name, *, deps=None, soft_deps=None):
        '''Direct instantiation of an :class:`EvalTestTask`.

        :param str name: the name of this task.
        :param str test_task_name: the name of the task that generated the
                                   tests.
        :param deps: the list of dependencies for this task.
        :type deps: list(Task) or None
        :param soft_deps: the list of soft dependencies for this task.
        :type soft_deps: list(Task) or None
        '''
        def evaluate(*, env, config):
            tests = from_env(env=env, task_name=test_task_name, key='result')
            if not isinstance(tests, list):
                raise TypeError('Expected a list of tests in EvalTestTask '
                                f'{self.name!r}; got a {type(tests)} instead')
            for test in tests:
                if not isinstance(test, Test):
                    raise TypeError('Expected a list of tests in EvalTestTask '
                                    f'{self.name!r}, but one of the list '
                                    'elements is a {type(test)}')

            results = [actually_eval_test(test) for test in tests]

            output_dir = Path(config.query('path', 'output-root'),
                              sanitize_filename(self.name))
            ensure(output_dir, is_dir=True)
            env_up = {self.name: {'result': results,
                                  'output_dir': str(output_dir)}}
            status = TaskStatus.DONE
            return env_up, status

        super().__init__(name, evaluate, deps=deps, soft_deps=soft_deps,
                         env_kwarg='env', config_kwarg='config')


def evaluate_tests(test_fn, name=None):
    '''Create an :class:`EvalTestTask` objects for the given test.

    :param test_fn: a function that produces tests, wrapped in a
        :class:`~valjean.cosette.use.Use` decorator.
    :type test_fn: valjean.cosette.use.Use
    :param str name: the name of the :class:`EvalTestTask` object.
    :returns: the task that evaluates your tests.
    :rtype: EvalTestTask
    '''
    return EvalTestTask.from_test_task(test_fn.get_task(), name=name)
