# SPDX-FileCopyrightText: 2022 Chris Heywood
#
# SPDX-License-Identifier: GPL-3.0-or-later

from gi.repository import Gtk, Gio

import logging
from typing import Any, Optional

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


class NoteListModelBase(Gtk.SortListModel):

    def __init__(self) -> None:
        self.__store_note_id_map: dict[int, Note] = {}
        self.__store_remote_id_map: dict[int, Note] = {}

        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.id != -1:
                self.__store_note_id_map[note.id] = note
            else:
                logging.warning("add_notes seeing -1 note id")
                continue
            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.id in self.__store_note_id_map:
                del self.__store_note_id_map[note.id]
            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 fetch_note_by_db_id(self, db_id: int) -> Optional[Note]:
        """Fetch a note by database id"

        :param int db_id: The database id of the note
        """
        if db_id not in self.__store_note_id_map:
            return None
        return self.__store_note_id_map[db_id]

    def ensure_remote_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, model: Gio.ListModel) -> None:
        self.__filter = Gtk.CustomFilter()
        self.__filter.set_filter_func(self.__filter_func)
        self.__category: Optional[Category] = None
        super().__init__(model=model, 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, model: Gio.ListModel) -> None:
        self.__filter = Gtk.CustomFilter()
        self.invalidate_filter()
        super().__init__(model=model, 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
        """
        return note.favourite


class NoteListModelTimeFiltered(Gtk.FilterListModel):

    def __init__(self, model: Gio.ListModel) -> None:
        self.__filter = Gtk.CustomFilter()

        self.__time_min: Optional[int] = None
        self.__time_max: Optional[int] = None

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

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

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

    def __filter_func(self, note: Note) -> bool:
        if note.favourite:
            include = False
        else:
            if self.__time_min is not None:
                include = note.last_modified > self.__time_min
            else:
                include = True
            if self.__time_max is not None:
                include = include and note.last_modified <= self.__time_max
        return include


class NoteListModelSearch(Gtk.FilterListModel):

    def __init__(self, model: Gio.ListModel) -> None:
        self.__filter = Gtk.CustomFilter()
        self.__filter.set_filter_func(self.__filter_func)

        self.__filter_to_ids: Optional[list[int]] = None

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

    def invalidate(self, ids: Optional[list[int]]) -> None:
        """Invalidate filter, restricting by the provided note ids.

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

    def __filter_func(self, note: Note) -> bool:
        if note.locally_deleted:
            include = False
        elif not self.__filter_to_ids:
            include = False
        else:
            include = note.id in self.__filter_to_ids
        return include
