# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com

"""
Contains classes to manage the creation of test benches and runnable test cases thereof
"""
import re
import logging
from collections import OrderedDict
from vunit.test_list import TestList
from vunit.test_bench import TestBench


LOGGER = logging.getLogger(__name__)


class TestBenchList(object):
    """
    A list of test benchs
    """

    def __init__(self, database=None):
        self._libraries = OrderedDict()
        self._database = database

    def add_from_source_file(self, source_file):
        """
        Scan test benches from the source file and add to test bench list
        """
        for design_unit in source_file.design_units:
            if design_unit.is_entity or design_unit.is_module:
                if tb_filter is None or tb_filter(design_unit):
                    if design_unit.is_module or design_unit.is_entity:
                        self._add_test_bench(TestBench(design_unit, self._database))

    def _add_test_bench(self, test_bench):
        """
        Add the test bench
        """
        if test_bench.library_name not in self._libraries:
            self._libraries[test_bench.library_name] = OrderedDict()
        self._libraries[test_bench.library_name][test_bench.name] = test_bench

    def get_test_bench(self, library_name, name):
        return self._libraries[library_name][name]

    def get_test_benches_in_library(self, library_name):
        return list(self._libraries.get(library_name, {}).values())

    def get_test_benches(self):
        """
        Get all test benches
        """
        result = []
        for test_benches in self._libraries.values():
            for test_bench in test_benches.values():
                result.append(test_bench)
        return result

    def create_tests(self, simulator_if, elaborate_only):
        """
        Create all test cases from the test benches
        """
        test_list = TestList()
        for test_bench in self.get_test_benches():
            test_bench.create_tests(simulator_if, elaborate_only, test_list)
        return test_list

    def warn_when_empty(self):
        """
        Log a warning when there are no test benches
        """
        if not self.get_test_benches():
            LOGGER.warning("Found no test benches using current filter rule:\n%s",
                           tb_filter.__doc__)


TB_PATTERN = "^(tb_.*)|(.*_tb)$"
TB_RE = re.compile(TB_PATTERN,
                   re.IGNORECASE)


def tb_filter(design_unit):
    """
    Filters entities and modules that have a runner_cfg generic/parameter

    Gives warning when design_unit matches tb_* or *_tb without having a runner_cfg
    Gives warning when a design_unit has  a runner_cfg but the name does not match tb_* or *_tb
    """
    # Above docstring can show up in ui.py warnings
    has_runner_cfg = "runner_cfg" in design_unit.generic_names
    has_tb_name = TB_RE.match(design_unit.name) is not None

    design_unit_type = "Entity" if design_unit.is_entity else "Module"
    generic_type = "generic" if design_unit.is_entity else "parameter"

    if (not has_runner_cfg) and has_tb_name:
        LOGGER.warning(
            "%s %s matches testbench name regex %s but has no %s runner_cfg and will therefore not be run.\n"
            "in file %s",
            design_unit_type,
            design_unit.name,
            TB_PATTERN,
            generic_type,
            design_unit.file_name)

    elif has_runner_cfg and not has_tb_name:
        LOGGER.warning(
            "%s %s has runner_cfg %s but the file name and the %s name does not match regex %s\n"
            "in file %s",
            design_unit_type,
            design_unit.name,
            generic_type,
            design_unit_type.lower(),
            TB_PATTERN,
            design_unit.file_name)

    return has_runner_cfg
