# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt

"""TOML configuration support for coverage.py"""

import io
import os
import re

from coverage import env
from coverage.backward import configparser, path_types, toml
from coverage.misc import CoverageException, substitute_variables


class TomlDecodeError(Exception):
    """An exception class that exists even when toml isn't installed."""
    pass


class TomlConfigParser:
    """TOML file reading with the interface of HandyConfigParser."""

    # This class has the same interface as config.HandyConfigParser, no
    # need for docstrings.
    # pylint: disable=missing-function-docstring

    def __init__(self, our_file):
        self.getters = [lambda obj: obj['tool']['coverage']]
        if our_file:
            self.getters.append(lambda obj: obj)

        self._data = []

    def read(self, filenames):
        if toml is None:
            raise RuntimeError('toml module is not installed.')

        if isinstance(filenames, path_types):
            filenames = [filenames]
        read_ok = []
        for filename in filenames:
            try:
                with io.open(filename, encoding='utf-8') as fp:
                    toml_data = fp.read()
                    toml_data = substitute_variables(toml_data, os.environ)
                    self._data.append(toml.loads(toml_data))
            except IOError:
                continue
            except toml.TomlDecodeError as err:
                raise TomlDecodeError(*err.args)
            if env.PYVERSION >= (3, 6):
                filename = os.fspath(filename)
            read_ok.append(filename)
        return read_ok

    def has_option(self, section, option):
        for data in self._data:
            for getter in self.getters:
                try:
                    getter(data)[section][option]
                except KeyError:
                    continue
                return True
        return False

    def has_section(self, section):
        for data in self._data:
            for getter in self.getters:
                try:
                    getter(data)[section]
                except KeyError:
                    continue
                return section
        return False

    def options(self, section):
        for data in self._data:
            for getter in self.getters:
                try:
                    section = getter(data)[section]
                except KeyError:
                    continue
                return list(section.keys())
        raise configparser.NoSectionError(section)

    def get_section(self, section):
        d = {}
        for opt in self.options(section):
            d[opt] = self.get(section, opt)
        return d

    def get(self, section, option):
        found_section = False
        for data in self._data:
            for getter in self.getters:
                try:
                    section = getter(data)[section]
                except KeyError:
                    continue

                found_section = True
                try:
                    value = section[option]
                except KeyError:
                    continue
                return value
        if not found_section:
            raise configparser.NoSectionError(section)
        raise configparser.NoOptionError(option, section)

    def getboolean(self, section, option):
        value = self.get(section, option)
        if not isinstance(value, bool):
            raise ValueError(
                'Option {!r} in section {!r} is not a boolean: {!r}'
                    .format(option, section, value)
            )
        return value

    def getlist(self, section, option):
        values = self.get(section, option)
        if not isinstance(values, list):
            raise ValueError(
                'Option {!r} in section {!r} is not a list: {!r}'
                    .format(option, section, values)
            )
        return values

    def getregexlist(self, section, option):
        values = self.getlist(section, option)
        for value in values:
            value = value.strip()
            try:
                re.compile(value)
            except re.error as e:
                raise CoverageException(
                    "Invalid [%s].%s value %r: %s" % (section, option, value, e)
                )
        return values

    def getint(self, section, option):
        value = self.get(section, option)
        if not isinstance(value, int):
            raise ValueError(
                'Option {!r} in section {!r} is not an integer: {!r}'
                    .format(option, section, value)
            )
        return value

    def getfloat(self, section, option):
        value = self.get(section, option)
        if isinstance(value, int):
            value = float(value)
        if not isinstance(value, float):
            raise ValueError(
                'Option {!r} in section {!r} is not a float: {!r}'
                    .format(option, section, value)
            )
        return value
