# -*- Mode: python; coding: utf-8; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Copyright (C) 2012 - fossfreedom
# Copyright (C) 2012 - Agustin Carrasco
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.

from datetime import date
from collections import OrderedDict
from collections import namedtuple

from gi.repository import GObject
from gi.repository import Gdk
from gi.repository import RB
from gi.repository import Gio
from gi.repository import GLib

from coverart_browser_prefs import CoverLocale
from coverart_browser_prefs import GSetting
from coverart_utils import create_pixbuf_from_file_at_size
from coverart_utils import GenreConfiguredSpriteSheet
from coverart_utils import ConfiguredSpriteSheet
from coverart_utils import get_stock_size
from coverart_utils import CaseInsensitiveDict
from coverart_utils import Theme
import rb


MenuNodeT = namedtuple('MenuNode', 'label menutype typevalue')


def MenuNode(label, menutype=None, typevalue=None):
    return MenuNodeT(label, menutype, typevalue)


class OptionsController(GObject.Object):
    # properties
    options = GObject.property(type=object, default=None)
    current_key = GObject.property(type=str, default=None)
    update_image = GObject.property(type=bool, default=False)
    enabled = GObject.property(type=bool, default=True)

    def __init__(self):
        super(OptionsController, self).__init__()

        # connect the variations on the current key to the controllers action
        self.connect('notify::current-key', self._do_action)

    def get_current_key_index(self):
        return self.options.index(self.current_key)

    def option_selected(self, key):
        if key != self.current_key:
            # update the current value
            self.current_key = key

    def _do_action(self, *args):
        self.do_action()

    def do_action(self):
        pass

    def get_current_image(self):
        return None

    def get_current_description(self):
        return self.current_key

    def update_images(self, *args):
        pass

    def create_spritesheet(self, plugin, sheet, typestr):
        '''
        helper function to create a specific spritesheet
        '''
        if sheet:
            del sheet

        return ConfiguredSpriteSheet(plugin, typestr, get_stock_size())

    def create_button_image(self, plugin, image, icon_name):
        '''
        helper function to create a button image
        '''
        if image:
            del image

        path = 'img/' + Theme(self.plugin).current + '/'

        return create_pixbuf_from_file_at_size(
            rb.find_plugin_file(self.plugin, path + icon_name),
            *get_stock_size())


class PlaylistPopupController(OptionsController):
    def __init__(self, plugin, album_model):
        super(PlaylistPopupController, self).__init__()

        self._album_model = album_model

        shell = plugin.shell
        self.plugin = plugin

        # get the library name and initialize the superclass with it
        self._library_name = shell.props.library_source.props.name

        # get the queue name
        self._queue_name = shell.props.queue_source.props.name

        if " (" in self._queue_name:
            self._queue_name = self._queue_name[0:self._queue_name.find(" (")]

        self._spritesheet = None
        self._update_options(shell)

        # get the playlist model so we can monitor changes
        playlist_model = shell.props.display_page_model

        # connect signals to update playlists
        playlist_model.connect('row-inserted', self._update_options, shell)
        playlist_model.connect('row-deleted', self._update_options, shell)
        playlist_model.connect('row-changed', self._update_options, shell)

    def update_images(self, *args):
        self._spritesheet = self.create_spritesheet(self.plugin,
                                                    self._spritesheet, 'playlist')

        if args[-1]:
            self.update_image = True

    def _update_options(self, *args):
        shell = args[-1]
        self.update_images(False)

        playlist_manager = shell.props.playlist_manager
        still_exists = self.current_key == self._library_name or \
                       self.current_key == self._queue_name

        # retrieve the options
        values = OrderedDict()

        # library and play queue sources
        values[self._library_name] = None
        values[self._queue_name] = shell.props.queue_source

        # playlists
        playlists_entries = playlist_manager.get_playlists()

        for playlist in playlists_entries:
            if playlist.props.is_local:
                name = playlist.props.name
                values[name] = playlist

                still_exists = still_exists or name == self.current_key

        self.values = values
        self.options = list(values.keys())

        self.current_key = self.current_key if still_exists else \
            self._library_name

    def do_action(self):
        playlist = self.values[self.current_key]

        if not playlist:
            self._album_model.remove_filter('model')
        else:
            self._album_model.replace_filter('model',
                                             playlist.get_query_model())

    def get_current_image(self):
        playlist = self.values[self.current_key]

        if self.current_key == self._library_name:
            image = self._spritesheet['music']
        elif self._queue_name in self.current_key:
            image = self._spritesheet['queue']
        elif isinstance(playlist, RB.StaticPlaylistSource):
            image = self._spritesheet['playlist']
        else:
            image = self._spritesheet['smart']

        return image


class GenrePopupController(OptionsController):
    # properties
    new_genre_icon = GObject.property(type=bool, default=False)

    def __init__(self, plugin, album_model):
        super(GenrePopupController, self).__init__()

        cl = CoverLocale()
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)

        self._album_model = album_model

        shell = plugin.shell
        self.plugin = plugin

        # create a new property model for the genres
        genres_model = RB.RhythmDBPropertyModel.new(shell.props.db,
                                                    RB.RhythmDBPropType.GENRE)

        query = shell.props.library_source.props.base_query_model
        genres_model.props.query_model = query

        # initial genre
        self._initial_genre = _('All Genres')

        self._spritesheet = None
        self._default_image = None
        self._unrecognised_image = None

        self._connect_properties()
        self._connect_signals(query, genres_model)

        # generate initial popup
        self._update_options(genres_model)

    def update_images(self, *args):
        if self._spritesheet:
            del self._spritesheet

        self._spritesheet = GenreConfiguredSpriteSheet(self.plugin,
                                                       'genre', get_stock_size())
        self._default_image = self.create_button_image(self.plugin,
                                                       self._default_image, 'default_genre.png')
        self._unrecognised_image = self.create_button_image(self.plugin,
                                                            self._unrecognised_image, 'unrecognised_genre.png')

        if args[-1]:
            self.update_image = True

    def _connect_signals(self, query, genres_model):
        # connect signals to update genres
        self.connect('notify::new-genre-icon', self._update_options, genres_model)
        query.connect('row-inserted', self._update_options, genres_model)
        query.connect('row-deleted', self._update_options, genres_model)
        query.connect('row-changed', self._update_options, genres_model)

    def _connect_properties(self):
        gs = GSetting()
        setting = gs.get_setting(gs.Path.PLUGIN)

        setting.bind(gs.PluginKey.NEW_GENRE_ICON, self, 'new_genre_icon',
                     Gio.SettingsBindFlags.GET)

    def _update_options(self, *args):
        genres_model = args[-1]

        self.update_images(False)

        still_exists = False

        # retrieve the options
        options = []
        row_num = 0
        for row in genres_model:
            if row_num == 0:
                cl = CoverLocale()
                cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
                genre = _('All Genres')
                row_num = row_num + 1
            else:
                genre = row[0]

            options.append(genre)

            still_exists = still_exists or genre == self.current_key

        self.options = options

        self.current_key = self.current_key if still_exists else \
            self._initial_genre

    def do_action(self):
        '''
        called when genre popup menu item chosen
        return None if the first entry in popup returned
        '''
        if self.current_key == self._initial_genre:
            self._album_model.remove_filter('genre')
        else:
            self._album_model.replace_filter('genre', self.current_key)

    def get_current_image(self):
        test_genre = self.current_key.lower()

        if test_genre == self._initial_genre.lower():
            image = self._default_image
        else:
            image = self._find_alternates(test_genre)

            if image == self._unrecognised_image and \
                            test_genre in self._spritesheet:
                image = self._spritesheet[test_genre]

        return image

    def _find_alternates(self, test_genre):
        # the following genre checks are required
        # 1. if we have user defined genres
        # 2. then check locale specific system genres
        # 3. then check local specific alternates
        # 4. then check if we system genres

        # where necessary check if any of the genres are a substring
        # of test_genre - check in reverse order so that we
        # test largest strings first (prevents spurious matches with
        # short strings)
        # N.B. we use RB.search_fold since the strings can be
        # in a mixture of cases, both unicode (normalized or not) and str
        # and as usual python cannot mix and match these types.


        test_genre = RB.search_fold(test_genre)

        ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_USER)
        if ret:
            return sprite

        for genre in sorted(self._spritesheet.locale_names,
                            key=lambda b: (-len(b), b)):
            if RB.search_fold(genre) in test_genre:
                return self._spritesheet[self._spritesheet.locale_names[genre]]

        # next check locale alternates
        ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_LOCALE)
        if ret:
            return sprite

        ret, sprite = self._match_genres(test_genre, self._spritesheet.GENRE_SYSTEM)
        if ret:
            return sprite

        # check if any of the default genres are a substring
        # of test_genre - check in reverse order so that we
        # test largest strings first (prevents spurious matches with
        # short strings)
        for genre in sorted(self._spritesheet.names,
                            key=lambda b: (-len(b), b)):
            if RB.search_fold(genre) in test_genre:
                return self._spritesheet[genre]

        # if no matches then default to unrecognised image
        return self._unrecognised_image

    def _match_genres(self, test_genre, genre_type):
        case_search = CaseInsensitiveDict(
            dict((k.name, v) for k, v in self._spritesheet.genre_alternate.items()
                 if k.genre_type == genre_type))

        if test_genre in case_search:
            return (True, self._spritesheet[case_search[test_genre]])
        else:
            return (False, None)


    def get_current_description(self):
        cl = CoverLocale()
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
        if self.current_key == self._initial_genre:
            return _('All Genres')
        else:
            return self.current_key


class SortPopupController(OptionsController):
    def __init__(self, plugin, viewmgr):
        super(SortPopupController, self).__init__()

        self._viewmgr = viewmgr
        self.plugin = plugin
        # sorts dictionary
        cl = CoverLocale()
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)

        self.values = OrderedDict([(_('Sort by album name'), 'name'),
                                   (_('Sort by album artist'), 'artist'),
                                   (_('Sort by year'), 'year'),
                                   (_('Sort by rating'), 'rating')])

        self.options = list(self.values.keys())

        # get the current sort key and initialise the superclass
        gs = GSetting()
        source_settings = gs.get_setting(gs.Path.PLUGIN)
        value = source_settings[gs.PluginKey.SORT_BY]

        self._spritesheet = None
        self.update_images(False)

        self.current_key = list(self.values.keys())[
            list(self.values.values()).index(value)]

    def update_images(self, *args):
        self._spritesheet = self.create_spritesheet(self.plugin,
                                                    self._spritesheet, 'sort')

        if args[-1]:
            self.update_image = True

    def do_action(self):
        sort = self.values[self.current_key]

        gs = GSetting()
        settings = gs.get_setting(gs.Path.PLUGIN)
        settings[gs.PluginKey.SORT_BY] = sort

        self._viewmgr.current_view.get_default_manager().emit('sort', "album")

    def get_current_image(self):
        sort = self.values[self.current_key]
        return self._spritesheet[sort]


class ArtistSortPopupController(OptionsController):
    def __init__(self, plugin, viewmgr):
        super(ArtistSortPopupController, self).__init__()

        self._viewmgr = viewmgr
        self.plugin = plugin
        # sorts dictionary
        cl = CoverLocale()
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)

        self.values = OrderedDict([(_('Sort by album name'), 'name_artist'),
                                   (_('Sort by year'), 'year_artist'),
                                   (_('Sort by rating'), 'rating_artist')])

        self.options = list(self.values.keys())

        # get the current sort key and initialise the superclass
        gs = GSetting()
        source_settings = gs.get_setting(gs.Path.PLUGIN)
        value = source_settings[gs.PluginKey.SORT_BY_ARTIST]

        if value not in list(self.values.values()):
            print("here")
            value = 'name_artist'
            source_settings[gs.PluginKey.SORT_BY_ARTIST] = value

        self._spritesheet = None
        self.update_images(False)

        self.current_key = list(self.values.keys())[
            list(self.values.values()).index(value)]
        print(self.current_key)

    def update_images(self, *args):
        self._spritesheet = self.create_spritesheet(self.plugin,
                                                    self._spritesheet, 'sort_artist')

        if args[-1]:
            self.update_image = True

    def do_action(self):
        sort = self.values[self.current_key]

        gs = GSetting()
        settings = gs.get_setting(gs.Path.PLUGIN)
        settings[gs.PluginKey.SORT_BY_ARTIST] = sort

        self._viewmgr.current_view.get_default_manager().emit('sort', "artist")

    def get_current_image(self):
        sort = self.values[self.current_key]
        return self._spritesheet[sort]


class PropertiesMenuController(OptionsController):
    favourites = GObject.property(type=bool, default=False)
    follow = GObject.property(type=bool, default=False)

    def __init__(self, plugin, source):
        super(PropertiesMenuController, self).__init__()

        self._source = source
        self.plugin = plugin
        self._connect_properties()
        # sorts dictionary
        cl = CoverLocale()
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)
        # options
        self.values = OrderedDict()
        self.values[MenuNode(_('Download all covers'))] = 'download'
        self.values[MenuNode(_('Play random album'))] = 'random'
        self.values[MenuNode(_('Follow playing song'), 'check',
                             (True if self.follow else False))] = 'follow'
        self.values[MenuNode('separator1', 'separator')] = ''
        self.values[MenuNode(_('Use favourites only'), 'check',
                             (True if self.favourites else False))] = 'favourite'
        self.values[MenuNode('separator2', 'separator')] = ''
        self.values[MenuNode(_('Browser Preferences'))] = 'browser prefs'
        self.values[MenuNode(_('Search Preferences'))] = 'search prefs'

        self.options = list(self.values.keys())

        self.update_images(False)

        if self.favourites:
            self._source.propertiesbutton_callback('favourite')

        if self.follow:
            self._source.propertiesbutton_callback('follow')

        self.current_key = None

    def _connect_properties(self):
        gs = GSetting()
        setting = gs.get_setting(gs.Path.PLUGIN)
        setting.bind(
            gs.PluginKey.USE_FAVOURITES,
            self,
            'favourites',
            Gio.SettingsBindFlags.DEFAULT)
        setting.bind(
            gs.PluginKey.FOLLOWING,
            self,
            'follow',
            Gio.SettingsBindFlags.DEFAULT)

    def _change_key(self, dict, old, new):
        for i in range(len(dict)):
            k, v = dict.popitem(False)
            dict[new if old == k else k] = v

    def update_images(self, *args):
        self._image = self.create_button_image(self.plugin,
                                               None, 'properties.png')

        if args[-1]:
            self.update_image = True

    def do_action(self):
        if self.current_key:
            key = [node for node in self.values if node.label == self.current_key]

            if self.current_key == _('Use favourites only'):
                self.favourites = not self.favourites

            if self.current_key == _('Follow playing song'):
                self.follow = not self.follow

            self._source.propertiesbutton_callback(self.values[key[0]])
            self.current_key = None

    def get_current_image(self):
        return self._image

    def get_current_description(self):
        return _('Properties')


class DecadePopupController(OptionsController):
    def __init__(self, plugin, album_model):
        super(DecadePopupController, self).__init__()

        self._album_model = album_model
        self.plugin = plugin

        self._spritesheet = None

        # decade options
        cl = CoverLocale()
        cl.switch_locale(cl.Locale.LOCALE_DOMAIN)

        self.values = OrderedDict()

        self.values[_('All Decades')] = [-1, 'All Decades']
        # '20s' as in the decade 2010
        self.values[_('20s')] = [2020, '20s']
        #'10s' as in the decade 2010
        self.values[_('10s')] = [2010, '10s']
        #'00s' as in the decade 2000
        self.values[_('00s')] = [2000, '00s']
        #'90s' as in the decade 1990
        self.values[_('90s')] = [1990, '90s']
        #'80s' as in the decade 1980
        self.values[_('80s')] = [1980, '80s']
        #'70s' as in the decade 1970
        self.values[_('70s')] = [1970, '70s']
        #'60s' as in the decade 1960
        self.values[_('60s')] = [1960, '60s']
        #'50s' as in the decade 1950
        self.values[_('50s')] = [1950, '50s']
        #'40s' as in the decade 1940
        self.values[_('40s')] = [1940, '40s']
        #'30s' as in the decade 1930
        self.values[_('30s')] = [1930, '30s']
        #'Older' as in 'older than the year 1930'
        self.values[_('Older')] = [-1, 'Older']

        self.options = list(self.values.keys())

        # if we aren't on the 20s yet, remove it
        if date.today().year < 2020:
            self.options.remove(_('20s'))

        # define a initial decade an set the initial key
        self._initial_decade = self.options[0]
        self.update_images(False)

        self.current_key = self._initial_decade

    def update_images(self, *args):
        self._spritesheet = self.create_spritesheet(self.plugin,
                                                    self._spritesheet, 'decade')

        if args[-1]:
            self.update_image = True

    def do_action(self):
        if self.current_key == self._initial_decade:
            self._album_model.remove_filter('decade')
        else:
            self._album_model.replace_filter('decade',
                                             self.values[self.current_key][0])

    def get_current_image(self):
        decade = self.values[self.current_key][1]
        return self._spritesheet[decade]

    def get_current_description(self):
        return self.current_key


class SortOrderToggleController(OptionsController):
    toolbar_type = "album"

    def __init__(self, plugin, viewmgr):
        super(SortOrderToggleController, self).__init__()

        self._viewmgr = viewmgr
        self.plugin = plugin

        # options
        self.values = OrderedDict([(_('Sort in descending order'), False),
                                   (_('Sort in ascending order'), True)])
        self.options = list(self.values.keys())

        self._images = []

        # set the current key
        self.gs = GSetting()
        self.settings = self.gs.get_setting(self.gs.Path.PLUGIN)
        self.key = self.get_key()
        sort_order = self.settings[self.key]
        self.current_key = list(self.values.keys())[
            list(self.values.values()).index(sort_order)]
        self.update_images(False)

    def get_key(self):
        return self.gs.PluginKey.SORT_ORDER

    def update_images(self, *args):
        # initialize images
        if len(self._images) > 0:
            del self._images[:]

        self._images.append(self.create_button_image(self.plugin,
                                                     None, 'arrow_down.png'))
        self._images.append(self.create_button_image(self.plugin,
                                                     None, 'arrow_up.png'))

        if args[-1]:
            self.update_image = True

    def do_action(self):
        sort_order = self.values[self.current_key]
        self.settings[self.key] = sort_order
        self._viewmgr.current_view.get_default_manager().emit('sort', self.toolbar_type)

    def get_current_image(self):
        return self._images[self.get_current_key_index()]


class ArtistSortOrderToggleController(SortOrderToggleController):
    toolbar_type = "artist"

    def __init__(self, plugin, model):
        super(ArtistSortOrderToggleController, self).__init__(plugin, model)

    def get_key(self):
        return self.gs.PluginKey.SORT_ORDER_ARTIST


class AlbumSearchEntryController(OptionsController):
    # properties
    search_text = GObject.property(type=str, default='')

    def __init__(self, album_model):
        super(AlbumSearchEntryController, self).__init__()

        self._album_model = album_model
        self._filter_type = 'all'

        # options
        self.values = OrderedDict()
        self.values[_('Search all fields')] = 'all'
        self.values[_('Search album artists')] = 'album_artist'
        self.values[_('Search track artists')] = 'artist'
        self.values[_('Search composers')] = 'composers'
        self.values[_('Search albums')] = 'album_name'
        self.values[_('Search titles')] = 'track'

        self.options = list(self.values.keys())
        self.current_key = list(self.values.keys())[0]

        self._typing = False
        self._typing_counter = 0
        self._current_search_text = ""

    def do_action(self):
        # remove old filter
        self._album_model.remove_filter(self._filter_type, False)

        # assign the new filter
        self._filter_type = self.values[self.current_key]

        self.do_search(self.search_text, True)

    def _search_typing(self, *args):
        self._typing_counter = self._typing_counter + 1

        if self._typing_counter >= 4 and self._typing:
            self._typing = False
            self._change_filter(self._current_search_text, False)

        return self._typing

    def _change_filter(self, search_text, force):
        # self.search_text = search_text
        self._current_search_text = search_text

        if search_text:
            self._album_model.replace_filter(self._filter_type,
                                             search_text)
        elif not force:
            self._album_model.remove_filter(self._filter_type)

    def do_search(self, search_text, force=False):
        '''
        if self.search_text != search_text or force:
            self.search_text = search_text

            if search_text:
                self._album_model.replace_filter(self._filter_type,
                    search_text)
            elif not force:
                self._album_model.remove_filter(self._filter_type)

        '''
        # self.search_text = search_text
        if force:
            self._typing_counter = 99
            self._typing = False
            self._change_filter(search_text, force)
            return

        if self._current_search_text != search_text:

            #self.search_text = search_text
            self._current_search_text = search_text
            self._typing_counter = 0

            if not self._typing:
                self._typing = True

                Gdk.threads_add_timeout(GLib.PRIORITY_DEFAULT_IDLE, 100,
                                        self._search_typing)


class AlbumQuickSearchController(object):
    def __init__(self, album_manager):
        self._album_manager = album_manager

    def connect_quick_search(self, quick_search):
        quick_search.connect('quick-search', self._on_quick_search)
        quick_search.connect('arrow-pressed', self._on_arrow_pressed)
        quick_search.connect('hide', self._on_hide)

    def _on_quick_search(self, quick_search, search_text, *args):
        album = self._album_manager.model.find_first_visible('album_name',
                                                             search_text)

        if album:
            path = self._album_manager.model.get_path(album)
            self._album_manager.current_view.select_and_scroll_to_path(path)

    def _on_arrow_pressed(self, quick_search, key, *args):
        current = self._album_manager.current_view.get_selected_objects()[0]
        search_text = quick_search.get_text()
        album = None

        if key == Gdk.KEY_Up:
            album = self._album_manager.model.find_first_visible(
                'album_name', search_text, current, True)
        elif key == Gdk.KEY_Down:
            album = self._album_manager.model.find_first_visible(
                'album_name', search_text, current)

        if album:
            path = self._album_manager.model.get_path(album)
            self._album_manager.current_view.select_and_scroll_to_path(path)

    def _on_hide(self, quick_search, *args):
        self._album_manager.current_view.grab_focus()


class ViewController(OptionsController):
    def __init__(self, shell, viewmgr):
        super(ViewController, self).__init__()

        self._viewmgr = viewmgr

        from coverart_browser_source import Views

        views = Views(shell)

        self.values = OrderedDict()
        for view_name in views.get_view_names():
            self.values[views.get_menu_name(view_name)] = view_name
            print(view_name)

        self.options = list(self.values.keys())
        viewmgr.connect('new-view', self.on_notify_view_name)

    def on_notify_view_name(self, *args):
        for key in self.options:
            if self.values[key] == self._viewmgr.view_name:
                self.current_key = key

    def do_action(self):
        if self._viewmgr.view_name != self.values[self.current_key]:
            self._viewmgr.view_name = self.values[self.current_key]
