import collections.abc

import notmuch2._base as base
import notmuch2._capi as capi
import notmuch2._errors as errors


__all__ = ['ConfigMapping']


class ConfigIter(base.NotmuchIter):

    def __init__(self, parent, iter_p):
        super().__init__(
            parent, iter_p,
            fn_destroy=capi.lib.notmuch_config_pairs_destroy,
            fn_valid=capi.lib.notmuch_config_pairs_valid,
            fn_get=capi.lib.notmuch_config_pairs_key,
            fn_next=capi.lib.notmuch_config_pairs_move_to_next)

    def __next__(self):
        # skip pairs whose value is NULL
        while capi.lib.notmuch_config_pairs_valid(super()._iter_p):
            val_p = capi.lib.notmuch_config_pairs_value(super()._iter_p)
            key_p = capi.lib.notmuch_config_pairs_key(super()._iter_p)
            if key_p == capi.ffi.NULL:
                # this should never happen
                raise errors.NullPointerError
            key = base.BinString.from_cffi(key_p)
            capi.lib.notmuch_config_pairs_move_to_next(super()._iter_p)
            if val_p != capi.ffi.NULL and base.BinString.from_cffi(val_p) != "":
                return key
        self._destroy()
        raise StopIteration

class ConfigMapping(base.NotmuchObject, collections.abc.MutableMapping):
    """The config key/value pairs loaded from the database, config file,
    and and/or defaults.

    The entries are exposed as a :class:`collections.abc.MutableMapping` object.
    Note that setting a value to an empty string is the same as deleting it.

    Mutating (deleting or updating values) in the map persists only in
    the database, which can be shadowed by config files.

    :param parent: the parent object
    :param ptr_name: the name of the attribute on the parent which will
       return the memory pointer.  This allows this object to
       access the pointer via the parent's descriptor and thus
       trigger :class:`MemoryPointer`'s memory safety.

    """

    def __init__(self, parent, ptr_name):
        self._parent = parent
        self._ptr = lambda: getattr(parent, ptr_name)

    @property
    def alive(self):
        return self._parent.alive

    def _destroy(self):
        pass

    def __getitem__(self, key):
        if isinstance(key, str):
            key = key.encode('utf-8')
        val_pp = capi.ffi.new('char**')
        ret = capi.lib.notmuch_database_get_config(self._ptr(), key, val_pp)
        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
            raise errors.NotmuchError(ret)
        val = base.BinString.from_cffi(val_pp[0])
        capi.lib.free(val_pp[0])
        if val == '':
            raise KeyError
        return val

    def __setitem__(self, key, val):
        if isinstance(key, str):
            key = key.encode('utf-8')
        if isinstance(val, str):
            val = val.encode('utf-8')
        ret = capi.lib.notmuch_database_set_config(self._ptr(), key, val)
        if ret != capi.lib.NOTMUCH_STATUS_SUCCESS:
            raise errors.NotmuchError(ret)

    def __delitem__(self, key):
        self[key] = ""

    def __iter__(self):
        """Return an iterator over the config items.

        :raises NullPointerError: If the iterator can not be created.
        """
        config_pairs_p = capi.lib.notmuch_config_get_pairs(self._ptr(), b'')
        if config_pairs_p == capi.ffi.NULL:
            raise KeyError
        return ConfigIter(self._parent, config_pairs_p)

    def __len__(self):
        return sum(1 for t in self)