def __init__(self) -> None:
     self._server = NEATServer()  # type: NEATServer
class SimulationConnector(object):
    """
    A class providing a high-level interface to
    a client connected via the NEATClient.
    It receives commands from NEATServer's message
    queue und responds to them.
    When a function from SimulationConnector is called,
    the client is expected to have sent a certain command
    (if there are no commands in the message queue, the Server
    will wait for the next command to be received). If the
    client has sent another command, or a command where
    the expected parameters are not present,
    NetworkProtocolException will be raised.
    """

    def __init__(self) -> None:
        self._server = NEATServer()  # type: NEATServer

    def _listen_for_command(
            self,
            command_type: str,
            parameter_filter: Dict[str, object]=None,
            timeout: int = None
    ) -> BaseCommand:
        """
        Waits for the message queue to have a command ready,
        pops the command from the queue and inspects it.
        Returns the received command if it's type and parameters
        match type / filter.
        If the next received command does not match,
        NetworkProtocolException is raised.
        :param command_type: The type of the expected command as string
        :param parameter_filter: A dictionary of parameters that are expected to be
        present in the command's parameters.
        :param timeout: The timeout after which the attempt to get
        the command will fail
        :return: The received command object
        """
        # TODO: timeouts, more error handling
        command = self._server.fetch(timeout)
        print("Received: ", command)
        if not command:
            raise NetworkTimeoutException

        if not command.type == command_type:
            self._respond_to_command(command, acknowledged=False)
            raise NetworkProtocolException(
                "Wrong command type encountered: " +
                command._type +
                ", expected " +
                command_type +
                "."
            )

        if parameter_filter:
            for key, value in parameter_filter.items():
                if key not in command.parameters.keys():
                    self._respond_to_command(command, acknowledged=False)
                    raise NetworkProtocolException("Filter key not in command parameters")
                if not value == command.parameters[key]:
                    self._respond_to_command(command, acknowledged=False)
                    raise NetworkProtocolException("Filter values do not match in command parameters")

        return command

    def _respond_to_command(
            self,
            command: BaseCommand,
            result: Dict[str, object]=None,
            acknowledged: bool=True,
            timeout: int=2000
    ) -> None:
        """
        Sends the altered command object of a previously
        received command back to the client.
        The command object that is sent back to the client
        should always be the same object that had been received
        from the client.
        the results of the command execution are passed back to client
        inside the command.result dictionary. If this dictionary
        isn't present in the passed command, it will be created.
        All commands that're passed back to the client and that
        have been handled by the server contain an 'acknowledged'
        field inside results. If this is set to true, the command
        has been executed and the networking protocol hasn't been
        breached.
        :param command: The command object that is sent back
        to the client
        :param result: The result dictionary to be attached to
        the command before it's sent back
        :param acknowledged: Whether the command has been
        acknowledged by the server
        :param timeout: The timeout after which the send operation
        will fail if the client hasn't connected yet
        :return: None
        """
        if result is None:
            result = dict({})
        command.result = result
        command.result["acknowledged"] = acknowledged
        self._server.respond(command)

    def get_session(self) -> Dict[str, object]:
        """
        Awaits the AnnounceSession command to be sent by the client.
        :return: The session parameters for the new simulation session
        """
        session_command = self._listen_for_command("AnnounceSession")
        self._respond_to_command(session_command)
        return session_command.parameters

    def send_block(
            self,
            block: List[StorageGenome],
            block_id: int
    ) -> None:
        """
        Awaits the GetBlock command to be sent by the client
        and sends the block to the client.
        :param block: The List of StorageGenomes to be sent to the client
        :param block_id: The id (0 - block_count) of the block to be sent
        :return: None
        """
        block_to_send = {
            genome.genome_id.__str__():
                {
                    input_label: None
                    for input_label in genome.inputs.keys()
                }
            for genome in block
        }
        result = {
            "block": block_to_send,
            "block_id": block_id,
            "next_block_id": block_id + 1,
            "block_size": len(list(block_to_send.keys()))
        }

        get_command = self._listen_for_command("GetBlock", {"block_id": block_id})
        print("Sending block: ", result)
        self._respond_to_command(
            get_command,
            result
        )

    def get_block_inputs(
            self,
            block_id: int
    ) -> Dict[ObjectId, Dict[str, float]]:
        """
        Awaits the SetInputs command to be sent by the client.
        :param block_id: The id of the block whose inputs
        should by received
        :return: The received inputs as Dictionary of
        genome_id: Dict[input_label: input_value]
        """
        set_command = self._listen_for_command("SetInputs", {"block_id": block_id})
        self._respond_to_command(copy.deepcopy(set_command))

        for key, value in set_command.parameters["block"].items():
            del set_command.parameters["block"][key]
            set_command.parameters["block"][ObjectId(key)] = value

        return set_command.parameters["block"]

    def send_block_outputs(
            self,
            outputs: Dict[ObjectId, Dict[str, float]],
            block_id: int
    ) -> None:
        """
        Awaits the GetOutputs command to be sent by the client
        and sends the outputs for the previously received
        inputs back to the client.
        :param outputs: The output values as dictionary of
        genome_id: Dict[output_label: output_value]
        :param block_id: The od of the block whose inputs
        should be sent
        :return: None
        """
        get_command = self._listen_for_command("GetOutputs", {"block_id": block_id})

        for key, value in outputs.items():
            del outputs[key]
            outputs[key.__str__()] = value

        result = {
            "outputs": outputs
        }
        self._respond_to_command(get_command, result)

    def get_fitness_values(
            self,
            block_id: int
    ) -> Dict[ObjectId, float]:
        """
        Awaits the SetFitnessValues command to be sent by the client.
        :param block_id: The id of the block whose fitness values
        should be received
        :return: The received fitness values as dictionary of
        genome_id: fitness_value
        """
        set_command = self._listen_for_command(
            "SetFitnessValues",
            {
                "block_id": block_id
            }
        )
        self._respond_to_command(copy.deepcopy(set_command))
        for key, value in set_command.parameters["fitness_values"].items():
            del set_command.parameters["fitness_values"][key]
            set_command.parameters["fitness_values"][ObjectId(key)] = value

        return set_command.parameters["fitness_values"]

    def get_advance_generation(self) -> bool:
        """
        Awaits the AdvanceGeneration command to be sent by the client.
        :return: A bool indicating whether the next generation should be
        calculated by NEAT (True) or the current session should be archived
        (False).
        """
        advance_command = self._listen_for_command("AdvanceGeneration")
        self._respond_to_command(advance_command)
        return advance_command.parameters["advance_generation"]