#!/usr/bin/env python3

r"""
Define variable manipulation functions.
"""

import os
import re

try:
    from robot.utils import DotDict
except ImportError:
    pass

import collections

import func_args as fa
import gen_misc as gm
import gen_print as gp


def create_var_dict(*args):
    r"""
    Create a dictionary whose keys/values are the arg names/arg values passed to it and return it to the
    caller.

    Note: The resulting dictionary will be ordered.

    Description of argument(s):
    *args  An unlimited number of arguments to be processed.

    Example use:

    first_name = 'Steve'
    last_name = 'Smith'
    var_dict = create_var_dict(first_name, last_name)

    gp.print_var(var_dict)

    The print-out of the resulting var dictionary is:
    var_dict:
      var_dict[first_name]:                           Steve
      var_dict[last_name]:                            Smith
    """

    try:
        result_dict = collections.OrderedDict()
    except AttributeError:
        result_dict = DotDict()

    arg_num = 1
    for arg in args:
        arg_name = gp.get_arg_name(None, arg_num, stack_frame_ix=2)
        result_dict[arg_name] = arg
        arg_num += 1

    return result_dict


default_record_delim = ":"
default_key_val_delim = "."


def join_dict(
    dict,
    record_delim=default_record_delim,
    key_val_delim=default_key_val_delim,
):
    r"""
    Join a dictionary's keys and values into a string and return the string.

    Description of argument(s):
    dict                            The dictionary whose keys and values are to be joined.
    record_delim                    The delimiter to be used to separate dictionary pairs in the resulting
                                    string.
    key_val_delim                   The delimiter to be used to separate keys from values in the resulting
                                    string.

    Example use:

    gp.print_var(var_dict)
    str1 = join_dict(var_dict)
    gp.print_var(str1)

    Program output.
    var_dict:
      var_dict[first_name]:                           Steve
      var_dict[last_name]:                            Smith
    str1:                                             first_name.Steve:last_name.Smith
    """

    format_str = "%s" + key_val_delim + "%s"
    return record_delim.join(
        [format_str % (key, value) for (key, value) in dict.items()]
    )


def split_to_dict(
    string,
    record_delim=default_record_delim,
    key_val_delim=default_key_val_delim,
):
    r"""
    Split a string into a dictionary and return it.

    This function is the complement to join_dict.

    Description of argument(s):
    string                          The string to be split into a dictionary.  The string must have the
                                    proper delimiters in it.  A string created by join_dict would qualify.
    record_delim                    The delimiter to be used to separate dictionary pairs in the input string.
    key_val_delim                   The delimiter to be used to separate keys/values in the input string.

    Example use:

    gp.print_var(str1)
    new_dict = split_to_dict(str1)
    gp.print_var(new_dict)


    Program output.
    str1:                                             first_name.Steve:last_name.Smith
    new_dict:
      new_dict[first_name]:                           Steve
      new_dict[last_name]:                            Smith
    """

    try:
        result_dict = collections.OrderedDict()
    except AttributeError:
        result_dict = DotDict()

    raw_keys_values = string.split(record_delim)
    for key_value in raw_keys_values:
        key_value_list = key_value.split(key_val_delim)
        try:
            result_dict[key_value_list[0]] = key_value_list[1]
        except IndexError:
            result_dict[key_value_list[0]] = ""

    return result_dict


def create_file_path(file_name_dict, dir_path="/tmp/", file_suffix=""):
    r"""
    Create a file path using the given parameters and return it.

    Description of argument(s):
    file_name_dict                  A dictionary with keys/values which are to appear as part of the file
                                    name.
    dir_path                        The dir_path that is to appear as part of the file name.
    file_suffix                     A suffix to be included as part of the file name.
    """

    dir_path = gm.add_trailing_slash(dir_path)
    return dir_path + join_dict(file_name_dict) + file_suffix


def parse_file_path(file_path):
    r"""
    Parse a file path created by create_file_path and return the result as a dictionary.

    This function is the complement to create_file_path.

    Description of argument(s):
    file_path                       The file_path.

    Example use:
    gp.print_var(boot_results_file_path)
    file_path_data = parse_file_path(boot_results_file_path)
    gp.print_var(file_path_data)

    Program output.

    boot_results_file_path:
    /tmp/pgm_name.obmc_boot_test:openbmc_nickname.beye6:master_pid.2039:boot_results
    file_path_data:
      file_path_data[dir_path]:                       /tmp/
      file_path_data[pgm_name]:                       obmc_boot_test
      file_path_data[openbmc_nickname]:               beye6
      file_path_data[master_pid]:                     2039
      file_path_data[boot_results]:
    """

    try:
        result_dict = collections.OrderedDict()
    except AttributeError:
        result_dict = DotDict()

    dir_path = os.path.dirname(file_path) + os.sep
    file_path = os.path.basename(file_path)

    result_dict["dir_path"] = dir_path

    result_dict.update(split_to_dict(file_path))

    return result_dict


def parse_key_value(string, delim=":", strip=" ", to_lower=1, underscores=1):
    r"""
    Parse a key/value string and return as a key/value tuple.

    This function is useful for parsing a line of program output or data that is in the following form:
    <key or variable name><delimiter><value>

    An example of a key/value string would be as follows:

    Current Limit State: No Active Power Limit

    In the example shown, the delimiter is ":".  The resulting key would be as follows:
    Current Limit State

    Note: If one were to take the default values of to_lower=1 and underscores=1, the resulting key would be
    as follows:
    current_limit_state

    The to_lower and underscores arguments are provided for those who wish to have their key names have the
    look and feel of python variable names.

    The resulting value for the example above would be as follows:
    No Active Power Limit

    Another example:
    name=Mike

    In this case, the delim would be "=", the key is "name" and the value is "Mike".

    Description of argument(s):
    string                          The string to be parsed.
    delim                           The delimiter which separates the key from the value.
    strip                           The characters (if any) to strip from the beginning and end of both the
                                    key and the value.
    to_lower                        Change the key name to lower case.
    underscores                     Change any blanks found in the key name to underscores.
    """

    pair = string.split(delim)

    key = pair[0].strip(strip)
    if len(pair) == 0:
        value = ""
    else:
        value = delim.join(pair[1:]).strip(strip)

    if to_lower:
        key = key.lower()
    if underscores:
        key = re.sub(r" ", "_", key)

    return key, value


def key_value_list_to_dict(key_value_list, process_indent=0, **args):
    r"""
    Convert a list containing key/value strings or tuples to a dictionary and return it.

    See docstring of parse_key_value function for details on key/value strings.

    Example usage:

    For the following value of key_value_list:

    key_value_list:
      [0]:          Current Limit State: No Active Power Limit
      [1]:          Exception actions:   Hard Power Off & Log Event to SEL
      [2]:          Power Limit:         0 Watts
      [3]:          Correction time:     0 milliseconds
      [4]:          Sampling period:     0 seconds

    And the following call in python:

    power_limit = key_value_outbuf_to_dict(key_value_list)

    The resulting power_limit directory would look like this:

    power_limit:
      [current_limit_state]:        No Active Power Limit
      [exception_actions]:          Hard Power Off & Log Event to SEL
      [power_limit]:                0 Watts
      [correction_time]:            0 milliseconds
      [sampling_period]:            0 seconds

    For the following list:

    headers:
      headers[0]:
        headers[0][0]:           content-length
        headers[0][1]:           559
      headers[1]:
        headers[1][0]:           x-xss-protection
        headers[1][1]:           1; mode=block

    And the following call in python:

    headers_dict = key_value_list_to_dict(headers)

    The resulting headers_dict would look like this:

    headers_dict:
      [content-length]:          559
      [x-xss-protection]:        1; mode=block

    Another example containing a sub-list (see process_indent description below):

    Provides Device SDRs      : yes
    Additional Device Support :
        Sensor Device
        SEL Device
        FRU Inventory Device
        Chassis Device

    Note that the 2 qualifications for containing a sub-list are met: 1) 'Additional Device Support' has no
    value and 2) The entries below it are indented.  In this case those entries contain no delimiters (":")
    so they will be processed as a list rather than as a dictionary.  The result would be as follows:

    mc_info:
      mc_info[provides_device_sdrs]:            yes
      mc_info[additional_device_support]:
        mc_info[additional_device_support][0]:  Sensor Device
        mc_info[additional_device_support][1]:  SEL Device
        mc_info[additional_device_support][2]:  FRU Inventory Device
        mc_info[additional_device_support][3]:  Chassis Device

    Description of argument(s):
    key_value_list                  A list of key/value strings.  (See docstring of parse_key_value function
                                    for details).
    process_indent                  This indicates that indented sub-dictionaries and sub-lists are to be
                                    processed as such.  An entry may have a sub-dict or sub-list if 1) It has
                                    no value other than blank 2) There are entries below it that are
                                    indented.  Note that process_indent is not allowed for a list of tuples
                                    (vs. a list of key/value strings).
    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
                                    parse_key_value function for details).
    """

    try:
        result_dict = collections.OrderedDict()
    except AttributeError:
        result_dict = DotDict()

    if not process_indent:
        for entry in key_value_list:
            if type(entry) is tuple:
                key, value = entry
            else:
                key, value = parse_key_value(entry, **args)
            result_dict[key] = value
        return result_dict

    # Process list while paying heed to indentation.
    delim = args.get("delim", ":")
    # Initialize "parent_" indentation level variables.
    parent_indent = len(key_value_list[0]) - len(key_value_list[0].lstrip())
    sub_list = []
    for entry in key_value_list:
        key, value = parse_key_value(entry, **args)

        indent = len(entry) - len(entry.lstrip())

        if indent > parent_indent and parent_value == "":
            # This line is indented compared to the parent entry and the parent entry has no value.
            # Append the entry to sub_list for later processing.
            sub_list.append(str(entry))
            continue

        # Process any outstanding sub_list and add it to result_dict[parent_key].
        if len(sub_list) > 0:
            if any(delim in word for word in sub_list):
                # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary.
                result_dict[parent_key] = key_value_list_to_dict(
                    sub_list, **args
                )
            else:
                result_dict[parent_key] = list(map(str.strip, sub_list))
            del sub_list[:]

        result_dict[key] = value

        parent_key = key
        parent_value = value
        parent_indent = indent

    # Any outstanding sub_list to be processed?
    if len(sub_list) > 0:
        if any(delim in word for word in sub_list):
            # If delim is found anywhere in the sub_list, we'll process as a sub-dictionary.
            result_dict[parent_key] = key_value_list_to_dict(sub_list, **args)
        else:
            result_dict[parent_key] = list(map(str.strip, sub_list))

    return result_dict


def key_value_outbuf_to_dict(out_buf, **args):
    r"""
    Convert a buffer with a key/value string on each line to a dictionary and return it.

    Each line in the out_buf should end with a \n.

    See docstring of parse_key_value function for details on key/value strings.

    Example usage:

    For the following value of out_buf:

    Current Limit State: No Active Power Limit
    Exception actions:   Hard Power Off & Log Event to SEL
    Power Limit:         0 Watts
    Correction time:     0 milliseconds
    Sampling period:     0 seconds

    And the following call in python:

    power_limit = key_value_outbuf_to_dict(out_buf)

    The resulting power_limit directory would look like this:

    power_limit:
      [current_limit_state]:        No Active Power Limit
      [exception_actions]:          Hard Power Off & Log Event to SEL
      [power_limit]:                0 Watts
      [correction_time]:            0 milliseconds
      [sampling_period]:            0 seconds

    Description of argument(s):
    out_buf                         A buffer with a key/value string on each line. (See docstring of
                                    parse_key_value function for details).
    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
                                    parse_key_value function for details).
    """

    # Create key_var_list and remove null entries.
    key_var_list = list(filter(None, out_buf.split("\n")))
    return key_value_list_to_dict(key_var_list, **args)


def key_value_outbuf_to_dicts(out_buf, **args):
    r"""
    Convert a buffer containing multiple sections with key/value strings on each line to a list of
    dictionaries and return it.

    Sections in the output are delimited by blank lines.

    Example usage:

    For the following value of out_buf:

    Maximum User IDs     : 15
    Enabled User IDs     : 1

    User ID              : 1
    User Name            : root
    Fixed Name           : No
    Access Available     : callback
    Link Authentication  : enabled
    IPMI Messaging       : enabled
    Privilege Level      : ADMINISTRATOR
    Enable Status        : enabled

    User ID              : 2
    User Name            :
    Fixed Name           : No
    Access Available     : call-in / callback
    Link Authentication  : disabled
    IPMI Messaging       : disabled
    Privilege Level      : NO ACCESS
    Enable Status        : disabled

    And the following call in python:

    user_info = key_value_outbuf_to_dicts(out_buf)

    The resulting user_info list would look like this:

    user_info:
      [0]:
        [maximum_user_ids]:      15
        [enabled_user_ids]:      1
      [1]:
        [user_id]:               1
        [user_name]:             root
        [fixed_name]:            No
        [access_available]:      callback
        [link_authentication]:   enabled
        [ipmi_messaging]:        enabled
        [privilege_level]:       ADMINISTRATOR
        [enable_status]:         enabled
      [2]:
        [user_id]:               2
        [user_name]:
        [fixed_name]:            No
        [access_available]:      call-in / callback
        [link_authentication]:   disabled
        [ipmi_messaging]:        disabled
        [privilege_level]:       NO ACCESS
        [enable_status]:         disabled

    Description of argument(s):
    out_buf                         A buffer with multiple secionts of key/value strings on each line.
                                    Sections are delimited by one or more blank lines (i.e. line feeds). (See
                                    docstring of parse_key_value function for details).
    **args                          Arguments to be interpreted by parse_key_value.  (See docstring of
                                    parse_key_value function for details).
    """
    return [
        key_value_outbuf_to_dict(x, **args)
        for x in re.split("\n[\n]+", out_buf)
    ]


def create_field_desc_regex(line):
    r"""
    Create a field descriptor regular expression based on the input line and return it.

    This function is designed for use by the list_to_report function (defined below).

    Example:

    Given the following input line:

    --------   ------------ ------------------ ------------------------

    This function will return this regular expression:

    (.{8})   (.{12}) (.{18}) (.{24})

    This means that other report lines interpreted using the regular expression are expected to have:
    - An 8 character field
    - 3 spaces
    - A 12 character field
    - One space
    - An 18 character field
    - One space
    - A 24 character field

    Description of argument(s):
    line                            A line consisting of dashes to represent fields and spaces to delimit
                                    fields.
    """

    # Split the line into a descriptors list.  Example:
    # descriptors:
    #  descriptors[0]:            --------
    #  descriptors[1]:
    #  descriptors[2]:
    #  descriptors[3]:            ------------
    #  descriptors[4]:            ------------------
    #  descriptors[5]:            ------------------------
    descriptors = line.split(" ")

    # Create regexes list.  Example:
    # regexes:
    #  regexes[0]:                (.{8})
    #  regexes[1]:
    #  regexes[2]:
    #  regexes[3]:                (.{12})
    #  regexes[4]:                (.{18})
    #  regexes[5]:                (.{24})
    regexes = []
    for descriptor in descriptors:
        if descriptor == "":
            regexes.append("")
        else:
            regexes.append("(.{" + str(len(descriptor)) + "})")

    # Join the regexes list into a regex string.
    field_desc_regex = " ".join(regexes)

    return field_desc_regex


def list_to_report(report_list, to_lower=1, field_delim=None):
    r"""
    Convert a list containing report text lines to a report "object" and return it.

    The first entry in report_list must be a header line consisting of column names delimited by white space.
    No column name may contain white space.  The remaining report_list entries should contain tabular data
    which corresponds to the column names.

    A report object is a list where each entry is a dictionary whose keys are the field names from the first
    entry in report_list.

    Example:
    Given the following report_list as input:

    rl:
      rl[0]: Filesystem           1K-blocks      Used Available Use% Mounted on
      rl[1]: dev                     247120         0    247120   0% /dev
      rl[2]: tmpfs                   248408     79792    168616  32% /run

    This function will return a list of dictionaries as shown below:

    df_report:
      df_report[0]:
        [filesystem]:                  dev
        [1k-blocks]:                   247120
        [used]:                        0
        [available]:                   247120
        [use%]:                        0%
        [mounted]:                     /dev
      df_report[1]:
        [filesystem]:                  dev
        [1k-blocks]:                   247120
        [used]:                        0
        [available]:                   247120
        [use%]:                        0%
        [mounted]:                     /dev

    Notice that because "Mounted on" contains a space, "on" would be considered the 7th field.  In this case,
    there is never any data in field 7 so things work out nicely.  A caller could do some pre-processing if
    desired (e.g. change "Mounted on" to "Mounted_on").

    Example 2:

    If the 2nd line of report data is a series of dashes and spaces as in the following example, that line
    will serve to delineate columns.

    The 2nd line of data is like this:
    ID                              status       size               tool,clientid,userid
    -------- ------------ ------------------ ------------------------
    20000001 in progress  0x7D0              ,,

    Description of argument(s):
    report_list                     A list where each entry is one line of output from a report.  The first
                                    entry must be a header line which contains column names.  Column names
                                    may not contain spaces.
    to_lower                        Change the resulting key names to lower case.
    field_delim                     Indicates that there are field delimiters in report_list entries (which
                                    should be removed).
    """

    if len(report_list) <= 1:
        # If we don't have at least a descriptor line and one line of data, return an empty array.
        return []

    if field_delim is not None:
        report_list = [re.sub("\\|", "", line) for line in report_list]

    header_line = report_list[0]
    if to_lower:
        header_line = header_line.lower()

    field_desc_regex = ""
    if re.match(r"^-[ -]*$", report_list[1]):
        # We have a field descriptor line (as shown in example 2 above).
        field_desc_regex = create_field_desc_regex(report_list[1])
        field_desc_len = len(report_list[1])
        pad_format_string = "%-" + str(field_desc_len) + "s"
        # The field descriptor line has served its purpose.  Deleting it.
        del report_list[1]

    # Process the header line by creating a list of column names.
    if field_desc_regex == "":
        columns = header_line.split()
    else:
        # Pad the line with spaces on the right to facilitate processing with field_desc_regex.
        header_line = pad_format_string % header_line
        columns = list(
            map(str.strip, re.findall(field_desc_regex, header_line)[0])
        )

    report_obj = []
    for report_line in report_list[1:]:
        if field_desc_regex == "":
            line = report_line.split()
        else:
            # Pad the line with spaces on the right to facilitate processing with field_desc_regex.
            report_line = pad_format_string % report_line
            line = list(
                map(str.strip, re.findall(field_desc_regex, report_line)[0])
            )
        try:
            line_dict = collections.OrderedDict(zip(columns, line))
        except AttributeError:
            line_dict = DotDict(zip(columns, line))
        report_obj.append(line_dict)

    return report_obj


def outbuf_to_report(out_buf, **args):
    r"""
    Convert a text buffer containing report lines to a report "object" and return it.

    Refer to list_to_report (above) for more details.

    Example:

    Given the following out_buf:

    Filesystem                      1K-blocks      Used Available Use% Mounted on
    dev                             247120         0    247120   0% /dev
    tmpfs                           248408     79792    168616  32% /run

    This function will return a list of dictionaries as shown below:

    df_report:
      df_report[0]:
        [filesystem]:                  dev
        [1k-blocks]:                   247120
        [used]:                        0
        [available]:                   247120
        [use%]:                        0%
        [mounted]:                     /dev
      df_report[1]:
        [filesystem]:                  dev
        [1k-blocks]:                   247120
        [used]:                        0
        [available]:                   247120
        [use%]:                        0%
        [mounted]:                     /dev

    Other possible uses:
    - Process the output of a ps command.
    - Process the output of an ls command (the caller would need to supply column names)

    Description of argument(s):
    out_buf                         A text report.  The first line must be a header line which contains
                                    column names.  Column names may not contain spaces.
    **args                          Arguments to be interpreted by list_to_report.  (See docstring of
                                    list_to_report function for details).
    """

    report_list = list(filter(None, out_buf.split("\n")))
    return list_to_report(report_list, **args)


def nested_get(key_name, structure):
    r"""
    Return a list of all values from the nested structure that have the given key name.

    Example:

    Given a dictionary structure named "personnel" with the following contents:

    personnel:
      [manager]:
        [last_name]:             Doe
        [first_name]:            John
      [accountant]:
        [last_name]:             Smith
        [first_name]:            Will

    The following code...

    last_names = nested_get('last_name', personnel)
    print_var(last_names)

    Would result in the following data returned:

    last_names:
      last_names[0]:             Doe
      last_names[1]:             Smith

    Description of argument(s):
    key_name                        The key name (e.g. 'last_name').
    structure                       Any nested combination of lists or dictionaries (e.g. a dictionary, a
                                    dictionary of dictionaries, a list of dictionaries, etc.).  This function
                                    will locate the given key at any level within the structure and include
                                    its value in the returned list.
    """

    result = []
    if type(structure) is list:
        for entry in structure:
            result += nested_get(key_name, entry)
        return result
    elif gp.is_dict(structure):
        for key, value in structure.items():
            result += nested_get(key_name, value)
            if key == key_name:
                result.append(value)

    return result


def match_struct(structure, match_dict, regex=False):
    r"""
    Return True or False to indicate whether the structure matches the match dictionary.

    Example:

    Given a dictionary structure named "personnel" with the following contents:

    personnel:
      [manager]:
        [last_name]:             Doe
        [first_name]:            John
      [accountant]:
        [last_name]:             Smith
        [first_name]:            Will

    The following call would return True.

    match_struct(personnel, {'last_name': '^Doe$'}, regex=True)

    Whereas the following call would return False.

    match_struct(personnel, {'last_name': 'Johnson'}, regex=True)

    Description of argument(s):
    structure                       Any nested combination of lists or dictionaries.  See the prolog of
                                    get_nested() for details.
    match_dict                      Each key/value pair in match_dict must exist somewhere in the structure
                                    for the structure to be considered a match.  A match value of None is
                                    considered a special case where the structure would be considered a match
                                    only if the key in question is found nowhere in the structure.
    regex                           Indicates whether the values in the match_dict should be interpreted as
                                    regular expressions.
    """

    # The structure must match for each match_dict entry to be considered a match.  Therefore, any failure
    # to match is grounds for returning False.
    for match_key, match_value in match_dict.items():
        struct_key_values = nested_get(match_key, structure)
        if match_value is None:
            # Handle this as special case.
            if len(struct_key_values) != 0:
                return False
        else:
            if len(struct_key_values) == 0:
                return False
            if regex:
                matches = [
                    x
                    for x in struct_key_values
                    if re.search(match_value, str(x))
                ]
                if not matches:
                    return False
            elif match_value not in struct_key_values:
                return False

    return True


def filter_struct(structure, filter_dict, regex=False, invert=False):
    r"""
    Filter the structure by removing any entries that do NOT contain the keys/values specified in filter_dict
    and return the result.

    The selection process is directed only at the first-level entries of the structure.

    Example:

    Given a dictionary named "properties" that has the following structure:

    properties:
      [/redfish/v1/Systems/system/Processors]:
        [Members]:
          [0]:
            [@odata.id]:                              /redfish/v1/Systems/system/Processors/cpu0
          [1]:
            [@odata.id]:                              /redfish/v1/Systems/system/Processors/cpu1
      [/redfish/v1/Systems/system/Processors/cpu0]:
        [Status]:
          [State]:                                    Enabled
          [Health]:                                   OK
      [/redfish/v1/Systems/system/Processors/cpu1]:
        [Status]:
          [State]:                                    Enabled
          [Health]:                                   Bad

    The following call:

    properties = filter_struct(properties, "[('Health', 'OK')]")

    Would return a new properties dictionary that looks like this:

    properties:
      [/redfish/v1/Systems/system/Processors/cpu0]:
        [Status]:
          [State]:                                    Enabled
          [Health]:                                   OK

    Note that the first item in the original properties directory had no key anywhere in the structure named
    "Health".  Therefore, that item failed to make the cut.  The next item did have a key named "Health"
    whose value was "OK" so it was included in the new structure.  The third item had a key named "Health"
    but its value was not "OK" so it also failed to make the cut.

    Description of argument(s):
    structure                       Any nested combination of lists or dictionaries.  See the prolog of
                                    get_nested() for details.
    filter_dict                     For each key/value pair in filter_dict, each entry in structure must
                                    contain the same key/value pair at some level.  A filter_dict value of
                                    None is treated as a special case.  Taking the example shown above,
                                    [('State', None)] would mean that the result should only contain records
                                    that have no State key at all.
    regex                           Indicates whether the values in the filter_dict should be interpreted as
                                    regular expressions.
    invert                          Invert the results.  Instead of including only matching entries in the
                                    results, include only NON-matching entries in the results.
    """

    # Convert filter_dict from a string containing a python object definition to an actual python object (if
    # warranted).
    filter_dict = fa.source_to_object(filter_dict)

    # Determine whether structure is a list or a dictionary and process accordingly.  The result returned
    # will be of the same type as the structure.
    if type(structure) is list:
        result = []
        for element in structure:
            if match_struct(element, filter_dict, regex) != invert:
                result.append(element)
    else:
        try:
            result = collections.OrderedDict()
        except AttributeError:
            result = DotDict()
        for struct_key, struct_value in structure.items():
            if match_struct(struct_value, filter_dict, regex) != invert:
                result[struct_key] = struct_value

    return result


def split_dict_on_key(split_key, dictionary):
    r"""
    Split a dictionary into two dictionaries based on the first occurrence of the split key and return the
    resulting sub-dictionaries.

    Example:
    dictionary = {'one': 1, 'two': 2, 'three':3, 'four':4}
    dict1, dict2 = split_dict_on_key('three', dictionary)
    pvars(dictionary, dict1, dict2)

    Output:
    dictionary:
      [one]:                                          1
      [two]:                                          2
      [three]:                                        3
      [four]:                                         4
    dict1:
      [one]:                                          1
      [two]:                                          2
    dict2:
      [three]:                                        3
      [four]:                                         4

    Description of argument(s):
    split_key                       The key value to be used to determine where the dictionary should be
                                    split.
    dictionary                      The dictionary to be split.
    """
    dict1 = {}
    dict2 = {}
    found_split_key = False
    for key in list(dictionary.keys()):
        if key == split_key:
            found_split_key = True
        if found_split_key:
            dict2[key] = dictionary[key]
        else:
            dict1[key] = dictionary[key]
    return dict1, dict2