# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of logilab-common.
#
# logilab-common is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option) any
# later version.
#
# logilab-common is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with logilab-common.  If not, see <http://www.gnu.org/licenses/>.
"""Logilab common library (aka Logilab's extension to the standard library).

:type STD_BLACKLIST: tuple
:var STD_BLACKLIST: directories ignored by default by the functions in
  this package which have to recurse into directories

:type IGNORED_EXTENSIONS: tuple
:var IGNORED_EXTENSIONS: file extensions that may usually be ignored
"""
__docformat__ = "restructuredtext en"

import sys
import types

if sys.version_info < (3, 10):
    from importlib_metadata import version
else:
    from importlib.metadata import version

from typing import List, Sequence

__version__ = version("logilab-common")

# deprecated, but keep compatibility with pylint < 1.4.4
__pkginfo__ = types.ModuleType("__pkginfo__")
__pkginfo__.__package__ = __name__
# mypy output: Module has no attribute "version"
# logilab's magic
__pkginfo__.version = __version__  # type: ignore
sys.modules["logilab.common.__pkginfo__"] = __pkginfo__

STD_BLACKLIST = ("CVS", ".svn", ".hg", ".git", ".tox", "debian", "dist", "build")

IGNORED_EXTENSIONS = (".pyc", ".pyo", ".elc", "~", ".swp", ".orig")

# set this to False if you've mx DateTime installed but you don't want your db
# adapter to use it (should be set before you got a connection)
USE_MX_DATETIME = True


class attrdict(dict):
    """A dictionary for which keys are also accessible as attributes."""

    def __getattr__(self, attr: str) -> str:
        try:
            return self[attr]
        except KeyError:
            raise AttributeError(attr)


class dictattr(dict):
    def __init__(self, proxy):
        self.__proxy = proxy

    def __getitem__(self, attr):
        try:
            return getattr(self.__proxy, attr)
        except AttributeError:
            raise KeyError(attr)


class nullobject:
    def __repr__(self):
        return "<nullobject>"

    def __bool__(self):
        return False

    __nonzero__ = __bool__


class tempattr:
    def __init__(self, obj, attr, value):
        self.obj = obj
        self.attr = attr
        self.value = value

    def __enter__(self):
        self.oldvalue = getattr(self.obj, self.attr)
        setattr(self.obj, self.attr, self.value)
        return self.obj

    def __exit__(self, exctype, value, traceback):
        setattr(self.obj, self.attr, self.oldvalue)


# flatten -----
# XXX move in a specific module and use yield instead
# do not mix flatten and translate
#
# def iterable(obj):
#    try: iter(obj)
#    except: return False
#    return True
#
# def is_string_like(obj):
#    try: obj +''
#    except (TypeError, ValueError): return False
#    return True
#
# def is_scalar(obj):
#    return is_string_like(obj) or not iterable(obj)
#
# def flatten(seq):
#    for item in seq:
#        if is_scalar(item):
#            yield item
#        else:
#            for subitem in flatten(item):
#               yield subitem


def flatten(iterable, tr_func=None, results=None):
    """Flatten a list of list with any level.

    If tr_func is not None, it should be a one argument function that'll be called
    on each final element.

    :rtype: list

    >>> flatten([1, [2, 3]])
    [1, 2, 3]
    """
    if results is None:
        results = []
    for val in iterable:
        if isinstance(val, (list, tuple)):
            flatten(val, tr_func, results)
        elif tr_func is None:
            results.append(val)
        else:
            results.append(tr_func(val))
    return results


# XXX is function below still used ?


def make_domains(lists):
    """
    Given a list of lists, return a list of domain for each list to produce all
    combinations of possibles values.

    :rtype: list

    Example:

    >>> make_domains(['a', 'b'], ['c','d', 'e'])
    [['a', 'b', 'a', 'b', 'a', 'b'], ['c', 'c', 'd', 'd', 'e', 'e']]
    """
    domains = []
    for iterable in lists:
        new_domain = iterable[:]
        for i in range(len(domains)):
            domains[i] = domains[i] * len(iterable)
        if domains:
            missing = (len(domains[0]) - len(iterable)) / len(iterable)
            i = 0
            for j in range(len(iterable)):
                value = iterable[j]
                for dummy in range(missing):
                    new_domain.insert(i, value)
                    i += 1
                i += 1
        domains.append(new_domain)
    return domains


# private stuff ################################################################


def _handle_blacklist(blacklist: Sequence[str], dirnames: List[str], filenames: List[str]) -> None:
    """remove files/directories in the black list

    dirnames/filenames are usually from os.walk
    """
    for norecurs in blacklist:
        if norecurs in dirnames:
            dirnames.remove(norecurs)
        elif norecurs in filenames:
            filenames.remove(norecurs)
