# -*- coding: utf-8 -*-
'''
Manage ini files
================

:maintainer: <akilesh1597@gmail.com>
:maturity: new
:depends: re
:platform: all

'''

# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals

# Import Salt libs
from salt.ext import six
from salt.utils.odict import OrderedDict

__virtualname__ = 'ini'


def __virtual__():
    '''
    Only load if the ini module is available
    '''
    return __virtualname__ if 'ini.set_option' in __salt__ else False


def options_present(name, sections=None, separator='=', strict=False):
    '''
    .. code-block:: yaml

        /home/saltminion/api-paste.ini:
          ini.options_present:
            - separator: '='
            - strict: True
            - sections:
                test:
                  testkey: 'testval'
                  secondoption: 'secondvalue'
                test1:
                  testkey1: 'testval121'

    options present in file and not specified in sections
    dict will be untouched, unless `strict: True` flag is
    used

    changes dict will contain the list of changes made
    '''
    ret = {'name': name,
           'changes': {},
           'result': True,
           'comment': 'No anomaly detected'
           }
    if __opts__['test']:
        ret['comment'] = ''
    # pylint: disable=too-many-nested-blocks
    try:
        changes = {}
        if sections:
            options = {}
            for sname, sbody in sections.items():
                if not isinstance(sbody, (dict, OrderedDict)):
                    options.update({sname: sbody})
            cur_ini = __salt__['ini.get_ini'](name, separator)
            original_top_level_opts = {}
            original_sections = {}
            for key, val in cur_ini.items():
                if isinstance(val, (dict, OrderedDict)):
                    original_sections.update({key: val})
                else:
                    original_top_level_opts.update({key: val})
            if __opts__['test']:
                for option in options:
                    if option in original_top_level_opts:
                        if six.text_type(original_top_level_opts[option]) == six.text_type(options[option]):
                            ret['comment'] += 'Unchanged key {0}.\n'.format(option)
                        else:
                            ret['comment'] += 'Changed key {0}.\n'.format(option)
                            ret['result'] = None
                    else:
                        ret['comment'] += 'Changed key {0}.\n'.format(option)
                        ret['result'] = None
            else:
                options_updated = __salt__['ini.set_option'](name, options, separator)
                changes.update(options_updated)
            if strict:
                for opt_to_remove in set(original_top_level_opts).difference(options):
                    if __opts__['test']:
                        ret['comment'] += 'Removed key {0}.\n'.format(opt_to_remove)
                        ret['result'] = None
                    else:
                        __salt__['ini.remove_option'](name, None, opt_to_remove, separator)
                        changes.update({opt_to_remove: {'before': original_top_level_opts[opt_to_remove],
                                                        'after': None}})
            for section_name, section_body in [(sname, sbody) for sname, sbody in sections.items()
                                               if isinstance(sbody, (dict, OrderedDict))]:
                section_descr = ' in section ' + section_name if section_name else ''
                changes[section_name] = {}
                if strict:
                    original = cur_ini.get(section_name, {})
                    for key_to_remove in set(original.keys()).difference(section_body.keys()):
                        orig_value = original_sections.get(section_name, {}).get(key_to_remove, '#-#-')
                        if __opts__['test']:
                            ret['comment'] += 'Deleted key {0}{1}.\n'.format(key_to_remove, section_descr)
                            ret['result'] = None
                        else:
                            __salt__['ini.remove_option'](name, section_name, key_to_remove, separator)
                            changes[section_name].update({key_to_remove: ''})
                            changes[section_name].update({key_to_remove: {'before': orig_value,
                                                                          'after': None}})
                if __opts__['test']:
                    for option in section_body:
                        if six.text_type(section_body[option]) == \
                                six.text_type(original_sections.get(section_name, {}).get(option, '#-#-')):
                            ret['comment'] += 'Unchanged key {0}{1}.\n'.format(option, section_descr)
                        else:
                            ret['comment'] += 'Changed key {0}{1}.\n'.format(option, section_descr)
                            ret['result'] = None
                else:
                    options_updated = __salt__['ini.set_option'](name, {section_name: section_body}, separator)
                    if options_updated:
                        changes[section_name].update(options_updated[section_name])
                    if not changes[section_name]:
                        del changes[section_name]
        else:
            if not __opts__['test']:
                changes = __salt__['ini.set_option'](name, sections, separator)
    except (IOError, KeyError) as err:
        ret['comment'] = "{0}".format(err)
        ret['result'] = False
        return ret
    if 'error' in changes:
        ret['result'] = False
        ret['comment'] = 'Errors encountered. {0}'.format(changes['error'])
        ret['changes'] = {}
    else:
        for ciname, body in changes.items():
            if body:
                ret['comment'] = 'Changes take effect'
                ret['changes'].update({ciname: changes[ciname]})
    return ret


def options_absent(name, sections=None, separator='='):
    '''
    .. code-block:: yaml

        /home/saltminion/api-paste.ini:
          ini.options_absent:
            - separator: '='
            - sections:
                test:
                  - testkey
                  - secondoption
                test1:
                  - testkey1

    options present in file and not specified in sections
    dict will be untouched

    changes dict will contain the list of changes made
    '''
    ret = {'name': name,
           'changes': {},
           'result': True,
           'comment': 'No anomaly detected'
           }
    if __opts__['test']:
        ret['result'] = True
        ret['comment'] = ''
        for section in sections or {}:
            section_name = ' in section ' + section if section else ''
            try:
                cur_section = __salt__['ini.get_section'](name, section, separator)
            except IOError as err:
                ret['comment'] = "{0}".format(err)
                ret['result'] = False
                return ret
            except AttributeError:
                cur_section = section
            if isinstance(sections[section], (dict, OrderedDict)):
                for key in sections[section]:
                    cur_value = cur_section.get(key)
                    if not cur_value:
                        ret['comment'] += 'Key {0}{1} does not exist.\n'.format(key, section_name)
                        continue
                    ret['comment'] += 'Deleted key {0}{1}.\n'.format(key, section_name)
                    ret['result'] = None
            else:
                option = section
                if not __salt__['ini.get_option'](name, None, option, separator):
                    ret['comment'] += 'Key {0} does not exist.\n'.format(option)
                    continue
                ret['comment'] += 'Deleted key {0}.\n'.format(option)
                ret['result'] = None

        if ret['comment'] == '':
            ret['comment'] = 'No changes detected.'
        return ret
    sections = sections or {}
    for section, keys in six.iteritems(sections):
        for key in keys:
            try:
                current_value = __salt__['ini.remove_option'](name, section, key, separator)
            except IOError as err:
                ret['comment'] = "{0}".format(err)
                ret['result'] = False
                return ret
            if not current_value:
                continue
            if section not in ret['changes']:
                ret['changes'].update({section: {}})
            ret['changes'][section].update({key: current_value})
            if not isinstance(sections[section], (dict, OrderedDict)):
                ret['changes'].update({section: current_value})
                # break
            ret['comment'] = 'Changes take effect'
    return ret


def sections_present(name, sections=None, separator='='):
    '''
    .. code-block:: yaml

        /home/saltminion/api-paste.ini:
          ini.sections_present:
            - separator: '='
            - sections:
                - section_one
                - section_two

    This will only create empty sections. To also create options, use
    options_present state

    options present in file and not specified in sections will be deleted
    changes dict will contain the sections that changed
    '''
    ret = {'name': name,
           'changes': {},
           'result': True,
           'comment': 'No anomaly detected'
           }
    if __opts__['test']:
        ret['result'] = True
        ret['comment'] = ''
        try:
            cur_ini = __salt__['ini.get_ini'](name, separator)
        except IOError as err:
            ret['result'] = False
            ret['comment'] = "{0}".format(err)
            return ret
        for section in sections or {}:
            if section in cur_ini:
                ret['comment'] += 'Section unchanged {0}.\n'.format(section)
                continue
            else:
                ret['comment'] += 'Created new section {0}.\n'.format(section)
            ret['result'] = None
        if ret['comment'] == '':
            ret['comment'] = 'No changes detected.'
        return ret
    section_to_update = {}
    for section_name in sections or []:
        section_to_update.update({section_name: {}})
    try:
        changes = __salt__['ini.set_option'](name, section_to_update, separator)
    except IOError as err:
        ret['result'] = False
        ret['comment'] = "{0}".format(err)
        return ret
    if 'error' in changes:
        ret['result'] = False
        ret['changes'] = 'Errors encountered {0}'.format(changes['error'])
        return ret
    ret['changes'] = changes
    ret['comment'] = 'Changes take effect'
    return ret


def sections_absent(name, sections=None, separator='='):
    '''
    .. code-block:: yaml

        /home/saltminion/api-paste.ini:
          ini.sections_absent:
            - separator: '='
            - sections:
                - test
                - test1

    options present in file and not specified in sections will be deleted
    changes dict will contain the sections that changed
    '''
    ret = {'name': name,
           'changes': {},
           'result': True,
           'comment': 'No anomaly detected'
           }
    if __opts__['test']:
        ret['result'] = True
        ret['comment'] = ''
        try:
            cur_ini = __salt__['ini.get_ini'](name, separator)
        except IOError as err:
            ret['result'] = False
            ret['comment'] = "{0}".format(err)
            return ret
        for section in sections or []:
            if section not in cur_ini:
                ret['comment'] += 'Section {0} does not exist.\n'.format(section)
                continue
            ret['comment'] += 'Deleted section {0}.\n'.format(section)
            ret['result'] = None
        if ret['comment'] == '':
            ret['comment'] = 'No changes detected.'
        return ret
    for section in sections or []:
        try:
            cur_section = __salt__['ini.remove_section'](name, section, separator)
        except IOError as err:
            ret['result'] = False
            ret['comment'] = "{0}".format(err)
            return ret
        if not cur_section:
            continue
        ret['changes'][section] = cur_section
        ret['comment'] = 'Changes take effect'
    return ret
