import collections
import math
from collections import OrderedDict
from concurrent import futures

from ordered_set import OrderedSet

from miniworld.Scenario import scenario_config
from miniworld.model.singletons.Resetable import Resetable
from miniworld.model.singletons.Singletons import singletons
from miniworld.util import ConcurrencyUtil


class ShellCommandSerializer(Resetable):
    """
    This class can serialize commands into a given order and prevent duplicate commands!
    A group contains a list of events that belong to it. The events have an order.
    Adding commands to the events in the group keeps the order of the commands (sorted by event order).

    The purpose of this class is to ensure that commands are executed in a order which is independent of the insertion order.

    The commands for each event, are executed in parallel.
    Therefore, commands which shall be executed in a special order, should be splitted into events so that the commands in the event can be executed in parallel.

    Moreover, one has also to define the order of the groups.
    Look at the example below:
    - There are 3 groups: bridge, connection and new_event_group.
    - The creation of a bridge has to be done before operations on it can be done.
      Therefore we set the group order so that all bridge commands are executed before any operations are done on the linked
    - Moreover, there is also an order inside the events: ["bridge_add", "bridge_set_hub", "bridge_up", "bridge_add_if"]

    Examples
    --------
    >>> scs=ShellCommandSerializer()

    >>> scs.set_event_order("bridge", ["bridge_add", "bridge_set_hub", "bridge_up", "bridge_add_if"]) # <- event_order
    >>> # naming:
    >>> #                        ^        ^
    >>> #                    group/tag  event
    >>> scs.add_command("bridge", "bridge_add_if", "e.g 1", "brctl addif br_foobar eth0", ["bridge"])
    >>> scs.add_command("bridge", "bridge_add_if", "e.g 1", "brctl addif br_foobar eth1", ["bridge"])
    >>> scs.add_command("bridge", "bridge_add", "e.g 1", "brctl addbr br_foobar", ["bridge"])

    >>> scs.set_event_order("connection", ["state_change"])
    >>> scs.add_command("connection", "state_change", "e.g 1", "ifconfig eth0 up", ["connection"])

    >>> scs.set_group_order(["bridge", "connection"])
    >>> print scs.get_all_commands()
    ['brctl addbr br_foobar', 'brctl addif br_foobar eth0', 'brctl addif br_foobar eth1', 'ifconfig eth0 up']

    >>> # add an event group inclusive the event_order later
    >>> scs.add_group("new_event_group")
    >>> scs.set_event_order("new_event_group", ["new_event_1", "new_event_2"])


    >>> print scs.mapping_group_to_event_order_to_cs
    {'new_event_group': Event2CommandStoreMapping([('new_event_1', CommandStore([])), ('new_event_2', CommandStore([]))]), 'bridge': Event2CommandStoreMapping([('bridge_add', CommandStore(['brctl addbr br_foobar'])), ('bridge_set_hub', CommandStore([])), ('bridge_up', CommandStore([])), ('bridge_add_if', CommandStore(['brctl addif br_foobar eth0', 'brctl addif br_foobar eth1']))]), 'connection': Event2CommandStoreMapping([('state_change', CommandStore(['ifconfig eth0 up']))])}
    >>> # Run the commands
    >>> sce.run_commands()
    ...

    """

    def __init__(self):
        """
        Parameters
        ----------
        mapping_group_to_event_order_to_cs : Group2EventMapping
        group_order : OrderedSet<str>
            Keeps the order of the groups.
            We intentionally do not use the order from the `mapping_group_to_event_order_to_cs`
            because the OrderedDict does not have the possibility to reorder the groups!
        """
        self.reset()

    def __str__(self):
        return str(self.mapping_group_to_event_order_to_cs)

    def __repr__(self):
        return repr(self.mapping_group_to_event_order_to_cs)

    def reset(self):
        self.mapping_group_to_event_order_to_cs = Group2EventMapping()
        self.group_order = OrderedSet()

    #########################################################
    # Add a command
    #########################################################

    def add_command(self, group, event,
                    id, cmd, prefixes):
        """

        Parameters
        ----------
        group : str
        event : str
        id : str
        cmd : str
        prefixes : iterable<str>

        Returns
        -------
        CommandStore
        """

        cs = self.mapping_group_to_event_order_to_cs[group][event]
        if cs is None:
            self.mapping_group_to_event_order_to_cs[group][event] = cs = CommandStore(event)
        cs.add_command(id, cmd, prefixes)

        return cs

    #########################################################
    # Set and get group order
    #########################################################

    def add_group(self, event):
        self.group_order.add(event)

    def set_group_order(self, event_order_id_order):
        """
        Set the group order.

        Parameters
        ----------
        event_order_id_order : iterable<str>
        """
        self.group_order = OrderedSet(event_order_id_order)

    #########################################################
    # Set and get event order
    #########################################################

    def set_event_order(self, group, event_order):