from typing import List


class LoggedMetrics:

    logged_metrics = [
        ("loss", ["train", "val", "test"], ["all"], ["simple"]),
        ("acc", ["train", "val", "test"], ["all"], ["simple"]),
        (
            "precision",
            ["val", "test"],
            ["all", "fil"],
            ["micro", "macro"],
        ),
        ("precision", ["val", "test"], ["chk", "ind"], ["micro"]),
        ("recall", ["val", "test"], ["all", "fil"], ["micro", "macro"]),
        ("recall", ["val", "test"], ["chk", "ind"], ["micro"]),
        ("f1", ["val", "test"], ["all", "fil"], ["micro", "macro"]),
        ("f1", ["val", "test"], ["chk", "ind"], ["micro"]),
    ]

    def __init__(self):
        for metric, phase_list, tags_list, micro_macro_list in self.logged_metrics:
            assert isinstance(metric, str)
            assert isinstance(phase_list, list)
            assert isinstance(tags_list, list)
            assert isinstance(micro_macro_list, list)

    def get_metrics(
        self,
        tag_group: List[str] = None,
        phase_group: List[str] = None,
        micro_macro_group: List[str] = None,
        exclude: List[str] = None,
    ) -> List[str]:
        """
        get metrics, filtered
        ---------------------
        :param tag_group:          [list] of tags that are required, e.g. ['all']
        :param phase_group:        [list] of phases that are required, e.g. ['train']
        :param micro_macro_group:  [list] simple/micro/macro that is required, e.g. ['micro', 'macro']
        :param exclude:            [list] of metrics to exclude, e.g. ['loss']
        :return: filtered_metrics: [list] of [str], e.g. ['precision', 'recall']
        """
        filtered_metrics = list()
        for metric, phase_list, tags_list, micro_macro_list in self.logged_metrics:
            add_metric_1 = (
                True
                if tag_group is None
                else all([tag in tags_list for tag in tag_group])
            )
            add_metric_2 = (
                True
                if phase_group is None
                else all([phase in phase_list for phase in phase_group])
            )
            add_metric_3 = (
                True
                if micro_macro_group is None
                else all(
                    [
                        micro_macro in micro_macro_list
                        for micro_macro in micro_macro_group
                    ]
                )
            )
            add_metric_4 = True if exclude is None else metric not in exclude

            if add_metric_1 and add_metric_2 and add_metric_3 and add_metric_4:
                filtered_metrics.append(metric)

        return list(set(filtered_metrics))

    @classmethod
    def as_flat_list(cls):
        """
        return logged_metrics as flat list
        ----------------------------------
        :return: [list] of [str], e.g. ['all_precision_micro', 'all_precision_macro', 'fil_precision_micro', ..]
        """
        flat_list = list()
        for metric, _, tags_list, micro_macro_list in cls.logged_metrics:
            for tags in tags_list:
                if micro_macro_list == ["simple"]:
                    flat_list.append(f"{tags}_{metric}")
                else:
                    for style in micro_macro_list:
                        flat_list.append(f"{tags}_{metric}_{style}")
        return flat_list
