import itertools
import operator

from irods.models import Collection, DataObject
from irods.data_object import iRODSDataObject, irods_basename
from irods.meta import iRODSMetaCollection


def _first_char(*Strings):
    for s in Strings:
        if s:
            return s[0]
    return ""


class iRODSCollection:

    class AbsolutePathRequired(Exception):
        """Exception raised by iRODSCollection.normalize_path.

        AbsolutePathRequired is raised by normalize_path( *paths ) when the leading path element
        does not start with '/'.  The exception will not be raised, however, if enforce_absolute = False
        is passed to normalize_path as a keyword option.
        """

        pass

    def __init__(self, manager, result=None):
        self.manager = manager
        if result:
            self.id = result[Collection.id]
            self.path = result[Collection.name]
            self.name = irods_basename(result[Collection.name])
            self.create_time = result[Collection.create_time]
            self.modify_time = result[Collection.modify_time]
            self._inheritance = result[Collection.inheritance]
            self.owner_name = result[Collection.owner_name]
            self.owner_zone = result[Collection.owner_zone]
        self._meta = None

    @property
    def inheritance(self):
        return bool(self._inheritance) and self._inheritance != "0"

    @property
    def metadata(self):
        if not self._meta:
            self._meta = iRODSMetaCollection(
                self.manager.sess.metadata, Collection, self.path
            )
        return self._meta

    @property
    def subcollections(self):
        query = self.manager.sess.query(Collection).filter(
            Collection.parent_name == self.path
        )
        return [
            iRODSCollection(self.manager, row)
            for row in query
            if row[Collection.name] != "/"
        ]

    @property
    def data_objects(self):
        query = self.manager.sess.query(DataObject).filter(Collection.name == self.path)
        results = query.get_results()
        grouped = itertools.groupby(results, operator.itemgetter(DataObject.id))
        return [
            iRODSDataObject(self.manager.sess.data_objects, self, list(replicas))
            for _, replicas in grouped
        ]

    def remove(self, recurse=True, force=False, **options):
        self.manager.remove(self.path, recurse, force, **options)

    def unregister(self, **options):
        self.manager.unregister(self.path, **options)

    def move(self, path):
        self.manager.move(self.path, path)

    def walk(self, topdown=True):
        """
        Collection tree generator.

        For each subcollection in the directory tree, starting at the
        collection, yield a 3-tuple
        """

        if topdown:
            yield (self, self.subcollections, self.data_objects)
        for subcollection in self.subcollections:
            new_root = subcollection
            for x in new_root.walk(topdown):
                yield x
        if not topdown:
            yield (self, self.subcollections, self.data_objects)

    @staticmethod
    def normalize_path(*paths, **kw_):
        """Normalize a path or list of paths.

        We use the iRODSPath class to eliminate extra slashes in,
        and (if more than one parameter is given) concatenate, paths.
        If the keyword argument `enforce_absolute' is set True, this
        function requires the first character of path(s) passed in
        should be '/'.
        """
        import irods.path

        absolute = kw_.get("enforce_absolute", False)
        if absolute and _first_char(*paths) != "/":
            raise iRODSCollection.AbsolutePathRequired
        return irods.path.iRODSPath(*paths, absolute=absolute)

    def __repr__(self):
        return f"<iRODSCollection {self.id} {self.name}>"
