# Copyright: 2009-2020 Gentoo Authors
# Author(s): Petteri Räty (betelgeuse@gentoo.org)
# License: GPL2

__all__ = ["database"]

import errno

from portage.cache import fs_template
from portage.versions import catsplit
from portage import cpv_getkey
from portage import os
from portage import _encodings
from portage import _unicode_decode
from portage.util._xattr import xattr


class NoValueException(Exception):
    pass


class database(fs_template.FsBased):
    autocommits = True

    def __init__(self, *args, **config):
        super().__init__(*args, **config)
        self.portdir = self.label
        self.ns = xattr.NS_USER + ".gentoo.cache"
        self.keys = set(self._known_keys)
        self.keys.add("_mtime_")
        self.keys.add("_eclasses_")
        # xattrs have an upper length
        self.max_len = self.__get_max()

    def __get_max(self):
        path = os.path.join(self.portdir, "profiles/repo_name")
        try:
            return int(self.__get(path, "value_max_len"))
        except NoValueException as e:
            maxattrlength = self.__calc_max(path)
            self.__set(path, "value_max_len", str(maxattrlength))
            return maxattrlength

    def __calc_max(self, path):
        """Find out max attribute length supported by the file system"""

        hundred = ""
        for i in range(100):
            hundred += "a"

        s = hundred

        # Could use finally but needs python 2.5 then
        try:
            while True:
                self.__set(path, "test_max", s)
                s += hundred
        except OSError as e:
            # ext based give wrong errno
            # https://bugzilla.kernel.org/show_bug.cgi?id=12793
            if e.errno in (errno.E2BIG, errno.ENOSPC):
                result = len(s) - 100
            else:
                raise

        try:
            self.__remove(path, "test_max")
        except OSError as e:
            if e.errno != errno.ENODATA:
                raise

        return result

    def __get_path(self, cpv):
        cat, pn = catsplit(cpv_getkey(cpv))
        return os.path.join(self.portdir, cat, pn, os.path.basename(cpv) + ".ebuild")

    def __has_cache(self, path):
        try:
            self.__get(path, "_mtime_")
        except NoValueException as e:
            return False

        return True

    def __get(self, path, key, default=None):
        try:
            return xattr.get(path, key, namespace=self.ns)
        except OSError as e:
            if not default is None and errno.ENODATA == e.errno:
                return default
            raise NoValueException()

    def __remove(self, path, key):
        xattr.remove(path, key, namespace=self.ns)

    def __set(self, path, key, value):
        xattr.set(path, key, value, namespace=self.ns)

    def _getitem(self, cpv):
        values = {}
        path = self.__get_path(cpv)
        attrs = {key: value for key, value in xattr.get_all(path, namespace=self.ns)}

        if not "_mtime_" in all:
            raise KeyError(cpv)

        # We default to '' like other caches
        for key in self.keys:
            attr_value = attrs.get(key, "1:")
            parts, sep, value = attr_value.partition(":")
            parts = int(parts)
            if parts > 1:
                for i in range(1, parts):
                    value += attrs.get(key + str(i))
            values[key] = value

        return values

    def _setitem(self, cpv, values):
        path = self.__get_path(cpv)
        max_len = self.max_len
        for key, value in values.items():
            # mtime comes in as long so need to convert to strings
            s = str(value)
            # We need to split long values
            value_len = len(s)
            parts = 0
            if value_len > max_len:
                # Find out how many parts we need
                parts = value_len / max_len
                if value_len % max_len > 0:
                    parts += 1

                # Only the first entry carries the number of parts
                self.__set(path, key, f"{parts}:{s[0:max_len]}")

                # Write out the rest
                for i in range(1, parts):
                    start = i * max_len
                    val = s[start : start + max_len]
                    self.__set(path, key + str(i), val)
            else:
                self.__set(path, key, f"{1}:{s}")

    def _delitem(self, cpv):
        pass  # Will be gone with the ebuild

    def __contains__(self, cpv):
        return os.path.exists(self.__get_path(cpv))

    def __iter__(self):
        for root, dirs, files in os.walk(self.portdir):
            for file in files:
                try:
                    file = _unicode_decode(
                        file, encoding=_encodings["fs"], errors="strict"
                    )
                except UnicodeDecodeError:
                    continue
                if file[-7:] == ".ebuild":
                    cat = os.path.basename(os.path.dirname(root))
                    pn_pv = file[:-7]
                    path = os.path.join(root, file)
                    if self.__has_cache(path):
                        yield f"{cat}/{os.path.basename(root)}/{file[:-7]}"
