def group_states(self, state_ids, scoped_variable_ids=None):
        """ Group states and scoped variables into a new hierarchy state and remain internal connections.
            Interconnecting transitions and data flows to parent and other child states are removed, at the moment.

        :param state_ids: state_id's of all states that are to be grouped.
        :param scoped_variable_ids: data_port_id's of all scoped variables that are to be grouped, too.
        :return:
        """
        # TODO remember changed state or state element ids and provide them for the model functionalities
        assert all([state_id in self.states.keys() for state_id in state_ids])
        if scoped_variable_ids is None:
            scoped_variable_ids = []
        assert all([p_id in self.scoped_variables.keys() for p_id in scoped_variable_ids])
        from rafcon.core.states.barrier_concurrency_state import DeciderState
        if any(isinstance(self.states[child_state_id], DeciderState) for child_state_id in state_ids):
            raise ValueError("State of type DeciderState can not be grouped.")

        def create_name(name_str, used_names):
            number_str = ""
            number_of_str = 0
            while name_str + number_str in used_names:
                number_str = "_{}".format(number_of_str)
                number_of_str += 1
            return name_str + number_str

        [related_transitions, related_data_flows] = self.related_linkage_states_and_scoped_variables(state_ids,
                                                                                                     scoped_variable_ids)

        def assign_ingoing_outgoing(df, going_data_linkage_for_port, ingoing=True):
            internal = 'internal' if ingoing else 'external'
            external = 'external' if ingoing else 'internal'
            if (df.to_state, df.to_key) in going_data_linkage_for_port['to']:
                going_data_linkage_for_port['to'][(df.to_state, df.to_key)][external].append(df)
            else:
                going_data_linkage_for_port['to'][(df.to_state, df.to_key)] = {external: [df], internal: [df]}
            if (df.from_state, df.from_key) in going_data_linkage_for_port['from']:
                going_data_linkage_for_port['from'][(df.from_state, df.from_key)][internal].append(df)
            else:
                going_data_linkage_for_port['from'][(df.from_state, df.from_key)] = {external: [df], internal: [df]}

        def print_df_from_and_to(going_data_linkage_for_port):
            logger.verbose('data linkage FROM: ')
            for port, port_dfs in going_data_linkage_for_port['from'].items():
                logger.verbose("\tport: {0} {1}".format(port, '' if 'args' not in port_dfs else port_dfs['args']))
                logger.verbose("\t\texternal: \n\t\t\t" + "\n\t\t\t".join([str(df) for df in port_dfs['external']]))
                logger.verbose("\t\tinternal: \n\t\t\t" + "\n\t\t\t".join([str(df) for df in port_dfs['internal']]))
            logger.verbose('data linkage TO: ')
            for port, port_dfs in going_data_linkage_for_port['to'].items():
                logger.verbose("\tport: {0} {1}".format(port, '' if 'args' not in port_dfs else port_dfs['args']))
                logger.verbose("\t\texternal: \n\t\t\t" + "\n\t\t\t".join([str(df) for df in port_dfs['external']]))
                logger.verbose("\t\tinternal: \n\t\t\t" + "\n\t\t\t".join([str(df) for df in port_dfs['internal']]))

        def reduce_dfs(port_data_linkages, df_id):
            for port_key in list(port_data_linkages.keys()):
                port_df = port_data_linkages[port_key]
                port_df['internal'] = [df for df in port_df['internal'] if df.data_flow_id != df_id]
                port_df['external'] = [df for df in port_df['external'] if df.data_flow_id != df_id]
                if not port_df['internal'] and not port_df['external']:
                    del port_data_linkages[port_key]

        def do_prior_in_out_going(going_data_linkage_for_port, prior_port_key, prior_locate_key):
            # logger.info("PRIOR IN OUT {0}, {1}".format(prior_port_key, prior_locate_key))
            minor_port_key = 'from' if prior_port_key == 'to' else 'to'
            minor_locate_key = 'external' if prior_locate_key == 'internal' else 'internal'
            for port_key in list(going_data_linkage_for_port[prior_port_key].keys()):
                port_dfs = going_data_linkage_for_port[prior_port_key][port_key]
                # print(prior_port_key, ": check: ", port_key, " length: ", len(port_dfs[prior_locate_key]))
                if len(port_dfs[prior_locate_key]) > 1:
                    for df in port_dfs[prior_locate_key]:
                        # print("remove: ", df.data_flow_id, prior_locate_key, minor_port_key)
                        reduce_dfs(going_data_linkage_for_port[minor_port_key], df.data_flow_id)
            for port_key in list(going_data_linkage_for_port[minor_port_key].keys()):
                port_dfs = going_data_linkage_for_port[minor_port_key][port_key]
                # print(minor_port_key, ": check: ", port_key, " length: ", len(port_dfs[minor_locate_key]))
                if len(port_dfs[minor_locate_key]) > 1:
                    for df in port_dfs[minor_locate_key]:
                        # print("remove: ", df.data_flow_id, minor_locate_key, prior_port_key)
                        reduce_dfs(going_data_linkage_for_port[prior_port_key], df.data_flow_id)
            for port_key in list(going_data_linkage_for_port[prior_port_key].keys()):
                port_dfs = going_data_linkage_for_port[prior_port_key][port_key]
                # print(prior_port_key, ": check: ", port_key, " length: ", len(port_dfs[prior_locate_key]))
                if len(port_dfs[prior_locate_key]) == 1:
                    for df in port_dfs[prior_locate_key]:
                        # print("remove: ", df.data_flow_id, prior_locate_key, minor_port_key)
                        reduce_dfs(going_data_linkage_for_port[minor_port_key], df.data_flow_id)

        def create_data_port_args(going_data_linkage_for_port):
            names = []
            for goal, data_port_linkage in going_data_linkage_for_port['to'].items():
                state = self.states[goal[0]] if goal[0] != self.state_id else self
                port = state.get_data_port_by_id(goal[1])
                data_port_linkage['args'] = port.state_element_to_dict(port)
                data_port_linkage['args']['name'] = create_name(data_port_linkage['args']['name'], names)
                names.append(data_port_linkage['args']['name'])
            for goal, data_port_linkage in going_data_linkage_for_port['from'].items():
                state = self.states[goal[0]] if goal[0] != self.state_id else self
                port = state.get_data_port_by_id(goal[1])
                data_port_linkage['args'] = port.state_element_to_dict(port)
                data_port_linkage['args']['name'] = create_name(data_port_linkage['args']['name'], names)
                names.append(data_port_linkage['args']['name'])

        ################## IDENTIFY/PRE-PROCESS INGOING DATA FLOWS ###################
        # ingoing data linkage to rebuild -> overview of all with duplicates
        ingoing_data_linkage_for_port = {'from': {}, 'to': {}}
        for df in related_data_flows['ingoing']:
            assign_ingoing_outgoing(df, ingoing_data_linkage_for_port)

        # logger.info("GROUP DATA INGOING BEFORE")
        # print_df_from_and_to(ingoing_data_linkage_for_port)

        # prior to-linkage-merge for ingoing over from-linkage-merge -> less internal ingoing data flows
        do_prior_in_out_going(ingoing_data_linkage_for_port, prior_port_key='to', prior_locate_key='external')

        # logger.info("GROUPED INGOING DATA AFTER")
        # print_df_from_and_to(ingoing_data_linkage_for_port)

        # get name and args for ports
        create_data_port_args(ingoing_data_linkage_for_port)
        # logger.info("GROUPED INGOING DATA PORTS")
        # print_df_from_and_to(ingoing_data_linkage_for_port)

        ################## IDENTIFY/PRE-PROCESS OUTGOING DATA FLOWS ###################
        # outgoing data linkage to rebuild -> overview of all with duplicates
        outgoing_data_linkage_for_port = {'from': {}, 'to': {}}
        for df in related_data_flows['outgoing']:
            assign_ingoing_outgoing(df, outgoing_data_linkage_for_port, ingoing=False)

        # logger.info("GROUP DATA OUTGOING BEFORE")
        # print_df_from_and_to(outgoing_data_linkage_for_port)

        # prior from-linkage-merge for outgoing over to-linkage-merge -> less outgoing data flows
        do_prior_in_out_going(outgoing_data_linkage_for_port, prior_port_key='from', prior_locate_key='external')

        # logger.info("GROUPED OUTGOING DATA AFTER")
        # print_df_from_and_to(outgoing_data_linkage_for_port)

        # get name and args for ports
        create_data_port_args(outgoing_data_linkage_for_port)
        # logger.info("GROUPED OUTGOING DATA PORTS")
        # print_df_from_and_to(outgoing_data_linkage_for_port)

        ############################# CREATE NEW STATE #############################
        # all internal transitions
        transitions_internal = {t.transition_id: self.remove_transition(t.transition_id, destroy=False)
                                for t in related_transitions['enclosed']}
        # all internal data flows
        data_flows_internal = {df.data_flow_id: self.remove_data_flow(df.data_flow_id, destroy=False)
                               for df in related_data_flows['enclosed']}
        # TODO add warning if a data-flow is connected to scoped variables which has ingoing and outgoing data flows
        # TODO linked with selected states -> group would change behavior and scoped would need to be selected or
        # TODO                                local scoped variable need to be introduced in new hierarchy state
        # all internal scoped variables
        scoped_variables_to_group = {dp_id: self.remove_scoped_variable(dp_id, destroy=False)
                                     for dp_id in scoped_variable_ids}
        # all states
        states_to_group = {state_id: self.remove_state(state_id, recursive=False, destroy=False)
                           for state_id in state_ids}
        from rafcon.core.states.hierarchy_state import HierarchyState
        # secure state id conflicts for the taken transitions
        from rafcon.core.id_generator import state_id_generator
        state_id = state_id_generator(used_state_ids=state_ids + [self.state_id])
        # if scoped variables are used all data flows have to be checked if those link to those and correct the state_id
        if scoped_variable_ids:
            for data_flow in data_flows_internal.values():
                if data_flow.from_state == self.state_id:
                    data_flow.from_state = state_id
                if data_flow.to_state == self.state_id:
                    data_flow.to_state = state_id
        s = HierarchyState(states=states_to_group, transitions=transitions_internal, data_flows=data_flows_internal,
                           scoped_variables=scoped_variables_to_group, state_id=state_id)
        state_id = self.add_state(s)

        def find_logical_destinations_of_transitions(transitions):
            destinations = {}
            for t in transitions:
                if (t.to_state, t.to_outcome) in destinations:
                    destinations[(t.to_state, t.to_outcome)].append(t)
                else:
                    destinations[(t.to_state, t.to_outcome)] = [t]
            return destinations

        ################## IDENTIFY TRANSITIONS #####################
        # transition from ingoing transition
        ingoing_logical_destinations = find_logical_destinations_of_transitions(related_transitions['ingoing'])
        if len(ingoing_logical_destinations) > 1:
            logger.warning("There is only one ingoing transition on a state possible. \n"
                           "The following transitions are removed by 'group_states': \n{}"
                           "".format('\n'.join([str(destination) for destination in ingoing_logical_destinations.items()[1:]])))
        ingoing_transitions = None
        if len(ingoing_logical_destinations) > 0:
            ingoing_transitions = list(ingoing_logical_destinations.items())[0][1]
        # transitions from outgoing transitions
        transitions_outgoing = {t.transition_id: t for t in related_transitions['outgoing']}
        outgoing_logical_destinations = find_logical_destinations_of_transitions(related_transitions['outgoing'])

        ################## DO INGOING TRANSITIONS ###################
        if ingoing_transitions:
            t = ingoing_transitions[0]
            s.add_transition(None, None, t.to_state, t.to_outcome)
            for t in ingoing_transitions:
                self.add_transition(t.from_state, t.from_outcome, s.state_id, None)

        ################## DO OUTGOING OUTCOMES ###################
        # outcomes from outgoing transitions
        outcomes_outgoing_transitions = {}
        new_outcome_ids = {}
        state_outcomes_by_name = {oc.name: oc_id for oc_id, oc in s.outcomes.items()}
        for goal, transitions in list(outgoing_logical_destinations.items()):
            t = transitions[0]
            name = s.states[t.from_state].outcomes[t.from_outcome].name
            # print((t.to_state, t.to_outcome))
            # print(outcomes_outgoing_transitions)
            if goal in outcomes_outgoing_transitions:
                # logger.info("old outcome {}".format((t.to_state, t.to_outcome)))
                name = outcomes_outgoing_transitions[goal]
            else:
                name = create_name(name, list(new_outcome_ids.keys()))
                outcomes_outgoing_transitions[goal] = name
            # print(outcomes_outgoing_transitions, "\n", new_outcome_ids)
            if name not in new_outcome_ids:
                if name in state_outcomes_by_name:
                    new_outcome_ids[name] = state_outcomes_by_name[name]
                    # logger.info("old outcome_id {0}\n{1}".format(state_outcomes_by_name[name], new_outcome_ids))
                else:
                    new_outcome_ids[name] = s.add_outcome(name=name)
                    # logger.info("new outcome_id {0}\n{1}".format(new_outcome_ids[name], new_outcome_ids))
            # else:
            #     logger.info("name {0} in {1} -> {2}".format(name, new_outcome_ids, outcomes_outgoing_transitions))

        ################## DO OUTGOING TRANSITIONS ###################
        # external outgoing transitions
        # print("external transitions to create", outcomes_outgoing_transitions)
        for goal, name in outcomes_outgoing_transitions.items():
            try:
                # avoid to use a outcome twice
                if any([t for t in s.parent.transitions.values()
                        if t.from_state == s.state_id and t.from_outcome == new_outcome_ids[name]]):
                    continue
                # add the transition for the outcome
                self.add_transition(s.state_id, new_outcome_ids[name], goal[0], goal[1])
            except ValueError:
                logger.exception("Error while recreation of logical linkage.")
        # internal outgoing transitions
        # print("internal transitions to create", transitions_outgoing)
        for t_id, t in transitions_outgoing.items():
            name = outcomes_outgoing_transitions[(t.to_state, t.to_outcome)]
            s.add_transition(t.from_state, t.from_outcome, s.state_id, new_outcome_ids[name], t_id)

        new_state_ids = {self.state_id: s.state_id}
        ############## REBUILD INGOING DATA LINKAGE ################
        full_linkage = copy(ingoing_data_linkage_for_port['from'])
        full_linkage.update(ingoing_data_linkage_for_port['to'])
        for port, data_port_linkage in full_linkage.items():
            # input data ports from ingoing data flows
            args = data_port_linkage['args']
            args['data_port_id'] = None
            args['data_port_id'] = s.add_input_data_port(**args)
            # internal data flows from ingoing data flows
            # print("ingoing internal data flows")
            for df in data_port_linkage['internal']:
                # print(df)
                s.add_data_flow(from_state_id=s.state_id, from_data_port_id=args['data_port_id'],
                                to_state_id=new_state_ids.get(df.to_state, df.to_state),
                                to_data_port_id=df.to_key, data_flow_id=df.data_flow_id)
            # external data flows from ingoing data flows
            # print("ingoing external data flows")
            for df in data_port_linkage['external']:
                # print(df)
                self.add_data_flow(from_state_id=df.from_state,
                                   from_data_port_id=df.from_key, to_state_id=s.state_id,
                                   to_data_port_id=args['data_port_id'], data_flow_id=df.data_flow_id)

        ############## REBUILD OUTGOING DATA LINKAGE ################
        full_linkage = copy(outgoing_data_linkage_for_port['from'])
        full_linkage.update(outgoing_data_linkage_for_port['to'])
        for port, data_port_linkage in full_linkage.items():
            # output data ports from outgoing data flows
            args = data_port_linkage['args']
            args['data_port_id'] = None
            args['data_port_id'] = s.add_output_data_port(**args)
            # internal data flows from outgoing data flows
            # print("outgoing internal data flows")
            for df in data_port_linkage['internal']:
                # print(df)
                s.add_data_flow(from_state_id=new_state_ids.get(df.from_state, df.from_state),
                                from_data_port_id=df.from_key, to_state_id=s.state_id,
                                to_data_port_id=args['data_port_id'], data_flow_id=df.data_flow_id)
            # external data flows from outgoing data flows
            # print("outgoing external data flows")
            for df in data_port_linkage['external']:
                # print(df)
                self.add_data_flow(from_state_id=s.state_id, from_data_port_id=args['data_port_id'],
                                   to_state_id=df.to_state, to_data_port_id=df.to_key, data_flow_id=df.data_flow_id)

        return self.states[state_id]