# List of valid osd flags
OSD_FLAGS = [
    'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill',
    'norecover', 'noscrub', 'nodeep-scrub',
]

# Implemented osd commands
OSD_IMPLEMENTED_COMMANDS = [
    'scrub', 'deep-scrub', 'repair'
]

# Valid values for the 'var' argument to 'ceph osd pool set'
POOL_PROPERTIES_1 = [
    'size', 'min_size', 'pg_num',
    'crush_rule', 'hashpspool',
]

POOL_PROPERTIES_2 = [
    'pgp_num'
]

POOL_PROPERTIES = POOL_PROPERTIES_1 + POOL_PROPERTIES_2

# Valid values for the 'ceph osd pool set-quota' command
POOL_QUOTA_PROPERTIES = [
    ('quota_max_bytes', 'max_bytes'),
    ('quota_max_objects', 'max_objects'),
]

POOL_ARGS = POOL_PROPERTIES + [x for x,_ in POOL_QUOTA_PROPERTIES]


# Transform command to a human readable form
def humanify_command(command):
    out = [command['prefix']]

    for arg, val in command.iteritems():
        if arg != 'prefix':
            out.append("%s=%s" % (str(arg), str(val)))

    return " ".join(out)


def invalid_pool_args(args):
    invalid = []
    for arg in args:
        if arg not in POOL_ARGS:
            invalid.append(arg)

    return invalid


def pool_update_commands(pool_name, args):
    commands = [[], []]

    # We should increase pgp_num when we are re-setting pg_num
    if 'pg_num' in args and 'pgp_num' not in args:
        args['pgp_num'] = args['pg_num']

    # Run the first pool set and quota properties in parallel
    for var in POOL_PROPERTIES_1:
        if var in args:
            commands[0].append({
                'prefix': 'osd pool set',
                'pool': pool_name,
                'var': var,
                'val': args[var],
            })

    for (var, field) in POOL_QUOTA_PROPERTIES:
        if var in args:
            commands[0].append({
                'prefix': 'osd pool set-quota',
                'pool': pool_name,
                'field': field,
                'val': str(args[var]),
            })

    # The second pool set properties need to be run after the first wave
    for var in POOL_PROPERTIES_2:
        if var in args:
            commands[1].append({
                'prefix': 'osd pool set',
                'pool': pool_name,
                'var': var,
                'val': args[var],
            })

    return commands

def crush_rule_osds(node_buckets, rule):
    nodes_by_id = dict((b['id'], b) for b in node_buckets)

    def _gather_leaf_ids(node):
        if node['id'] >= 0:
            return set([node['id']])

        result = set()
        for item in node['items']:
            if item['id'] >= 0:
                result.add(item['id'])
            else:
                result |= _gather_leaf_ids(nodes_by_id[item['id']])

        return result

    def _gather_descendent_ids(node, typ):
        result = set()
        for item in node['items']:
            if item['id'] >= 0:
                if typ == "osd":
                    result.add(item['id'])
            else:
                child_node = nodes_by_id[item['id']]
                if child_node['type_name'] == typ:
                    result.add(child_node['id'])
                elif 'items' in child_node:
                    result |= _gather_descendent_ids(child_node, typ)

        return result

    def _gather_osds(root, steps):
        if root['id'] >= 0:
            return set([root['id']])

        osds = set()
        step = steps[0]
        if step['op'] == 'choose_firstn':
            # Choose all descendents of the current node of type 'type'
            descendent_ids = _gather_descendent_ids(root, step['type'])
            for node_id in descendent_ids:
                if node_id >= 0:
                    osds.add(node_id)
                else:
                    for desc_node in nodes_by_id[node_id]:
                        osds |= _gather_osds(desc_node, steps[1:])
        elif step['op'] == 'chooseleaf_firstn':
            # Choose all descendents of the current node of type 'type',
            # and select all leaves beneath those
            descendent_ids = _gather_descendent_ids(root, step['type'])
            for node_id in descendent_ids:
                if node_id >= 0:
                    osds.add(node_id)
                else:
                    for desc_node in nodes_by_id[node_id]['items']:
                        # Short circuit another iteration to find the emit
                        # and assume anything we've done a chooseleaf on
                        # is going to be part of the selected set of osds
                        osds |= _gather_leaf_ids(desc_node)
        elif step['op'] == 'emit':
            if root['id'] >= 0:
                osds |= root['id']

        return osds

    osds = set()
    for i, step in enumerate(rule['steps']):
        if step['op'] == 'take':
            osds |= _gather_osds(nodes_by_id[step['item']], rule['steps'][i + 1:])
    return osds
