# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Elisa with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.
#
# Authors:  Philippe Normand <philippe@fluendo.com>
#           Olivier Tilloy <olivier@fluendo.com>

from elisa.core import common
from elisa.core.utils import defer
from elisa.core.media_uri import MediaUri
from elisa.core.input_event import EventValue
from elisa.core.utils.i18n import install_translation, get_current_locale

from elisa.plugins.pigment.pigment_controller import PigmentController
from elisa.plugins.pigment.graph.text import Text
from elisa.plugins.pigment.widgets.box import VBox, HBox
from elisa.plugins.pigment.widgets.list_vertical import ListVertical

from elisa.plugins.poblesec.base.list import BaseListController
from elisa.plugins.poblesec.base.info_list import \
    ListItemWidgetWithoutActions, ButtonItem
from elisa.plugins.poblesec.actions import OpenControllerAction
from elisa.plugins.poblesec.modal_popup import Popup
from elisa.plugins.poblesec.widgets.panel_screen import PanelScreen
from elisa.plugins.poblesec.widgets.background import WidgetWithBackground
from elisa.plugins.poblesec.widgets.button import PanelButton
from elisa.plugins.poblesec.widgets.keyboard import Keyboard, TextEntry
from elisa.plugins.poblesec.widgets.menu_item import ThreeLineMenuItemWidget

from elisa.plugins.database import models, video_parser
from elisa.plugins.database.actions import PlayVideoAction

from elisa.plugins.thetvdb import utils as tvdb_utils
from elisa.plugins.thetvdb import models as thetvdb_models

from elisa.plugins.themoviedb import utils as themoviedb_utils

from twisted.internet import task

import cgi


_ = install_translation('database')
_p = install_translation('poblesec')

class RecategorizeButtonItem(ButtonItem):

    def __init__(self):
        super(RecategorizeButtonItem, self).__init__()
        self.panel.set_name("darker")

class RecategorizeMenuItemWidget(ThreeLineMenuItemWidget):

    def __init__(self):
        super(RecategorizeMenuItemWidget, self).__init__()
        self.panel.set_name("darker")


class RecategorizePanelScreen(PanelScreen):

    """
    Specialized panel screen that embeds:
     - A left panel containing a top title bar, text contents and a bottom
       button bar.
     - A right panel containing a top title bar and a list as contents.

    @cvar node_widget:     the widget class that embeds the item widget and its
                           contextual actions if any
    @cvar item_widget_cls: the item widget class
    """

    node_widget = ListItemWidgetWithoutActions
    item_widget_cls = RecategorizeButtonItem

    def __init__(self, controller, contextual_actions):
        # controller may be used when implementing create_buttons, to link the
        # buttons' callbacks to some of the controller's methods.
        self._controller = controller
        self._contextual_actions = contextual_actions
        super(RecategorizePanelScreen, self).__init__()

    def create_header(self):
        # Overridden from mother class
        header = WidgetWithBackground(foreground=Text())
        header.visible = True
        header.foreground.label = _('VIDEO RECATEGORIZATION')
        return header

    def create_left_panel(self):
        # Overridden from mother class
        panel = VBox()
        panel.visible = True
        panel.title = self._create_title()
        panel.pack_start(panel.title)
        panel.contents = self._create_left_contents()
        panel.pack_start(panel.contents, expand=True)
        panel.button_bar = self._create_button_bar()
        panel.pack_start(panel.button_bar)
        return panel

    def create_right_panel(self):
        # Overridden from mother class
        panel = VBox()
        panel.visible = True
        panel.title = self._create_title()
        panel.pack_start(panel.title)
        panel.contents = self._create_right_contents()
        panel.pack_start(panel.contents, expand=True)
        return panel

    def _create_title(self):
        title = WidgetWithBackground(foreground=Text())
        title.visible = True
        return title

    def _create_left_contents(self):
        contents = WidgetWithBackground(foreground=Text())
        contents.visible = True
        return contents

    def _create_button_bar(self):
        button_bar = HBox()
        button_bar.visible = True
        button_bar.navigable = True
        return button_bar

    def add_button(self, label):
        """
        Add a button to the left panel's button bar.

        The caller is responsible for connecting to the button's 'activated'
        signal.

        @param label: the text label of the button
        @type label:  C{unicode}

        @return:      the button
        @rtype:       L{elisa.plugins.poblesec.widgets.button.PanelButton}
        """
        new_button = PanelButton()
        new_button.visible = True
        new_button.text.label = label
        button_bar = self.left_panel.button_bar
        button_bar.pack_start(new_button)
        button_width = 1.0 / len(button_bar)
        for button in button_bar:
            button.width = button_width
        return new_button

    def _create_right_contents(self):
        widget_kwargs = {'item_widget_cls': self.item_widget_cls,
                         'contextual_actions': self._contextual_actions}
        contents = ListVertical(self.node_widget, visible_range_size=6,
                                widget_kwargs=widget_kwargs)
        contents.visible = True
        contents.render_empty_items = True
        return contents


class RecategorizePanelController(BaseListController):

    """
    A controller that displays a recategorize panel screen with a vertical list
    of items/actions associated to the model.

    @cvar panel_screen_cls: the panel screen class
    """

    panel_screen_cls = RecategorizePanelScreen

    def set_frontend(self, frontend):
        super(RecategorizePanelController, self).set_frontend(frontend)
        self.populate_screen()
        self.create_buttons()
        # Set up the navigation between the list and the button bar
        panel = self.panel_screen
        self.widget.set_focus_proxy(panel.right_panel)
        panel.contents.navigable = True
        button_bar = panel.left_panel.button_bar
        panel.left_panel.set_focus_proxy(button_bar)
        panel.right_panel.set_focus_proxy(self.nodes)

    def nodes_setup(self):
        self.panel_screen = \
            self.panel_screen_cls(self, self._contextual_actions)
        self.panel_screen.set_name('panel_screen')
        self.widget.add(self.panel_screen)
        self.panel_screen.visible = True
        right_panel = self.panel_screen.right_panel
        self.nodes = right_panel.contents

    def populate_screen(self):
        """
        Populate the static information of the screen.

        This includes the main header, the left and right panels' titles, and
        the left panel's contents.

        This method must be overridden in subclasses.
        """
        raise NotImplementedError()

    def create_buttons(self):
        """
        Create the left panel's buttons.

        This method must be overridden in subclasses.
        """
        raise NotImplementedError()

    def cancel(self, button):
        # Go back to the start screen.
        browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
        return browser.history.goto_mark('start_recategorization')


class SearchMoviesMixin(object):

    def search_movies(self, name):
        """
        Search for movies matching a given name on themoviedb.org.

        @param name: the search term
        @type name:  C{unicode}

        @return:     a deferred fired when the search is complete with a list of
                     matching movies
                     (C{list} of L{elisa.plugins.themoviedb.models.MovieModel})
        @rtype:      L{elisa.core.utils.defer.Deferred}
        """
        def got_search_results(movies_model):
            if movies_model and movies_model.movies:
                return movies_model.movies
            else:
                return []

        lookup_url = themoviedb_utils.get_movie_lookup_url(name, True)
        model, dfr = common.application.resource_manager.get(lookup_url)
        dfr.addCallbacks(got_search_results,
                         lambda failure: got_search_results(None))
        return dfr


class RecategorizeToMovieAction(OpenControllerAction, SearchMoviesMixin):

    """
    Action to start recategorizing a video as a movie.
    It performs an initial lookup based on the video filename.
    """

    label = _('Movie')

    def __init__(self, controller):
        super(RecategorizeToMovieAction, self).__init__(controller, None)

    def execute(self, item=None):
        video = self.controller.video

        def got_search_results(movies):
            kwargs = {'video': video, 'first_shot': True}
            if movies:
                path = '/poblesec/database/recategorize/movie'
                kwargs['movies'] = movies
                title = _('Movies')
            else:
                path = '/poblesec/database/recategorize/noresults/movie'
                title = _('No Results')

            return self.open_controller(path, title, **kwargs)

        parser = video_parser.VideoParser(common.application.store)
        cleaned_video_name = parser.clean_video_name(video.file_path)
        dfr = self.search_movies(cleaned_video_name)
        dfr.addCallback(got_search_results)
        return dfr


class SearchTVEpisodesMixin(object):

    def search_tvepisodes(self, show, season, episode):
        """
        Search for TV episodes matching a given name on thetvdb.org.

        @param show:    the show name to search
        @type show:     C{unicode}
        @param season:  the season number
        @type season:   C{int}
        @param episode: the episode number
        @type episode:  C{int}

        @return:     a deferred fired when the search is complete with a list of
                     matching TV shows / TV episodes
                     (C{list} of C{tuple} of
                     (L{elisa.plugins.thetvdb.models.TvSeriesModel},
                      L{elisa.plugins.thetvdb.models.TvEpisodeModel}))
        @rtype:      L{elisa.core.utils.defer.Deferred}
        """
        resource_manager = common.application.resource_manager

        def get_season_and_episode(tvdb_model, season, episode):
            if tvdb_model and tvdb_model.series:
                # Get the matching season and episode for each show.
                episodes = []
                lang = unicode(get_current_locale().split('_')[0]).lower()

                def append_episode(episode, show, episodes):
                    episodes.append((show, episode))

                def iterate_shows(tvdb_model, episodes):
                    for show in tvdb_model.series:
                        uri = show.episode_url(season, episode, lang)
                        episode_model = thetvdb_models.TvEpisodeModel()
                        model, dfr = resource_manager.get(uri, episode_model)
                        dfr.addCallbacks(append_episode, lambda failure: None,
                                         callbackArgs=(show, episodes))
                        yield dfr

                dfr = task.coiterate(iterate_shows(tvdb_model, episodes))
                dfr.addCallback(lambda result: episodes)
                return dfr
            else:
                return []

        def error_searching_shows(failure):
            # Swallow the failure and return an empty list of episodes.
            return []

        lookup_url = tvdb_utils.get_series_lookup_url(show)
        model, dfr = resource_manager.get(lookup_url)
        dfr.addCallbacks(get_season_and_episode, error_searching_shows,
                         callbackArgs=(season, episode))
        return dfr


class RecategorizeToTVEpisodeAction(OpenControllerAction,
                                    SearchTVEpisodesMixin):

    """
    Action to start recategorizing a video as a TV episode.
    It performs an initial lookup based on the video filename.
    """

    label = _('TV Episode')

    def __init__(self, controller):
        super(RecategorizeToTVEpisodeAction, self).__init__(controller, None)

    def execute(self, item=None):
        video = self.controller.video

        def got_search_results(episodes):
            kwargs = {'video': video, 'first_shot': True}
            if episodes:
                path = '/poblesec/database/recategorize/tvepisode'
                kwargs['episodes'] = episodes
                title = _('TV Episodes')
            else:
                path = '/poblesec/database/recategorize/noresults/tvepisode'
                title = _('No Results')

            return self.open_controller(path, title, **kwargs)

        parser = video_parser.VideoParser(common.application.store)
        tvshow_match = parser.try_tvshow_match(video.file_path)
        if tvshow_match is not None:
            show, season, episode, remain = tvshow_match.groups()
            show = parser.clean_video_name(show)
            dfr = self.search_tvepisodes(show, int(season), int(episode))
            dfr.addCallback(got_search_results)
            return dfr
        else:
            return got_search_results([])


class ChooseCategoryController(RecategorizePanelController):

    """
    The first recategorize controller: lets the user choose a category.
    """

    panel_screen_cls = RecategorizePanelScreen

    _instructions = \
        _("<b>Define Video Kind</b>\n" \
          "We are about to begin recategorizing the video called '<b>%(filename)s</b>'." \
          "First select what kind of video this is from the list of options " \
          "at the right of this message.\n\n" \
          "You may preview the video file by selecting 'Video Preview' which " \
          "may help determine what kind of Video this is. Select 'Cancel' to " \
          "cancel recategorizing this video.")

    def initialize(self, video):
        self.video = video
        uri = MediaUri({'scheme': 'file', 'path': self.video.file_path})
        self._filename = uri.filename
        return super(ChooseCategoryController, self).initialize()

    def populate_screen(self):
        # Overridden from mother class.
        screen = self.panel_screen
        screen.left_panel.title.foreground.label = _('Instructions')
        screen.left_panel.contents.foreground.markup = \
            self._instructions % {'filename': cgi.escape(self._filename)}
        screen.right_panel.title.foreground.label = _('Video Kind')

    def create_buttons(self):
        # Overridden from mother class.
        screen = self.panel_screen
        self._cancel_button = screen.add_button(_('Cancel'))
        self._cancel_button.connect('activated', self.cancel)
        self._preview_button = screen.add_button(_('Video Preview'))
        self._preview_button.connect('activated', self.preview)

    def clean(self):
        self._preview_button.disconnect_by_func(self.preview)
        self._cancel_button.disconnect_by_func(self.cancel)
        return super(ChooseCategoryController, self).clean()

    def populate_model(self):
        movie = RecategorizeToMovieAction(self)
        tvepisode = RecategorizeToTVEpisodeAction(self)
        model = [movie, tvepisode]
        return defer.succeed(model)

    def node_renderer(self, item, widget):
        widget.item_widget.label.label = item.label

    def item_activated(self, item):
        return item.execute()

    def preview(self, button):
        play = PlayVideoAction(self)
        # Always pass the player a "raw" Video model for preview.
        if isinstance(self.video, (models.Movie, models.TVEpisode)):
            dfr = self.video.video
            dfr.addCallback(play.execute)
            return dfr
        else:
            return play.execute(self.video)


class NoResultsController(PigmentController):

    """
    Controller that displays an informative popup when the lookup returned no
    results. The user is allowed to cancel the recategorization or manually
    enter information to refine the search.
    """

    _initial_message = \
        _("Moovida has performed a quick online search based on the video " \
          "entitled '<b>%(filename)s</b>' in order to determine what the true identity " \
          "of this video might be. Unfortunately Moovida was unable to find " \
          "any results based on the file name alone.\n\n" \
          "You may manually enter some basic details for this video file by " \
          "selecting 'Refine Search'." \
          "Doing so will retrieve new improved search results for the " \
          "possible identity of this video.\n\n" \
          "Alternatively, select 'Cancel' to cancel recategorizing this " \
          "video file.")

    def initialize(self, video, first_shot):
        self.video = video
        self.first_shot = first_shot
        return super(NoResultsController, self).initialize()

    def set_frontend(self, frontend):
        super(NoResultsController, self).set_frontend(frontend)
        title = _('NO RESULTS FOUND')
        message = Popup(title, self._get_subtitle(),
                        self._get_message(), self._get_buttons())
        message.set_name('message')
        self.widget.add(message)
        message.visible = True
        self.widget.set_focus_proxy(message)

    def _get_subtitle(self):
        if self.first_shot:
            return _('Initial Search Returned No Results')
        else:
            return _('Manual Entry Returned No Results')

    def _get_message(self):
        if self.first_shot:
            uri = MediaUri({'scheme': 'file', 'path': self.video.file_path})
            return self._initial_message % {'filename': uri.filename}
        else:
            return self._get_updated_message()

    def _get_updated_message(self):
        # To be overridden by subclasses.
        raise NotImplementedError()

    def _get_buttons(self):
        buttons = []
        buttons.append((_('Cancel'), self._cancel))
        if not self.first_shot:
            buttons.append((_('Continue'), self._continue))
        buttons.append((_('Refine Search'), self._refine))
        return buttons

    def _close(self):
        browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
        return browser.history.go_back()

    def _cancel(self):
        # Go back to the initial screen.
        browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
        return browser.history.goto_mark('start_recategorization')

    def _refine(self):
        # Open the "refine search" controller to enter details.
        # To be overridden by subclasses.
        return defer.fail(NotImplementedError())

    def _continue(self):
        # Use the supplied details to add a manual entry in the DB.
        # To be overridden by subclasses.
        return defer.fail(NotImplementedError())

    def handle_input(self, manager, event):
        if event.value == EventValue.KEY_MENU:
            # Specific handling of the BACK event to not allow going back to the
            # previous screen.
            self._cancel()
            return True

        return super(NoResultsController, self).handle_input(manager, event)


class NoMovieResultsController(NoResultsController):

    """
    Specialized "no search results" screen when recategorizing into a movie.
    """

    _updated_message = \
        _("Moovida has performed an online search based on the following " \
          "information:\n\n" \
          "Movie Title:\t\t%(title)s\n\n" \
          "Unfortunately Moovida was unable to find any results based on " \
          "this information.\n\n" \
          "To edit some details or start over select 'Refine Search'. " \
          "Select 'Continue' to recategorize the video with the information " \
          "above (Note: no additional information or artwork will be added). " \
          "Alternatively, select 'Cancel' to cancel recategorizing this " \
          "video file.")

    def initialize(self, video, first_shot, movie_title=None):
        self.movie_title = movie_title
        return super(NoMovieResultsController,
                     self).initialize(video, first_shot)

    def _get_updated_message(self):
        # Overridden from mother class.
        return self._updated_message % {'title': self.movie_title}

    def _refine(self):
        # Overridden from mother class.
        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/search/movie'
        title = _('Search')
        kwargs = {'video': self.video, 'first_shot': self.first_shot}
        dfr = self._close()
        dfr.addCallback(lambda result: \
                        action.open_controller(path, title, **kwargs))
        return dfr

    def _continue(self):
        # Overridden from mother class.
        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/confirm/movie/manual'
        title = _('Confirmation')
        dfr = self._close()
        dfr.addCallback(lambda result: \
                        action.open_controller(path, title, video=self.video,
                                               movie_title=self.movie_title))
        return dfr


class NoTVEpisodeResultsController(NoResultsController):

    """
    Specialized "no search results" screen when recategorizing into a
    TV episode.
    """

    _updated_message = \
        _("Moovida has performed an online search based on the following " \
          "information:\n\n" \
          "Series Title:\t\t%(title)s\n" \
          "Season Number:\t%(season)d\n" \
          "Episode Number:\t%(episode)d\n\n" \
          "Unfortunately Moovida was unable to find any results based on " \
          "this information.\n\n" \
          "To edit some details or start over select 'Refine Search'. " \
          "Select 'Continue' to recategorize the video with the information " \
          "above (Note: no additional information or artwork will be added). " \
          "Alternatively, select 'Cancel' to cancel recategorizing this " \
          "video file.")

    def initialize(self, video, first_shot, show_name=None,
                   season_number=None, episode_number=None):
        self.show_name = show_name
        self.season_number = season_number
        self.episode_number = episode_number
        return super(NoTVEpisodeResultsController,
                     self).initialize(video, first_shot)

    def _get_updated_message(self):
        # Overridden from mother class.
        return self._updated_message % \
                    {'title': self.show_name,
                     'season': self.season_number,
                     'episode': self.episode_number}

    def _refine(self):
        # Overridden from mother class.
        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/search/tvepisode'
        title = _('Series')
        kwargs = {'video': self.video, 'first_shot': self.first_shot}
        dfr = self._close()
        dfr.addCallback(lambda result: \
                        action.open_controller(path, title, **kwargs))
        return dfr

    def _continue(self):
        # Overridden from mother class.
        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/confirm/tvepisode/manual'
        title = _('Confirmation')
        episode_name = _(u'Episode %(number)d') % {'number': int(self.episode_number)}
        dfr = self._close()
        dfr.addCallback(lambda result: \
            action.open_controller(path, title, video=self.video,
                                   show_name=self.show_name,
                                   season_number=int(self.season_number),
                                   episode_number=int(self.episode_number),
                                   episode_name=episode_name))
        return dfr


class ChooseFromSearchResultsController(RecategorizePanelController):

    """
    Generic controller that allows the user to pick a match from a list of
    search results.
    """

    panel_screen_cls = RecategorizePanelScreen

    def initialize(self, video, first_shot):
        self.video = video
        uri = MediaUri({'scheme': 'file', 'path': self.video.file_path})
        self._filename = uri.filename
        self.first_shot = first_shot
        return super(ChooseFromSearchResultsController, self).initialize()

    def get_instructions(self):
        # Return the instructions string.
        # To be overridden by subclasses.
        raise NotImplementedError()

    def populate_screen(self):
        # Overridden from mother class.
        screen = self.panel_screen
        screen.left_panel.title.foreground.label = _('Instructions')
        screen.left_panel.contents.foreground.markup = self.get_instructions()
        if self.first_shot:
            title = _('Initial Search Results')
        else:
            title = _('Updated Search Results')
        screen.right_panel.title.foreground.label = title

    def create_buttons(self):
        # Overridden from mother class.
        screen = self.panel_screen
        self._cancel_button = screen.add_button(_('Cancel'))
        self._cancel_button.connect('activated', self.cancel)
        self._refine_button = screen.add_button(_('Refine Search'))
        self._refine_button.connect('activated', self.refine)
        if not self.first_shot:
            self._manual_button = screen.add_button(_('Manual Entry'))
            self._manual_button.connect('activated', self.manual)

    def clean(self):
        if not self.first_shot:
            self._manual_button.disconnect_by_func(self.manual)
        self._refine_button.disconnect_by_func(self.refine)
        self._cancel_button.disconnect_by_func(self.cancel)
        return super(ChooseFromSearchResultsController, self).clean()

    def _close(self):
        # Go back one screen (close this controller).
        browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
        return browser.history.go_back()

    def refine(self, button):
        # Open the relevant search controller to refine the search.
        # To be overridden by subclasses.
        return defer.fail(NotImplementedError())

    def manual(self, button):
        # Open the relevant controller to enter manual information.
        # To be overridden by subclasses.
        return defer.fail(NotImplementedError())


class ChooseMovieFromSearchResultsController(ChooseFromSearchResultsController):

    """
    Controller created to display a list of movie search results to select from.
    """

    _instructions_first_shot = \
        _("<b>Finding Your Video</b>\n" \
          "We believe the video file '<b>%(filename)s</b>' which you are about to " \
          "recategorize is one of the Movies displayed to the right of this " \
          "message.\n\n" \
          "Please select one of the options to the right to begin " \
          "recategorizing it with all available information.\n\n" \
          "If the correct Movie does not appear to the right of this message " \
          "then please select 'Refine Search' to manually enter some basic " \
          "details and perform a new search.\n\n" \
          "Select 'Cancel' to cancel recategorizing this video file.")

    _instructions_refined = \
        _("<b>Finding Your Video</b>\n" \
          "Your video search results have been refined and updated.\n\n" \
          "We believe the video file '<b>%(filename)s</b>' which you are about to " \
          "recategorize is one of the Movies displayed to the right of this " \
          "message.\n\n" \
          "Please select one of the options to the right to begin " \
          "recategorizing it with all available information.\n\n" \
          "If the correct Movie does not appear to the right of this message " \
          "then please select 'Refine Search' to manually enter some basic " \
          "details and perform a new search.\n\n" \
          "Select 'Cancel' to cancel recategorizing this video file.")

    def initialize(self, video, first_shot, movies):
        self.movies = movies
        return super(ChooseMovieFromSearchResultsController,
                     self).initialize(video, first_shot)

    def populate_model(self):
        return defer.succeed(self.movies)

    def get_instructions(self):
        # Overridden from mother class.
        if self.first_shot:
            return self._instructions_first_shot % {'filename': cgi.escape(self._filename)}
        else:
            return self._instructions_refined % {'filename': cgi.escape(self._filename)}

    def node_renderer(self, item, widget):
        widget.item_widget.label.label = item.title

    def item_activated(self, item):
        path = '/poblesec/database/recategorize/confirm/movie'
        action = OpenControllerAction(self, None)
        return action.open_controller(path, _('Confirmation'),
                                      video=self.video, movie=item)

    def refine(self, button):
        # Overridden from mother class.
        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/search/movie'
        title = _('Search')
        dfr = self._close()
        dfr.addCallback(lambda result: \
                        action.open_controller(path, title, video=self.video,
                                               first_shot=False))
        return dfr

    def manual(self, button):
        # Overridden from mother class.
        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/manual/movie'
        title = _('Manual Entry')
        dfr = self._close()
        dfr.addCallback(lambda result: \
                        action.open_controller(path, title, video=self.video))
        return dfr


class TVEpisodeRecategorizePanelScreen(RecategorizePanelScreen):

    item_widget_cls = RecategorizeMenuItemWidget


class ChooseTVEpisodeFromSearchResultsController(ChooseFromSearchResultsController):

    """
    Controller created to display a list of TV episode search results to select
    from.
    """

    panel_screen_cls = TVEpisodeRecategorizePanelScreen

    _instructions_first_shot = \
        _("<b>Finding Your Video</b>\n" \
          "We believe the video file '<b>%(filename)s</b>' which you are about to " \
          "recategorize is a TV episode belonging to one of the seasons " \
          "displayed to the right of this message.\n\n" \
          "Please select on of the options to the right to begin " \
          "recategorizing it with all available information.\n\n" \
          "If the correct TV season does not appear to the right of this " \
          "message then please select 'Refine Search' to manually enter some " \
          "basic details and perform a new search.\n\n" \
          "Select 'Cancel' to cancel recategorizing this video file.")

    _instructions_refined = \
        _("<b>Finding Your Video</b>\n" \
          "Your video search results have been refined and updated.\n\n" \
          "We believe the video file '<b>%(filename)s</b>' which you are about to " \
          "recategorize is a TV episode belonging to one of the seasons " \
          "displayed to the right of this message.\n\n" \
          "Please select on of the options to the right to begin " \
          "recategorizing it with all available information.\n\n" \
          "If the correct TV season does not appear to the right of this " \
          "message then please select 'Refine Search' to manually enter some " \
          "basic details and perform a new search.\n\n" \
          "Select 'Cancel' to cancel recategorizing this video file.")

    def initialize(self, video, first_shot, episodes):
        # episodes is a list of tuples (show, episode).
        self.episodes = episodes
        return super(ChooseTVEpisodeFromSearchResultsController,
                     self).initialize(video, first_shot)

    def populate_model(self):
        return defer.succeed(self.episodes)

    def get_instructions(self):
        # Overridden from mother class.
        if self.first_shot:
            return self._instructions_first_shot % {'filename': cgi.escape(self._filename)}
        else:
            return self._instructions_refined % {'filename': cgi.escape(self._filename)}

    def node_renderer(self, item, widget):
        show, episode = item
        # NOTE: <Episode number>. <Episode name>
        widget.item_widget.label.label = \
            _('%(number)d. %(name)s') % {'number': episode.episode_number,
                                         'name': episode.name}
        widget.item_widget.sublabel.label = show.name
        widget.item_widget.sublabel2.label = \
            _('Season %(number)d') % {'number': episode.season_number}

    def item_activated(self, item):
        path = '/poblesec/database/recategorize/confirm/tvepisode'
        action = OpenControllerAction(self, None)
        show, episode = item
        return action.open_controller(path, _('Confirmation'), video=self.video,
                                      show=show, episode=episode)

    def refine(self, button):
        # Overridden from mother class.
        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/search/tvepisode'
        title = _('Series')
        dfr = self._close()
        dfr.addCallback(lambda result: \
                        action.open_controller(path, title, video=self.video,
                                               first_shot=False))
        return dfr

    def manual(self, button):
        # Overridden from mother class.
        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/manual/tvepisode'
        title = _('Manual Entry')
        dfr = self._close()
        dfr.addCallback(lambda result: \
                        action.open_controller(path, title, video=self.video))
        return dfr


class RecategorizationEntryController(PigmentController):

    """
    A Generic OSK/entry controller for all sorts of manual entry for the
    recategorization (initial search, refine search, manual entry).
    """

    def initialize(self, video):
        self.video = video
        uri = MediaUri({'scheme': 'file', 'path': self.video.file_path})
        self._filename = uri.filename
        return super(RecategorizationEntryController, self).initialize()

    def set_frontend(self, frontend):
        super(RecategorizationEntryController, self).set_frontend(frontend)

        self.keyboard = Keyboard(self.get_layouts())
        self.keyboard.set_name('manual')
        self.keyboard.title.foreground.label = self.get_title()

        self._entry = TextEntry()
        self._entry.visible = True
        self.keyboard.add_entry(self._entry)

        self.keyboard.visible = True
        self.widget.add(self.keyboard)
        self.widget.set_focus_proxy(self.keyboard)

        self.keyboard.connect('validated', self._on_entry_validated)
        self._entry.content = self.get_initial_entry_content()

    def clean(self):
        self.keyboard.disconnect_by_func(self._on_entry_validated)
        return super(RecategorizationEntryController, self).clean()

    def get_layouts(self):
        # Return a list of keyboard layouts.
        # May be overridden by subclasses.
        osk_qwerty = 'elisa.plugins.poblesec.osk_qwerty'
        osk_123 = 'elisa.plugins.poblesec.osk_123'
        osk_symbols = 'elisa.plugins.poblesec.osk_symbols'
        return [osk_qwerty, osk_123, osk_symbols]

    def get_title(self):
        # Return the title.
        # May be overridden by subclasses.
        return _('VIDEO RECATEGORIZATION')

    def get_initial_entry_content(self):
        # Return the initial content of the entry.
        # May be overridden by subclasses.
        return ''

    def _on_entry_validated(self, keyboard):
        return self.validate(self._entry.content)

    def _close(self):
        # Go back one screen (close this controller).
        browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
        return browser.history.go_back()

    def validate(self, value):
        # Called when the entry was validated.
        # To be overridden by subclasses.
        raise NotImplementedError()


class RecategorizationSearchController(RecategorizationEntryController):

    """
    Generic search controller for the recategorization.
    """

    def initialize(self, video, first_shot):
        self.first_shot = first_shot
        return super(RecategorizationSearchController, self).initialize(video)


class SearchMovieController(RecategorizationSearchController,
                            SearchMoviesMixin):

    """
    [Refine] Search controller for recategorization of a video into a movie.
    """

    def get_title(self):
        # Overridden from mother class.
        return _('ENTER MOVIE NAME')

    def validate(self, value):
        # Overridden from mother class.
        if not value:
            return

        def got_search_results(movies):
            kwargs = {'video': self.video}
            if movies:
                path = '/poblesec/database/recategorize/movie'
                kwargs['first_shot'] = self.first_shot
                kwargs['movies'] = movies
                title = _('Movies')
            else:
                path = '/poblesec/database/recategorize/noresults/movie'
                kwargs['first_shot'] = False
                kwargs['movie_title'] = value
                title = _('No Results')

            action = OpenControllerAction(self, None)
            dfr = self._close()
            dfr.addCallback(lambda result: \
                            action.open_controller(path, title, **kwargs))
            return dfr

        dfr = self.search_movies(value)
        dfr.addCallback(got_search_results)
        return dfr


class SearchTVEpisodeController(RecategorizationSearchController,
                                SearchTVEpisodesMixin):

    """
    [Refine] Search controller for recategorization of a video into a TV
    episode.
    """

    # Note: as a temporary solution, when refining a search for a TV episode,
    # the user is presented with three consecutive screens (series title, season
    # number, episode title or number).
    # The final solution according to the design should be a single screen with
    # the OSK and three entry widgets.

    # FIXME: the design says that the user should be able to enter either a
    # number or a title for the episode. However the current API of the thetvdb
    # resource provider only allows to search by number.

    def initialize(self, video, first_shot, series=None, season=None):
        self.series = series
        self.season = season
        return super(SearchTVEpisodeController,
                     self).initialize(video, first_shot)

    def get_layouts(self):
        # Overridden from mother class.
        osk_qwerty = 'elisa.plugins.poblesec.osk_qwerty'
        osk_123 = 'elisa.plugins.poblesec.osk_123'
        osk_symbols = 'elisa.plugins.poblesec.osk_symbols'
        if self.series is None:
            return [osk_qwerty, osk_123, osk_symbols]
        elif self.season is None:
            return [osk_123]
        else:
            return [osk_123]

    def get_title(self):
        # Overridden from mother class.
        if self.series is None:
            return _('ENTER SERIES TITLE')
        elif self.season is None:
            return _('ENTER SEASON NUMBER')
        else:
            return _('ENTER EPISODE NUMBER')

    def _close_all(self):
        # Go back three screen (close all refine search controllers).
        dfr = self._close()
        dfr.addCallback(lambda result: self._close())
        dfr.addCallback(lambda result: self._close())
        return dfr

    def validate(self, value):
        # Overridden from mother class.
        if not value:
            return

        action = OpenControllerAction(self, None)
        search_path = '/poblesec/database/recategorize/search/tvepisode'

        if self.series is None:
            title = _('Season')
            return action.open_controller(search_path, title, video=self.video,
                                          first_shot=self.first_shot,
                                          series=value)
        elif self.season is None:
            title = _('Episode')
            return action.open_controller(search_path, title, video=self.video,
                                          first_shot=self.first_shot,
                                          series=self.series, season=value)
        else:
            def got_search_results(episodes):
                kwargs = {'video': self.video}
                if episodes:
                    path = '/poblesec/database/recategorize/tvepisode'
                    kwargs['first_shot'] = self.first_shot
                    kwargs['episodes'] = episodes
                    title = _('TV Episodes')
                else:
                    path = '/poblesec/database/recategorize/noresults/tvepisode'
                    kwargs['first_shot'] = False
                    kwargs['show_name'] = self.series
                    kwargs['season_number'] = int(self.season)
                    kwargs['episode_number'] = int(value)
                    title = _('No Results')

                dfr = self._close_all()
                dfr.addCallback(lambda result: \
                                action.open_controller(path, title, **kwargs))
                return dfr

            dfr = self.search_tvepisodes(self.series,
                                         int(self.season), int(value))
            dfr.addCallback(got_search_results)
            return dfr


class ManualMovieController(RecategorizationEntryController):

    """
    Controller to allow the manual recategorization of a video into a movie.
    """

    def get_title(self):
        # Overridden from mother class.
        return _('ENTER MOVIE NAME')

    def validate(self, value):
        # Overridden from mother class.
        if not value:
            return

        action = OpenControllerAction(self, None)
        path = '/poblesec/database/recategorize/confirm/movie/manual'
        title = _('Confirmation')
        dfr = self._close()
        dfr.addCallback(lambda result: \
                        action.open_controller(path, title, video=self.video,
                                               movie_title=value))


class ManualTVEpisodeController(RecategorizationEntryController):

    """
    Controller to allow the manual recategorization of a video into a TV
    episode.
    """

    # Note: as a temporary solution, when manually entering information for a
    # TV episode, the user is presented with four consecutive screens
    # (series title, season number, episode number, episode title).
    # The final solution according to the design should be a single screen with
    # the OSK and four entry widgets.

    def initialize(self, video, series=None, season=None, episode=None):
        self.series = series
        self.season = season
        self.episode = episode
        return super(ManualTVEpisodeController, self).initialize(video)

    def get_layouts(self):
        # Overridden from mother class.
        osk_qwerty = 'elisa.plugins.poblesec.osk_qwerty'
        osk_123 = 'elisa.plugins.poblesec.osk_123'
        osk_symbols = 'elisa.plugins.poblesec.osk_symbols'
        if self.series is None:
            return [osk_qwerty, osk_123, osk_symbols]
        elif self.season is None:
            return [osk_123]
        elif self.episode is None:
            return [osk_123]
        else:
            return [osk_qwerty, osk_123, osk_symbols]

    def get_title(self):
        # Overridden from mother class.
        if self.series is None:
            return _('ENTER SERIES TITLE')
        elif self.season is None:
            return _('ENTER SEASON NUMBER')
        elif self.episode is None:
            return _('ENTER EPISODE NUMBER')
        else:
            return _('ENTER EPISODE TITLE')

    def get_initial_entry_content(self):
        # Overridden from mother class.
        if self.season is not None and self.episode is not None:
            return _('Episode %(number)d') % {'number': int(self.episode)}
        else:
            return ''

    def _close_all(self):
        # Go back four screen (close all manual entry controllers).
        dfr = self._close()
        dfr.addCallback(lambda result: self._close())
        dfr.addCallback(lambda result: self._close())
        dfr.addCallback(lambda result: self._close())
        return dfr

    def validate(self, value):
        # Overridden from mother class.
        if not value:
            return

        action = OpenControllerAction(self, None)
        manual_path = '/poblesec/database/recategorize/manual/tvepisode'

        if self.series is None:
            title = _('Season')
            return action.open_controller(manual_path, title, video=self.video,
                                          series=value)
        elif self.season is None:
            title = _('Episode #')
            return action.open_controller(manual_path, title, video=self.video,
                                          series=self.series, season=value)
        elif self.episode is None:
            title = _('Episode')
            return action.open_controller(manual_path, title, video=self.video,
                                          series=self.series,
                                          season=self.season, episode=value)
        else:
            def open_confirmation(result):
                path = \
                    '/poblesec/database/recategorize/confirm/tvepisode/manual'
                title = _('Confirmation')
                return action.open_controller(path, title, video=self.video,
                                              show_name=self.series,
                                              season_number=int(self.season),
                                              episode_number=int(self.episode),
                                              episode_name=value)

            dfr = self._close_all()
            dfr.addCallback(open_confirmation)
            return dfr


class ConfirmRecategorizationController(PigmentController):

    """
    Controller that displays a popup at the end of the process to request the
    user's confirmation to proceed to the actual recategorization.
    """

    def initialize(self, video):
        self.video = video
        uri = MediaUri({'scheme': 'file', 'path': self.video.file_path})
        self._filename = uri.filename
        return super(ConfirmRecategorizationController, self).initialize()

    def set_frontend(self, frontend):
        super(ConfirmRecategorizationController, self).set_frontend(frontend)
        title = _('VIDEO RECATEGORIZATION')
        subtitle = _('Confirm Recategorization')
        text = self._get_message()
        buttons = []
        buttons.append((_('Cancel'), self._cancel))
        buttons.append((_('Confirm'), self._confirm))
        buttons.append((_('View Information'), self._view_info))
        buttons.append((_('Video Preview'), self._preview))
        message = Popup(title, subtitle, text, buttons)
        message.set_name('message')
        self.widget.add(message)
        message.visible = True
        self.widget.set_focus_proxy(message)

    def _get_message(self):
        # To be overridden by subclasses.
        raise NotImplementedError()

    def _cancel(self):
        # Cancel the recategorization. Go back to the start screen.
        browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
        return browser.history.goto_mark('start_recategorization')

    def _confirm(self):
        # Confirm the recategorization.
        dfr = self._delete_from_db()
        dfr.addCallback(lambda result: self._change_category())
        dfr.addCallback(self._set_new_video)
        dfr.addCallback(lambda result: self._success())
        return dfr

    def _delete_from_db(self):
        store = common.application.store

        if isinstance(self.video, models.Movie):
            return models.delete_movie(self.video, store)

        elif isinstance(self.video, models.TVEpisode):
            return models.delete_tvepisode(self.video, store)

        return defer.succeed(None)

    def _change_category(self):
        # Create an entry in the database for the video in its new category.
        # Return a deferred fired with the new DB object when done.
        # To be overridden by subclasses.
        return defer.fail(NotImplementedError())

    def _set_new_video(self, new_video):
        self.new_video = new_video

    def _success(self):
        # Notify the user of the success of the recategorization.
        # To be overridden by subclasses.
        return defer.fail(NotImplementedError())

    def _view_info(self):
        # Open an information screen for the recategorized video
        # (typically a synopsis).
        # To be overridden by subclasses.
        return defer.fail(NotImplementedError())

    def _preview(self):
        play = PlayVideoAction(self)
        # Always pass the player a "raw" Video model for preview.
        if isinstance(self.video, (models.Movie, models.TVEpisode)):
            dfr = self.video.video
            dfr.addCallback(play.execute)
            return dfr
        else:
            return play.execute(self.video)


class ConfirmToMovieController(ConfirmRecategorizationController):

    """
    Specialized confirmation controller when recategorizing a video to a movie.
    """

    _message = \
        _("Please confirm your video recategorization selection. " \
          "The video entitled '<b>%(filename)s</b>' will appear as a Movie and will be " \
          "placed in the Movie section. The Movie will now appear as:\n\n" \
          "Movie Title:\t\t%(title)s\n\n" \
          "Additional information such as artwork, synopsis, runtime and " \
          "cast etc. will also be added (where available). " \
          "To review this information click 'View Information'. " \
          "Confirm your recategorization selection by selecting 'Confirm'. " \
          "Select 'Cancel' to cancel recategorizing this video.")

    def initialize(self, video, movie):
        self.movie = movie
        return super(ConfirmToMovieController, self).initialize(video)

    def _get_message(self):
        return self._message % {'filename': cgi.escape(self._filename),
                                'title': cgi.escape(self.movie.title)}

    def _change_category(self):
        # Overridden from mother class.
        store = common.application.store
        parser = video_parser.VideoParser(store)

        def check_duplicate(movie):
            if movie:
                # There is a duplicate in the DB, TODO.
                raise NotImplementedError()

        def lookup_movie(result):
            return parser._lookup_movie(self.video.file_path, self.movie.title,
                                        themoviedb_model=self.movie)

        def commit(movie):
            dfr = store.commit()
            dfr.addCallback(lambda result: movie)
            return dfr

        dfr = store.find(models.Movie, models.Movie.title == self.movie.title)
        dfr.addCallback(lambda result_set: result_set.one())
        dfr.addCallback(check_duplicate)
        dfr.addCallback(lookup_movie)
        dfr.addCallback(commit)
        return dfr

    def _success(self):
        # Overridden from mother class.
        path = '/poblesec/database/recategorize/success/movie'
        action = OpenControllerAction(self, None)
        return action.open_controller(path, _('Success'),
                                      old_video=self.video,
                                      new_video=self.new_video)


class ConfirmToManualMovieController(ConfirmToMovieController):

    """
    Specialized confirmation controller when recategorizing a video to a movie
    with manually entered information.
    """

    def initialize(self, video, movie_title):
        movie = models.Movie()
        movie.file_path = video.file_path
        movie.title = movie_title
        return super(ConfirmToManualMovieController,
                     self).initialize(video, movie)

    def _change_category(self):
        # Overridden from mother class.
        def check_duplicate(movie):
            if movie:
                # There is a duplicate in the DB, TODO.
                raise NotImplementedError()

        store = common.application.store
        dfr = store.find(models.Movie, models.Movie.title == self.movie.title)
        dfr.addCallback(lambda result_set: result_set.one())
        dfr.addCallback(check_duplicate)
        dfr.addCallback(lambda result: store.add(self.movie))
        dfr.addCallback(lambda result: store.commit())
        dfr.addCallback(lambda result: self.movie)
        return dfr


class ConfirmToTVEpisodeController(ConfirmRecategorizationController):

    """
    Specialized confirmation controller when recategorizing a video to a TV
    episode.
    """

    _message = \
        _("Please confirm your video recategorization selection. The video " \
          "entitled '<b>%(filename)s</b>' will appear as a TV episode and placed in " \
          "the TV Shows section within the following series and season:\n\n" \
          "Series Title:\t\t%(title)s\n" \
          "Season Number:\t%(season)d\n" \
          "Episode Number:\t%(episode)d\n" \
          "Episode Title:\t\t%(episode_title)s\n\n" \
          "Additional information such as artwork, episode summary, runtime " \
          "and cast etc. will also be added (where available). " \
          "To review this information click 'View Information'. " \
          "Confirm your recategorization selection by selecting 'Confirm'. " \
          "Select 'Cancel' to cancel recategorizing this video.")

    def initialize(self, video, show, episode):
        self.show = show
        self.episode = episode
        return super(ConfirmToTVEpisodeController, self).initialize(video)

    def _get_message(self):
        return self._message % \
            {'filename': cgi.escape(self._filename),
             'title': cgi.escape(self.show.name),
             'season': self.episode.season_number,
             'episode': self.episode.episode_number,
             'episode_title': cgi.escape(self.episode.name)}

    def _change_category(self):
        # Overridden from mother class.
        store = common.application.store
        parser = video_parser.VideoParser(store)

        def get_season(show):
            if show is not None:
                # The show already exists in the DB.
                dfr = store.find(models.TVSeason,
                                 models.TVSeason.tvshow_id == show.id,
                                 models.TVSeason.number == self.episode.season_number)
                dfr.addCallback(lambda result_set: result_set.one())
                return dfr
            else:
                return None

        def get_episode(season):
            if season is not None:
                # The season already exists in the DB.
                dfr = store.find(models.TVEpisode,
                                 models.TVEpisode.season_id == season.id,
                                 models.TVEpisode.number == self.episode.episode_number)
                dfr.addCallback(lambda result_set: result_set.one())
                return dfr
            else:
                return None

        def check_duplicate(episode):
            if episode is not None:
                # There is a duplicate in the DB, TODO.
                raise NotImplementedError()

        def lookup_episode(result):
            return parser._lookup_tv_episode(self.video.file_path,
                                             self.episode.season_number,
                                             self.episode.episode_number,
                                             self.show.name)

        def commit(episode):
            dfr = store.commit()
            dfr.addCallback(lambda result: episode)
            return dfr

        dfr = store.find(models.TVShow,
                         models.TVShow.thetvdb_id == self.show.id)
        dfr.addCallback(lambda result_set: result_set.one())
        dfr.addCallback(get_season)
        dfr.addCallback(get_episode)
        dfr.addCallback(check_duplicate)
        dfr.addCallback(lookup_episode)
        dfr.addCallback(commit)
        return dfr

    def _success(self):
        # Overridden from mother class.
        path = '/poblesec/database/recategorize/success/tvepisode'
        action = OpenControllerAction(self, None)
        return action.open_controller(path, _('Success'),
                                      old_video=self.video,
                                      new_video=self.new_video)


class ConfirmToManualTVEpisodeController(ConfirmToTVEpisodeController):

    """
    Specialized confirmation controller when recategorizing a video to a TV
    episode with manually entered information.
    """

    def initialize(self, video, show_name, season_number,
                   episode_number, episode_name):
        show = models.TVShow()
        show.name = show_name
        self.season = models.TVSeason()
        self.season.number = season_number
        episode = models.TVEpisode()
        episode.file_path = video.file_path
        episode.number = episode_number
        episode.name = episode_name
        return super(ConfirmToManualTVEpisodeController,
                     self).initialize(video, show, episode)

    def _get_message(self):
        return self._message % \
            {'filename': self._filename,
             'title': self.show.name,
             'season': self.season.number,
             'episode': self.episode.number,
             'episode_title': self.episode.name}

    def _change_category(self):
        # Overridden from mother class.
        store = common.application.store

        def get_show(result):
            dfr = store.find(models.TVShow,
                         models.TVShow.name == self.show.name)
            dfr.addCallback(lambda result_set: result_set.one())
            return dfr

        def save_back_show(show):
            self.show = show
            self.season.tvshow_id = show.id

        def got_show(show):
            if show is None:
                dfr = store.add(self.show)
                dfr.addCallback(get_show)
                dfr.addCallback(save_back_show)
                return dfr

        def get_season(result):
            dfr = store.find(models.TVSeason,
                             models.TVSeason.tvshow_id == self.show.id,
                             models.TVSeason.number == self.season.number)
            dfr.addCallback(lambda result_set: result_set.one())
            return dfr

        def save_back_season(season):
            self.season = season
            self.episode.season_id = season.id

        def got_season(season):
            if season is None:
                dfr = store.add(self.season)
                dfr.addCallback(get_season)
                dfr.addCallback(save_back_season)
                return dfr

        def get_episode(result):
            dfr = store.find(models.TVEpisode,
                             models.TVEpisode.season_id == self.season.id,
                             models.TVEpisode.number == self.episode.number)
            dfr.addCallback(lambda result_set: result_set.one())
            return dfr

        def got_episode(episode):
            if episode:
                # There is a duplicate in the DB, TODO.
                raise NotImplementedError()
            else:
                return store.add(self.episode)

        dfr = store.find(models.TVShow,
                         models.TVShow.name == self.show.name)
        dfr.addCallback(lambda result_set: result_set.one())
        dfr.addCallback(got_show)
        dfr.addCallback(get_season)
        dfr.addCallback(got_season)
        dfr.addCallback(get_episode)
        dfr.addCallback(got_episode)
        dfr.addCallback(lambda result: store.commit())
        dfr.addCallback(lambda result: self.episode)
        return dfr


class RecategorizationCompleteController(PigmentController):

    """
    Controller that displays a confirmation popup at the end of the
    recategorization process to inform the user that the recategorization was
    successful.
    """

    _message = \
        _("The video previously referred to as '<b>%(name)s</b>' has now been " \
          "recategorized.")

    def initialize(self, old_video, new_video):
        self.old_video = old_video
        self.new_video = new_video
        return super(RecategorizationCompleteController, self).initialize()

    def set_frontend(self, frontend):
        super(RecategorizationCompleteController, self).set_frontend(frontend)
        title = _('VIDEO RECATEGORIZATION')
        subtitle = _('Recategorization Complete')
        text = self._get_message()
        buttons = []
        buttons.append((_('Continue Browsing'), self._continue))
        buttons.append((_('View New Information'), self._view_info))
        message = Popup(title, subtitle, text, buttons)
        message.set_name('message')
        self.widget.add(message)
        message.visible = True
        self.widget.set_focus_proxy(message)

    def _get_old_title(self):
        # Return the title by which the video was previously referred to.
        video = self.old_video
        if isinstance(video, models.Movie):
            return video.title
        elif isinstance(video, models.TVEpisode):
            return video.name
        else:
            # Generic Video model.
            return video.name

    def _get_old_section_message(self):
        video = self.old_video
        if isinstance(video, models.Movie):
            msg = _("To return to the Movie Library select " \
                    "'Continue Browsing'.")
        elif isinstance(video, models.TVEpisode):
            msg = _("To return to the TV Library select " \
                    "'Continue Browsing'.")
        else:
            # Generic Video model.
            msg = _("To return to the Unclassified Section select " \
                    "'Continue Browsing'.")
        return msg

    def _get_new_info_message(self):
        # To be overridden by subclasses.
        raise NotImplementedError()

    def _get_message(self):
        message = self._message % {'name': self._get_old_title()}
        # NOTE: This is a compilation of "The video..." and two
        # "To return..." messages  
        return _('%(message)s\n\n%(old_section)s\n%(new_section)s') % \
            {'message': message,
             'old_section': self._get_old_section_message(),
             'new_section': self._get_new_info_message()}

    def _continue(self):
        # Continue browsing (go back to the initial screen).
        browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
        return browser.history.goto_mark('start_recategorization')

    def _view_info(self):
        # Open the information about the newly categorized video.
        # To be overridden by subclasses.
        return defer.fail(NotImplementedError())

    def handle_input(self, manager, event):
        if event.value == EventValue.KEY_MENU:
            # Specific handling of the BACK event to not allow going back to the
            # confirmation popup.
            self._continue()
            return True

        return super(RecategorizationCompleteController,
                     self).handle_input(manager, event)


class RecategorizationToMovieCompleteController(RecategorizationCompleteController):

    """
    Specialized confirmation controller to inform the user that the
    video was successfully recategorized as a movie.
    """

    def _get_new_info_message(self):
        # Overridden from mother class.
        return _("To review the newly recategorized video select " \
                 "'View New Information' to go to the Movie Library " \
                 "and view the video files new information.")

    def _view_info(self):
        # Overridden from mother class.
        browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
        paths = [('/poblesec/movie_menu', _p('MOVIES'), {}),
                 ('/poblesec/database/movie/list', _p('Library'), {}),
                 ('/poblesec/database/movie/synopsis', _('Synopsis'),
                  {'movie': self.new_video, 'movies': None})]
        return browser.navigate(paths)


class RecategorizationToTVEpisodeCompleteController(RecategorizationCompleteController):

    """
    Specialized confirmation controller to inform the user that the
    video was successfully recategorized as a TV episode.
    """

    def _get_new_info_message(self):
        # Overridden from mother class.
        return _("To review the newly recategorized video select " \
                 "'View New Information' to go to the TV Library " \
                 "and view the video files new information.")

    def _view_info(self):
        # Overridden from mother class.
        def navigate(episodes):
            browser = self.frontend.retrieve_controllers('/poblesec/browser')[0]
            paths = [('/poblesec/movie_menu', _p('TV SHOWS'), {}),
                     ('/poblesec/database/tvshows/list', _p('Library'), {}),
                     ('/poblesec/database/tvseason/list',
                      _('%(name)s Season %(number)d') % {'name': self._show.name,
                                                         'number': self._season.number},
                      {'season': self._season}),
                     ('/poblesec/database/tvepisode/synopsis', _('Synopsis'),
                      {'episode': self.new_video, 'episodes': episodes})]
            return browser.navigate(paths)

        def get_show(season):
            self._season = season
            return season.tvshow

        def get_episodes(show):
            self._show = show
            dfr = self._season.episodes.order_by(models.TVEpisode.number)
            dfr.addCallback(lambda result_set: result_set.all())
            return dfr

        dfr = self.new_video.season
        dfr.addCallback(get_show)
        dfr.addCallback(get_episodes)
        dfr.addCallback(navigate)
        return dfr
