"""
* Copyright (c) 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.
"""
import omni.ui as ui


class SimpleItem(ui.AbstractItem):
    """Single item of the model"""

    def __init__(self, model, name, fav=False, block=False):
        super(SimpleItem, self).__init__()
        self._model = model
        self.name = name
        self._favorite = fav
        self._blocklist = block

    def item_mouse_released(self):
        self._model.item_mouse_released(self)

    @property
    def favorite(self):
        return self._favorite

    @favorite.setter
    def favorite(self, value: bool):
        """Add favorite"""
        if value:
            self._model.prepend_item("favorites", self, refresh=False)
        else:
            if self in self._model.favorites:
                self._model.favorites.remove(self)
        self._favorite = value

    @property
    def blocklist(self):
        return self._blocklist

    @blocklist.setter
    def blocklist(self, value: bool):
        """Add blocklist"""
        if value:
            self._model.prepend_item("blocklist", self, refresh=False)
            if self in self._model.favorites:
                self._model.favorites.remove(self)
            if self in self._model.previous_items:
                self._model.previous_items.remove(self)
        else:
            if self in self._model.blocklist:
                self._model.blocklist.remove(self)
            if self.favorite and self not in self._model.favorites:
                self._model.prepend_item("favorites", self, refresh=False)
            if self not in self._model.previous_items:
                self._model.prepend_item("previous_items", self, refresh=False)
        self._blocklist = value


class SimpleModel(ui.AbstractItemModel):
    """Simple list model that can be used in a lot of different way"""

    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, column_count: int):
        super(SimpleModel, self).__init__()
        self.items = []
        self.latest = []
        self.favorites = []
        self.blocklist = []
        self.previous_items = []
        self._column_count = column_count
        self.__on_item_mouse_released = self._Event()

    def item_mouse_released(self, item):
        """Call the event object that has the list of functions"""
        self.__on_item_mouse_released(item)

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

    def get_item_children(self, item: SimpleItem) -> list:
        """Returns all the children when the widget asks it."""
        if item is not None:
            # Since we are doing a flat list, we return the children of root only.
            # If it's not root we return.
            return []

        return self.items

    def set_items(self, mode_attr: str, items: list, refresh: bool = True):
        """Set items on the list"""
        setattr(self, mode_attr, items)
        if refresh:
            self.refresh()

    def append_item(self, mode_attr: str, item, refresh: bool = True):
        """Append items on the list"""
        current_items = getattr(self, mode_attr)
        indexes = []
        for i, current_item in enumerate(current_items):
            if item.name.get_value_as_string() == current_item.name.get_value_as_string():
                indexes.append(i)
        indexes.reverse()
        for index in indexes:
            current_items.pop(index)
        current_items.append(item)
        self.set_items(mode_attr, current_items, refresh=refresh)

    def prepend_item(self, mode_attr: str, item, refresh: bool = True):
        """Prepend items on the list"""
        current_items = getattr(self, mode_attr)
        indexes = []
        for i, current_item in enumerate(current_items):
            if item.name.get_value_as_string() == current_item.name.get_value_as_string():
                indexes.append(i)
        indexes.reverse()
        for index in indexes:
            current_items.pop(index)
        current_items.insert(0, item)
        self.set_items(mode_attr, current_items, refresh=refresh)

    def refresh(self):
        """Reset the model"""
        self._item_changed(None)

    def get_item_value_model_count(self, item) -> int:
        """The number of columns"""
        return self._column_count

    def get_item_value_model(self, item: SimpleItem, column_id: int):
        """
        Return value model.
        It's the object that tracks the specific value.
        In our case we use ui.SimpleStringModel.
        """
        return item.name
