import copy


def update_with_default_dict(main_dict, level1_key, default_dict, in_place=False):
    """
    takes a dictionary of the form:
    {
    level1_key1:
        level2_key1:
            level3_key1: value1
            level3_key2: value2
        level2_key2:
            level3_key1: value1
            level3_key2: value2
    level1_key2:
        ...
    }
    and updates all dictionary under the given level1 key with the defaults given in default_dict if necessary.
    The level3 key-value pairs are updated with the key-value pairs in the default dict if the level3 dictionary
    is located under the level1 key given.

    :param main_dict: The main dictionary in which to make the changes
    :param level1_key: The key of the dictionary under which all updates will take place
    :param default_dict: the dictionary used to update the level3 dictionaries.
    :param in_place: if True, the changes are made in the given dictionary, else a copy is used.
    :return: Dictionary with the updated values if not in_place else nothing
    """

    if not in_place:
        main_dict = copy.deepcopy(main_dict)

    if type(main_dict[level1_key]) is list:
        level2_new = {}
        for f in main_dict[level1_key]:
            level2_new[f] = default_dict

        main_dict[level1_key] = level2_new

    else:
        for level2_key in main_dict[level1_key].keys():
            if main_dict[level1_key][level2_key] is None:
                # if there is no settings dictionary, just add the default
                main_dict[level1_key][level2_key] = default_dict
            else:
                # if there is a dictionary, update the default dictionary with the provided dictionary
                default_dict_ = default_dict.copy()
                default_dict_.update(main_dict[level1_key][level2_key])
                main_dict[level1_key][level2_key] = default_dict_

    if not in_place: return main_dict