def log_to_collapsed_structure(execution_history_items, throw_on_pickle_error=True,
                               include_erroneous_data_ports=False, full_next=False):
    """
    Collapsed structure means that all history items belonging to the same state execution are
    merged together into one object (e.g. CallItem and ReturnItem of an ExecutionState). This
    is based on the log structure in which all Items which belong together have the same run_id.
    The collapsed items hold input as well as output data (direct and scoped), and the outcome
    the state execution.
    :param dict execution_history_items: history items, in the simplest case
           directly the opened shelve log file
    :param bool throw_on_pickle_error: flag if an error is thrown if an object cannot be un-pickled
    :param bool include_erroneous_data_ports: flag if to include erroneous data ports
    :param bool full_next: flag to indicate if the next relationship has also to be created at the end
           of container states
    :return: start_item, the StateMachineStartItem of the log file
             next_, a dict mapping run_id --> run_id of the next executed state on the same
                    hierarchy level
             concurrent, a dict mapping run_id --> []list of run_ids of the concurrent next
                         executed states (if present)
             hierarchy, a dict mapping run_id --> run_id of the next executed state on the
                        deeper hierarchy level (the start state within that HierarchyState)
             items, a dict mapping run_id --> collapsed representation of the execution of
                    the state with that run_id
    :rtype: tuple
    """

    # for debugging purposes
    # execution_history_items_dict = dict()
    # for k, v in execution_history_items.items():
    #     execution_history_items_dict[k] = v

    start_item, previous, next_, concurrent, grouped = log_to_raw_structure(execution_history_items)

    start_item = None
    collapsed_next = {}
    collapsed_concurrent ={}
    collapsed_hierarchy = {}
    collapsed_items = {}

    # single state executions are not supported
    if len(next_) == 0 or len(next_) == 1:
        for rid, gitems in grouped.items():
            if gitems[0]['item_type'] == 'StateMachineStartItem':
                item = gitems[0]
                execution_item = {}
                ## add base properties will throw if not existing
                for l in ['description', 'path_by_name', 'state_name', 'run_id', 'state_type',
                          'path', 'timestamp', 'root_state_storage_id', 'state_machine_version',
                          'used_rafcon_version', 'creation_time', 'last_update', 'os_environment']:
                    try:
                        execution_item[l] = item[l]
                    except KeyError:
                        logger.warning("Key {} not in history start item".format(str(l)))

                ## add extended properties (added in later rafcon versions),
                ## will add default value if not existing instead
                for l, default in [('semantic_data', {}),
                                     ('is_library', None),
                                     ('library_state_name', None),
                                     ('library_name', None),
                                     ('library_path', None)]:
                    execution_item[l] = item.get(l, default)

                start_item = execution_item
        return start_item, collapsed_next, collapsed_concurrent, collapsed_hierarchy, collapsed_items

    # build collapsed items
    for rid, gitems in grouped.items():
        if gitems[0]['item_type'] == 'StateMachineStartItem':
            item = gitems[0]
            execution_item = {}
            ## add base properties will throw if not existing
            for l in ['description', 'path_by_name', 'state_name', 'run_id', 'state_type',
                      'path', 'timestamp', 'root_state_storage_id', 'state_machine_version',
                      'used_rafcon_version', 'creation_time', 'last_update', 'os_environment']:
                try:
                    execution_item[l] = item[l]
                except KeyError:
                    logger.warning("Key {} not in history start item".format(str(l)))

            ## add extended properties (added in later rafcon versions),
            ## will add default value if not existing instead
            for l, default in [('semantic_data', {}),
                                 ('is_library', None),
                                 ('library_state_name', None),
                                 ('library_name', None),
                                 ('library_path', None)]:
                execution_item[l] = item.get(l, default)

            start_item = execution_item

            collapsed_next[rid] = execution_history_items[next_[gitems[0]['history_item_id']]]['run_id']
            collapsed_items[rid] = execution_item
        elif gitems[0]['state_type'] == 'ExecutionState' or \
             gitems[0]['state_type'] == 'HierarchyState' or \
             gitems[0]['state_type'] == 'LibraryState' or \
             'Concurrency' in gitems[0]['state_type']:

            # for item in gitems:
            #     if item["description"] is not None:
            #         print(item["item_type"], item["call_type"], item["state_type"], item["state_name"])
            #     print(item["description"])

            # select call and return items for this state
            try:
                call_item = gitems[[gitems[i]['item_type'] == 'CallItem' and \
                                    gitems[i]['call_type'] == 'EXECUTE' \
                                    for i in range(len(gitems))].index(True)]
            except ValueError:
                # fall back to container call, should only happen for root state
                try:
                    call_item = gitems[[gitems[i]['item_type'] == 'CallItem' and \
                                        gitems[i]['call_type'] == 'CONTAINER' \
                                        for i in range(len(gitems))].index(True)]
                except ValueError:
                    logger.warning('Could not find a CallItem in run_id group %s\nThere will probably be log information missing on this execution branch!' % str(rid))
                    ## create dummy returnitem with the properties referenced later in this code
                    call_item = dict(description=None,
                                     history_item_id=None,
                                     path_by_name=None,
                                     state_name=None,
                                     run_id=None,
                                     state_type=None,
                                     path=None,
                                     timestamp=None,
                                     input_output_data={},
                                     scoped_data={})

            try:
                return_item = gitems[[gitems[i]['item_type'] == 'ReturnItem' and \
                                      gitems[i]['call_type'] == 'EXECUTE' \
                                      for i in range(len(gitems))].index(True)]
            except ValueError:
                # fall back to container call, should only happen for root state
                try:
                    return_item = gitems[[gitems[i]['item_type'] == 'ReturnItem' and \
                                          gitems[i]['call_type'] == 'CONTAINER' \
                                          for i in range(len(gitems))].index(True)]
                except ValueError:
                    logger.warning('Could not find a ReturnItem in run_id group %s\nThere will probably be log information missing on this execution branch!' % str(rid))
                    ## create dummy returnitem with the properties referenced later in this code
                    return_item = dict(history_item_id=None,
                                       outcome_name=None,
                                       outcome_id=None,
                                       timestamp=None,
                                       input_output_data={},
                                       scoped_data={})

            # next item (on same hierarchy level) is always after return item
            if return_item['history_item_id'] in next_:
                # no next relationship at the end of containers
                if execution_history_items[next_[return_item['history_item_id']]]['state_type'] == 'HierarchyState' and execution_history_items[next_[return_item['history_item_id']]]['item_type'] == 'ReturnItem' and execution_history_items[next_[return_item['history_item_id']]]['call_type'] == 'CONTAINER':
                    if full_next:
                        collapsed_next[rid] = execution_history_items[next_[return_item['history_item_id']]]['run_id']
                    else:
                        pass
                else:
                    collapsed_next[rid] = execution_history_items[next_[return_item['history_item_id']]]['run_id']

            # treat hierarchy level
            if call_item['history_item_id'] in previous:
                if execution_history_items[previous[call_item['history_item_id']]]['state_type'] == 'HierarchyState' and execution_history_items[previous[call_item['history_item_id']]]['item_type'] == 'CallItem':
                    prev_rid = execution_history_items[previous[call_item['history_item_id']]]['run_id']
                    collapsed_hierarchy[prev_rid] = rid

                # treat concurrency level
                if execution_history_items[previous[call_item['history_item_id']]]['item_type'] == 'ConcurrencyItem':
                    prev_rid = execution_history_items[previous[call_item['history_item_id']]]['run_id']
                    if prev_rid in collapsed_concurrent:
                        collapsed_concurrent[prev_rid].append(rid)
                    else:
                        collapsed_concurrent[prev_rid] = [rid]

            # assemble grouped item
            execution_item = {}
            ## add base properties will throw if not existing
            for l in ['description', 'path_by_name', 'state_name', 'run_id', 'state_type', 'path']:
                execution_item[l] = call_item[l]

            ## add extended properties (added in later rafcon versions),
            ## will add default value if not existing instead
            for l, default in [('semantic_data', {}),
                                 ('is_library', None),
                                 ('library_state_name', None),
                                 ('library_name', None),
                                 ('library_path', None)]:
                execution_item[l] = return_item.get(l, default)

            for l in ['outcome_name', 'outcome_id']:
                execution_item[l] = return_item[l]
            for l in ['timestamp']:
                execution_item[l+'_call'] = call_item[l]
                execution_item[l+'_return'] = return_item[l]

            def unpickle_data(data_dict):
                r = dict()
                # support backward compatibility
                if isinstance(data_dict, string_types):  # formerly data dict was a json string
                    r = json.loads(data_dict)
                else:
                    for k, v in data_dict.items():
                        if not k.startswith('!'): # ! indicates storage error
                            try:
                                r[k] = pickle.loads(v)
                            except Exception as e:
                                if throw_on_pickle_error:
                                    raise
                                elif include_erroneous_data_ports:
                                    r['!' + k] = (str(e), v)
                                else:
                                    pass # ignore
                        elif include_erroneous_data_ports:
                            r[k] = v

                return r

            execution_item['data_ins'] = unpickle_data(call_item['input_output_data'])
            execution_item['data_outs'] = unpickle_data(return_item['input_output_data'])
            execution_item['scoped_data_ins'] = unpickle_data(call_item['scoped_data'])
            execution_item['scoped_data_outs'] = unpickle_data(return_item['scoped_data'])
            execution_item['semantic_data'] = unpickle_data(execution_item['semantic_data'])

            collapsed_items[rid] = execution_item

    return start_item, collapsed_next, collapsed_concurrent, collapsed_hierarchy, collapsed_items