from typing import List
from ruleskit import RuleSet
import logging
from .duplicated_rules_updater import update_duplicated_rules

logger = logging.getLogger(__name__)


class Aggregation:
    """Abstract class for aggregating node models."""

    # noinspection PyUnresolvedReferences
    def __init__(self, aggregator: "Aggregator", **kwargs):
        """
        Parameters
        ----------
        aggregator: CentralServer
            `ifra.aggregator.Aggregator` object
        kwargs:
            Any additionnal keyword argument that the overleading class accepts. Those arguments will become attributes.
        """
        self.aggregator = aggregator
        for arg in kwargs:
            setattr(self, arg, kwargs[arg])

    def _aggregate(self, rulesets: List[RuleSet]) -> str:
        """To be implemented in daughter class.
        Aggregates the node models into aggregated model. Must NOT drop duplicated rules.

        Parameters
        ----------
        rulesets: List[RuleSet]
            New rulesets provided by the nodes.

        Returns
        -------
        str\n
          * "updated" if new rules were found\n
          * "pass" otherwise
        """
        pass

    def aggregate(self, rulesets: List[RuleSet]) -> str:
        """
        Aggregates the node models into aggregated model. Will handle the fact that rules will be duplicated by
        using `ifra.aggegations.aggregator_rules_attributes_setter`.

        Parameters
        ----------
        rulesets: List[RuleSet]
            New rulesets provided by the nodes.

        Returns
        -------
        str\n
          * "updated" if new rules were found\n
          * "pass" otherwise
        """
        aggregated = self._aggregate(rulesets)
        if aggregated == "updated":
            update_duplicated_rules(
                self.aggregator.model,
                self.aggregator.aggregator_configs.weight,
                self.aggregator.aggregator_configs.best
            )
        logger.info(f"Aggregated {len(self.aggregator.model)} new rules")
        return aggregated


class AdaBoostAggregation(Aggregation):
    """Among all rules generated by the nodes in the current iteration, will keep only the most recurrent rule(s).

    Can be used by giving *adaboost* as *aggregation* argument when creating`ifra.aggregator.Aggregator`.
    """

    def _aggregate(self, rulesets: List[RuleSet]) -> str:
        """In AdaBoostAggregation, 'aggregate' only returns "updated", for bad and already known rules should have been
        filtered out by the nodes already."""
        logger.info("Aggregating fit results using AdaBoost method...")
        all_rules = []

        for rs in rulesets:
            all_rules += rs.rules

        occurences = {r: all_rules.count(r) for r in set(all_rules)}

        max_occurences = max(list(occurences.values()))

        new_rules = RuleSet()

        for r in occurences:
            if occurences[r] == max_occurences:
                for rs in rulesets:
                    for r2 in rs:
                        if r2 == r:
                            new_rules.append(r, update_activation=False)

        del self.aggregator.model
        self.aggregator.model = new_rules
        return "updated"
