from gi.repository import Gtk, Gio

from typing import Any, List, Optional

from iotas.category import Category
from iotas.note import Note


class NoteListModelBase(Gtk.SortListModel):
    def __init__(self):
        self.__store_remote_id_map = {}

        self.__list_store = Gio.ListStore(item_type=Note)
        self.__sorter = Gtk.CustomSorter()
        self.invalidate_sort()

        super().__init__(model=self.__list_store, sorter=self.__sorter)

    def invalidate_sort(self) -> None:
        """Invalidate the sorter."""
        self.__sorter.set_sort_func(NoteListModelBase.sort_func)

    def add_notes(self, notes: List[Note]) -> None:
        """Add new notes to store.

        :param List[Note] notes: The notes to add
        """
        for note in notes:
            self.__list_store.append(note)
            if note.has_remote_id:
                self.__store_remote_id_map[note.remote_id] = note

    def remove_notes(self, notes: List[Note]) -> None:
        """Remove notes from the store.

        :param List[Note] notes: The notes to remove
        """
        for note in notes:
            for index in range(len(self.__list_store)):
                if note == self.__list_store[index]:
                    self.__list_store.remove(index)
                    break
            if note.has_remote_id and note.remote_id in self.__store_remote_id_map:
                del self.__store_remote_id_map[note.remote_id]

    def ensure_map_entry(self, note: Note) -> None:
        """Ensure the provided note has an entry in the remote id to note map.

        :param Note note: The note
        """
        if note.has_remote_id and note.remote_id not in self.__store_remote_id_map:
            self.__store_remote_id_map[note.remote_id] = note

    def fetch_note_by_remote_id(self, remote_id: int) -> Optional[Note]:
        """Fetch a note by remote id"

        :param int remote_id: The remote id of the note
        """
        if remote_id not in self.__store_remote_id_map:
            return None
        return self.__store_remote_id_map[remote_id]

    @staticmethod
    def sort_func(note1: Note, note2: Note, _data: Any) -> int:
        """Sort notes.

        :param Note note1: First note
        :param Note note2: Second note
        :param _data: Unused
        :return: -1 if note1 earlier, 1 if later, 0 unused
        :rtype: int
        """
        if note1.favourite != note2.favourite:
            return -1 if note1.favourite else 1
        return -1 if note1.last_modified > note2.last_modified else 1


class NoteListModelCategoryFiltered(Gtk.FilterListModel):
    def __init__(self, sorter_store: Gtk.SortListModel):
        self.__sorter_store = sorter_store
        self.__filter = Gtk.CustomFilter()
        self.__filter.set_filter_func(self.__filter_func)
        self.__category = None
        super().__init__(model=self.__sorter_store, filter=self.__filter)

    def invalidate_filter(self, category: Category) -> None:
        """Invalidate the filter.

        :param Category: Category to display
        """
        self.__category = category
        self.__filter.set_filter_func(self.__filter_func)

    def __filter_func(self, note: Note) -> bool:
        res = not note.locally_deleted
        if self.__category is not None:
            res = res and self.__category.includes_note(note)
        return res


class NoteListModelFavourites(Gtk.FilterListModel):
    def __init__(self, sorter_store: Gtk.SortListModel):
        self.__sorter_store = sorter_store
        self.__filter = Gtk.CustomFilter()
        self.invalidate_filter()
        super().__init__(model=self.__sorter_store, filter=self.__filter)

    def invalidate_filter(self) -> None:
        """Invalidate the filter."""
        self.__filter.set_filter_func(NoteListModelFavourites.filter_func)

    @staticmethod
    def filter_func(note: Note) -> bool:
        """Filter for favourites.

        :param Note note: The note to filter
        """
        res = not note.locally_deleted
        res = res and note.favourite
        return res


class NoteListModelTimeFiltered(Gtk.FilterListModel):
    def __init__(self, sorter_store: Gtk.SortListModel):
        self.__sorter_store = sorter_store
        self.__filter = Gtk.CustomFilter()
        self.__filter.set_filter_func(self.__filter_func)

        self.__filter_to_ids = None
        self.__time_min = None
        self.__time_max = None

        super().__init__(model=self.__sorter_store, filter=self.__filter)

    def invalidate_filter(
        self, time_min: Optional[int] = None, time_max: Optional[int] = None
    ) -> None:
        """Invalidate the filter.

        :param Optional[int] time_min: Minimum date/time threshold timestamp
        :param Optional[int] time_max: Maximum date/time threshold timestamp
        """
        self.__time_min = time_min
        self.__time_max = time_max
        self.__filter.set_filter_func(self.__filter_func)

    def restrict_by_id(self, ids: Optional[List[int]]) -> None:
        """Restrict the filter by the provided note ids.

        Applied in addition to any time filter.

        :param Optional[List[int]] ids: The note ids (row primary key, not remote) to be
            filtered by.
        """
        self.__filter_to_ids = ids

    def __filter_func(self, note: Note) -> bool:
        res = not note.locally_deleted
        if self.__filter_to_ids is not None:
            res = res and note.id in self.__filter_to_ids
        else:
            res = res and not note.favourite
            if self.__time_min is not None:
                res = res and note.last_modified > self.__time_min
            if self.__time_max is not None:
                res = res and note.last_modified <= self.__time_max
        return res
