# Copyright 2016, 2017, 2018 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import os
import sys

from config_tempest import constants as C
from oslo_config import cfg
from six.moves import configparser
import tempest.config


class TempestConf(configparser.ConfigParser):
    # causes the config parser to preserve case of the options
    optionxform = str

    # set of pairs `(section, key)` which have a higher priority (are
    # user-defined) and will usually not be overwritten by `set()`
    priority_sectionkeys = set()

    CONF = tempest.config.TempestConfigPrivate(parse_conf=False)

    def __init__(self, write_credentials=True, **kwargs):
        self.write_credentials = write_credentials
        configparser.ConfigParser.__init__(self, **kwargs)

    def get_bool_value(self, value):
        """Returns boolean value of the string value given.

        :param value: True/False
        :type value: String
        :returns: Boolean value of the string value given
        :rtype: Boolean
        """
        strval = str(value).lower()
        if strval == 'true':
            return True
        elif strval == 'false':
            return False
        else:
            raise ValueError("'%s' is not a boolean" % value)

    def get_defaulted(self, section, key):
        """Returns default value for the section.key pair.

        :param section: a section in a tempest.conf file
        :type section: String
        :param key: a key in a section in a tempest.conf file
        :type key: String
        :returns: default value for the section.key pair
        :rtype: String
        """
        try:
            if self.has_option(section, key):
                return self.get(section, key)
            else:
                return self.CONF.get(section).get(key)
        except cfg.NoSuchOptError:
            C.LOG.warning("Option %s is not defined in %s section",
                          key, section)

    def set(self, section, key, value, priority=False):
        """Set value in configuration, similar to `ConfigParser.set`

        Creates non-existent sections. Keeps track of options which were
        specified by the user and should not be normally overwritten.

        :param section: a section in a tempest.conf file
        :type section: String
        :param key: a key in a section in a tempest.conf file
        :type key: String
        :param value: a value to be set to the section.key
        :type value: String
        :param priority: if True, always over-write the value. If False, don't
            over-write an existing value if it was written before with a
            priority (i.e. if it was specified by the user)
        :type priority: Boolean
        :returns: True if the value was written, False if not (because of
            priority)
        :rtype: Boolean
        """
        if not self.has_section(section) and section.lower() != "default":
            self.add_section(section)
        if not priority and (section, key) in self.priority_sectionkeys:
            C.LOG.debug("Option '[%s] %s = %s' was defined by user, NOT"
                        " overwriting into value '%s'", section, key,
                        self.get(section, key), value)
            return False
        if priority:
            self.priority_sectionkeys.add((section, key))
        C.LOG.debug("Setting [%s] %s = %s", section, key, value)
        configparser.ConfigParser.set(self, section, key, value)
        return True

    def write(self, out_path):
        C.LOG.info("Creating configuration file %s", os.path.abspath(out_path))
        if not self.write_credentials:
            C.LOG.info("Credentials will not be printed to a tempest.conf, "
                       "writing credentials is disabled.")
            self.remove_values(C.ALL_CREDENTIALS_KEYS)
        with open(out_path, 'w') as f:
            configparser.ConfigParser.write(self, f)

    def remove_values(self, to_remove):
        """Remove values from configuration file specified in arguments.

        :param to_remove: {'section.key': [values_to_be_removed], ...}
        :type to_remove: dict
        """
        for key_path in to_remove:
            section, key = key_path.split('.')
            try:
                conf_values = self.get(section, key).split(',')
                remove = to_remove[key_path]
                if len(remove) == 0:
                    # delete all values in section.key
                    self.remove_option(section, key)
                elif len(conf_values) == 1:
                    # make sure only the value specified by user
                    # will be deleted if in the key is other value
                    # than expected, ignore it
                    if conf_values[0] in to_remove[key_path]:
                        self.remove_option(section, key)
                else:
                    # exclude all unwanted values from the list
                    # and preserve the original order of items
                    conf_values = [v for v in conf_values if v not in remove]
                    self.set(section, key, ",".join(conf_values))
            except configparser.NoOptionError:
                # only inform a user, option specified by him doesn't exist
                C.LOG.error(sys.exc_info()[1])
            except configparser.NoSectionError:
                # only inform a user, section specified by him doesn't exist
                C.LOG.error(sys.exc_info()[1])

    def append_values(self, to_append):
        """Appends values to configuration file specified in arguments.

        :param to_append: {'section.key': [values_to_be_added], ...}
        :type to_append: dict
        """
        for key_path in to_append:
            section, key = key_path.split('.')
            try:
                conf_val = self.get(section, key).split(',')
                # omit duplicates if found any
                conf_val += list(set(to_append[key_path]) - set(conf_val))
                self.set(section, key, ",".join(conf_val))
            except configparser.NoOptionError:
                # only inform a user, option specified by him doesn't exist
                C.LOG.error(sys.exc_info()[1])
            except configparser.NoSectionError:
                # only inform a user, section specified by him doesn't exist
                C.LOG.error(sys.exc_info()[1])
