#!/usr/bin/env python2

import os
import yaml
from collections import OrderedDict

from ceph_ansible_copilot.utils import get_used_roles

description = "use the existing site.yml, or create one from the sample"
yml_file = '/usr/share/ceph-ansible/site.yml'

# The sample file includes a host entry for each role, but if the role isn't
# supported by copilot, the playbook generates warning messages that disrupt
# the UI. To address this, roles that are NOT supported by copilot, are
# deleted from the generated site.yml file. Also, the all.yml vars file is
# not found by default in Ansible 2.4 (2.3 is fine), so this plugin also
# adds a task to use include_vars to ensure all.yml is included in the play.


def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict):

    class OrderedLoader(Loader):
        pass

    def construct_mapping(loader, node):
        loader.flatten_mapping(node)
        return object_pairs_hook(loader.construct_pairs(node))
    OrderedLoader.add_constructor(
        yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
        construct_mapping)
    return yaml.load(stream, OrderedLoader)


def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwds):

    class OrderedDumper(Dumper):
        pass

    def _dict_representer(dumper, data):
        return dumper.represent_mapping(
            yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
            data.items())
    OrderedDumper.add_representer(OrderedDict, _dict_representer)
    return yaml.dump(data, stream, OrderedDumper, **kwds)


def plugin_main(config=None, mode='add'):

    if not config:
        raise ValueError("Config object not received from caller")

    if not os.path.exists(yml_file):
        # create a copy from the sample file
        sample = '{}.sample'.format(yml_file)
        if os.path.exists(sample):

            if isinstance(config, list):
                used_roles = config
            else:
                used_roles = get_used_roles(config)

            yml_data = load_yaml(sample)

            updated_yaml = process_yaml(yml_data, used_roles)
            updated_yaml = manage_all_yml(updated_yaml, mode=mode)

            write_yaml(yml_file, updated_yaml)
        else:
            raise EnvironmentError("sample file for site.yml not found")
    else:
        # site.yml already exists, so we just make sure the include for
        # all.yml is not there
        yaml_data = load_yaml(yml_file)
        updated_yaml_data = manage_all_yml(yaml_data, mode=mode)
        if updated_yaml_data:
            write_yaml(yml_file, updated_yaml_data)

    return None


def manage_all_yml(yaml_data, mode):

    all_vars_file = '/usr/share/ceph-ansible/group_vars/all.yml'
    pre_req_tasks = yaml_data[0]['tasks']

    if mode == 'add':
        all_vars = OrderedDict([('name', 'Add all.yml'),
                               ('include_vars',
                                OrderedDict([('file',
                                              all_vars_file)]))])
        pre_req_tasks.append(all_vars)
        return yaml_data
    elif mode == 'delete':
        p = [item for item in pre_req_tasks if item['name'] != 'Add all.yml']
        yaml_data[0]['tasks'] = p
        if len(yaml_data[0]['tasks']) == len(pre_req_tasks):
            return None
        else:
            return yaml_data
    else:
        raise ValueError("manage_all_yml passed invalid mode")


def load_yaml(yml_filename):
    with open(yml_filename, "r") as stream:
        yaml_data = ordered_load(stream, yaml.SafeLoader)

    return yaml_data


def process_yaml(yaml_data, used_roles):
    """
    Read the site.yml.sample deleting any role that is not used by copilot
    (i.e. doesn't match an item in the used_roles parameter), and explicitly
    include the all.yml global_vars file
    :param yaml_data: (list) load yamldata
    :param used_roles: (list) list of role names (mon, rgw, osd)
    :return: edited yaml data to write to the site.yml file
    """

    supported_roles = ["{}s".format(role) for role in used_roles]
    if 'mons' in supported_roles:
        supported_roles.append('mgrs')

    for item in yaml_data:
        if 'hosts' not in item:
            # only interested yaml tasks that defined the hosts, skip the rest
            continue

        # yaml section has a hosts definition, so check it
        hosts = item['hosts']
        if isinstance(hosts, str):
            # single role
            if hosts in supported_roles:
                continue
            # this role is not defined by copilot, so drop the entry
            item['deleteme'] = True

        else:
            # multiple roles
            item['hosts'] = [role for role in hosts if role in supported_roles]

    return [item for item in yaml_data if not item.get('deleteme')]


def write_yaml(yaml_file, yaml_data):

    with open(yaml_file, "w", 0) as out:
        ordered_dump(yaml_data,
                     Dumper=yaml.SafeDumper,
                     stream=out,
                     default_flow_style=False,
                     explicit_start=True)


if __name__ == '__main__':
    plugin_main(config=['mon', 'osd', 'rgw', 'mds'])