# Copyright (c) 2018-2020, NVIDIA CORPORATION.  All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto.  Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
#
from datetime import datetime
import abc


class AbstractSearchItem:
    """AbstractSearchItem represents a single file in the file browser."""

    @property
    def path(self):
        """The full path that goes to usd when Drag and Drop"""
        return ""

    @property
    def name(self):
        """The name as it appears in the widget"""
        return ""

    @property
    def date(self):
        # TODO: Grid View needs datatime, but Tree View needs a string. We need to make them the same.
        return datetime.now()

    @property
    def size(self):
        # TODO: Grid View needs int, but Tree View needs a string. We need to make them the same.
        return 0

    @property
    def icon(self):
        pass

    @property
    def is_folder(self):
        pass

    def __getitem__(self, key):
        """Access to methods by text for _RedirectModel"""
        return getattr(self, key)


class SearchLifetimeObject(metaclass=abc.ABCMeta):
    """
    SearchLifetimeObject encapsulates a callback to be called when a search is finished.
    It is the responsibility of the implementers of AbstractSearchModel to get the object
    argument and keep it alive until the search is completed if the search runs long.
    """
    def __init__(self, callback):
        self._callback = callback

    def __del__(self):
        self.destroy()

    def destroy(self):
        if self._callback:
            self._callback()
        self._callback = None


class AbstractSearchModel(metaclass=abc.ABCMeta):
    """
    AbstractSearchModel represents the search results. It supports async
    mode. If the search engine needs some time to process the request, it can
    return an empty list and do a search in async mode. As soon as a result
    is ready, the model should call `self._item_changed()`. It will make the
    view reload the model. It's also possible to return the search result
    with portions.

    __init__ is usually called with the named arguments search_text and
    current_dir, and optionally a search_lifetime object.
    """

    class _Event(set):
        """
        A list of callable objects. Calling an instance of this will cause a
        call to each item in the list in ascending order by index.
        """

        def __call__(self, *args, **kwargs):
            """Called when the instance is “called” as a function"""
            # Call all the saved functions
            for f in self:
                f(*args, **kwargs)

        def __repr__(self):
            """
            Called by the repr() built-in function to compute the “official”
            string representation of an object.
            """
            return f"Event({set.__repr__(self)})"

    class _EventSubscription:
        """
        Event subscription.

        _Event has callback while this object exists.
        """

        def __init__(self, event, fn):
            """
            Save the function, the event, and add the function to the event.
            """
            self._fn = fn
            self._event = event
            event.add(self._fn)

        def __del__(self):
            """Called by GC."""
            self._event.remove(self._fn)

    def __init__(self):
        # TODO: begin_edit/end_edit
        self.__on_item_changed = self._Event()

    @property
    @abc.abstractmethod
    def items(self):
        """Should be implemented"""
        pass

    def destroy(self):
        """Called to cancel current search"""
        pass

    def _item_changed(self, item=None):
        """Call the event object that has the list of functions"""
        self.__on_item_changed(item)

    def subscribe_item_changed(self, fn):
        """
        Return the object that will automatically unsubscribe when destroyed.
        """
        return self._EventSubscription(self.__on_item_changed, fn)
