# -*- 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 Moovida 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.
#
# Author: Philippe Normand <philippe@fluendo.com>

from elisa.core import log
from elisa.core import common
from elisa.core.utils.i18n import get_current_locale
from elisa.core.utils.cancellable_defer import cancellable_coiterate
from elisa.core.components.resource_provider import ResourceNotFound

from elisa.plugins.database.models import File, Video, Movie, TVShow, TVSeason, \
     TVEpisode, TVSeasonPoster

import elisa.plugins.themoviedb.utils as themoviedb_utils
import elisa.plugins.thetvdb.utils as thetvdb_utils
from elisa.plugins.thetvdb.models import TvEpisodeModel

from twisted.internet import defer

import datetime, os, re


class VideoParser(log.Loggable):
    _tvshow_regexes = [re.compile(regex) for regex in
                       [# ex: lost.[s3]_[e5].hdtv-lol.avi
                        r"(.*)\[[Ss]([0-9]+)\]_\[[Ee]([0-9]+)\]?([^\\/]*)$",
                        # ex: Dexter - 01x06 (HDTV-LOL) Return to Sender.avi
                        r"(.*)[\\/\._ \[-]([0-9]+)x([0-9]+)([^\\/]*)$",
                        # ex: Day.Break.S01E03.HDTV.XviD-XOR.avi
                        r"(.*)[Ss]([0-9]+)[\.-]?[Ee]([0-9]+)([^\\/]*)$",
                        # ex: lost.305.hdtv-lol.avi
                        r"(.*)[\._ -]([0-9]+)([0-9][0-9])([\._ -][^\\/]*)$",
                        ]
                       ]

    dumbthings = "ac3|custom|dc|divx|dsr|dsrip|dutch|dvd|dvdrip|"\
                 "dvdscr|dvdscreener|fragment|fs|hdtv|internal|limited|"\
                 "multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|"\
                 "retail|r3|r5|svcd|swedish|german|read.nfo|nfofix|"\
                 "unrated|ws|telesync|telecine|bdrip|720p|1080p|hddvd|"\
                 "bluray|x264|xvid|xxx".split("|")

    def __init__(self, store=None):
        super(VideoParser, self).__init__()
        self.store = store

    def try_tvshow_match(self, file_path):
        for regex in self._tvshow_regexes:
            match = regex.search(file_path)
            if match:
                return match
        return None

    def clean_video_name(self, video_name):

        # extract filename, without its extension
        video_name = video_name.lower()
        video_name = os.path.basename(video_name)
        video_name, ext = os.path.splitext(video_name)
        self.info("original video name: %r" % video_name)
        old_name = video_name

        # remove anything between [] and ()
        for regex in ("\[.*\]", "\(.*\)"):
            video_name = re.sub(regex, '', video_name)

        # drop dumb things
        for dumb in self.dumbthings:
            video_name = video_name.replace(dumb, '')

        # replace seperators with spaces
        for sep in '.-_':
            video_name = video_name.replace(sep, ' ')

        video_name = video_name.strip()
        if not video_name:
            self.debug("video name was too dumb, nothing left. Reusing original one")
            video_name = old_name

        while video_name.find('  ') != -1:
            video_name = video_name.replace('  ', ' ')

        self.info("cleanup name: %r" % video_name)
        return video_name

    def _get_one_result(self, result_set, klass):
        """
        Returns through a deferred the first result from the result_set, or
        None if empty. Handle the case where result_set is already of type
        @klass
        """

        def on_count(count):
            if count:
                return result_set.one()
            else:
                return None

        if isinstance(result_set, klass):
            return defer.succeed(result_set)
        else:
            dfr = result_set.count()
            dfr.addCallback(on_count)
            return dfr

    def _create_season_poster(self, uri, show_id, season_number, lang_id):
        poster = TVSeasonPoster()
        poster.uri = uri
        poster.lang_id = lang_id
        poster.show_id = show_id
        poster.season_number = season_number
        dfr = self.store.add(poster)
        return dfr

    def _get_season_poster(self, uri, show_id, season_number, lang_id):
        """
        Get a TVSeasonPoster from the db if there is one that matches (@uri,
        @show_id, @season_number, @lang_id), create a new one else.
        """
        dfr = self.store.find(TVSeasonPoster, (TVSeasonPoster.uri == uri) & \
                          (TVSeasonPoster.show_id == show_id) & \
                          (TVSeasonPoster.season_number == season_number) & \
                          (TVSeasonPoster.lang_id == lang_id))
        dfr.addCallback(self._get_one_result, TVSeasonPoster)

        def on_result(result):
            if result is None:
                return self._create_season_poster(uri, show_id, season_number,
                                                  lang_id)
            
            return result
        
        dfr.addCallback(on_result)
        return dfr
                

    def _set_season_posters(self, tv_show, season_banners):
        """
        Create the necessary TVSeasonPoster instances for tv_show, if needed,
        with the information contained in season_banners

        Returns a deferred that will return @tv_show
        """
        # season_banners is of type: season_number -> (lang -> uri list)

        def coiterate():
            for season_number, lang_banners in  season_banners.iteritems():
                for lang, uris in lang_banners.iteritems():
                    for uri in uris:
                        # _get_season_poster() will create the TVSeasonPoster if
                        # needed
                        uri = unicode(uri)
                        dfr = self._get_season_poster(uri, tv_show.id,
                                                      season_number, lang)
                        yield dfr

        dfr = cancellable_coiterate(coiterate)
        dfr.addCallback(lambda result: tv_show)

        return dfr

    def _lookup_movie(self, file_path, movie_name, themoviedb_model=None):
        resource_manager = common.application.resource_manager

        def got_movie(result, movie_model, attrs):
            if not result:
                model, dfr = resource_manager.get(movie_model.api_url,
                                                  movie_model)
                dfr.addCallback(got_extended_infos, attrs)
                return dfr

        def got_extended_infos(movie_model, attrs):
            obj = Movie()
            obj.file_path = file_path

            attrs.update({'runtime': movie_model.runtime,
                          'user_rating': movie_model.rating,
                          'budget': movie_model.budget,
                          'revenue': movie_model.revenue,
                          'imdb_id': movie_model.imdbid})

            for key, value in attrs.iteritems():
                setattr(obj, key, value)
            dfr = self.store.add(obj)
            dfr.addCallback(lambda r: obj)
            return dfr

        def got_error(failure):
            err=failure.trap(ResourceNotFound)
            if err == ResourceNotFound:
                self.debug("Could not find a match (%s)" % str(failure.value[:]))

        def got_movies(movies_model):
            if movies_model is None:
                return
            movies = movies_model.movies
            if movies:
                movie_model = movies[0]
                if movie_model.score > 0.75:
                    return add_movie_in_db(movie_model)

        def add_movie_in_db(movie_model):
            self.debug("Found a good movie candidate: %r",
                       movie_model.title)

            lang_code = unicode(get_current_locale().split('_')[0])
            lang_code = lang_code.lower()
            attrs = {'release_date': movie_model.release_date,
                     'title': movie_model.title,
                     'short_overview': movie_model.short_overview,
                     'metadata_lang_code': lang_code}

            if 'original' in movie_model.posters:
                attrs['cover_uri'] = unicode(movie_model.posters['original'][0])
            if 'original' in movie_model.backdrops:
                attrs['backdrop_uri'] = unicode(movie_model.backdrops['original'][0])
            dfr = self.store.get(Movie, file_path)
            dfr.addCallback(got_movie, movie_model, attrs)
            return dfr

        def do_lookup():
            if themoviedb_model is None:
                # do a lookup on themoviedb.org
                lookup_url = themoviedb_utils.get_movie_lookup_url(movie_name)
                model, dfr = resource_manager.get(lookup_url)
                dfr.addCallback(got_movies)
                dfr.addErrback(got_error)
            else:
                # we already have a good candidate, let's add it in db
                dfr = add_movie_in_db(themoviedb_model)
            return dfr

        def got_movie_from_db(movie):
            if movie:
                return movie
            else:
                return do_lookup()

        def got_results(results):
            dfr = results.one()
            dfr.addCallback(got_movie_from_db)
            return dfr

        dfr = self.store.find(Movie, Movie.title == movie_name)
        dfr.addCallback(got_results)
        return dfr

    def _lookup_tv_episode(self, file_path, season_nb, episode_nb, show_name):
        season_nb = int(season_nb)
        episode_nb = int(episode_nb)

        def got_extended_show_infos(show_model, db_show):
            db_show.poster_uri = unicode(show_model.poster_url)
            db_show.fanart_uri = unicode(show_model.fanart_url)

            banners_url = show_model.season_banners_url
            m, dfr = common.application.resource_manager.get(banners_url,
                                                             show_model)
            dfr.addCallback(got_seasons_banners, db_show)
            return dfr

        def got_seasons_banners(show_model, db_show):
            dfr = self.store.add(db_show)
            dfr.addCallback(self._set_season_posters,
                            show_model.season_banners)
            dfr.addCallback(got_show)
            return dfr

        def got_episode_metadata(episode_model, episode):
            episode.name = episode_model.name
            if episode_model.guest_stars is not None:
                episode.guest_stars = u', '.join(episode_model.guest_stars)
            episode.overview = episode_model.overview
            episode.poster_uri = unicode(episode_model.poster_url)
            dfr = self.store.add(episode)
            dfr.addCallback(lambda result: episode)
            return dfr

        def got_error(failure,series_model, show_name=None):
            err=failure.trap(ResourceNotFound)
            if err == ResourceNotFound:
                self.debug("Could not find a match (%s)" % str(failure.value[:]))

        def got_show_metadata(tvdb_model):
            if tvdb_model and tvdb_model.series:

                obj = TVShow()
                obj.filesystem_name = show_name

                # store metadata_lang_code
                lang_code = unicode(get_current_locale().split('_')[0])
                lang_code = lang_code.lower()
                obj.metadata_lang_code = lang_code

                # take first show of the search results
                first_series = tvdb_model.series[0]
                obj.name = first_series.name
                obj.thetvdb_id = first_series.id

                # this is the place where more data should be stored in the
                # show DB object, if needed.

                extended_infos_url = first_series.extended_infos_url(lang_code)
                m, dfr = common.application.resource_manager.get(extended_infos_url,
                                                                 first_series)
                dfr.addCallback(got_extended_show_infos, obj)
                dfr.addErrback(got_error, first_series)
                return dfr

        def lookup_episode_metadata(episode, season_number, show):
            if show.thetvdb_id:
                episode_url = thetvdb_utils.get_episode_url(show.thetvdb_id,
                                                            season_number,
                                                            episode.number,
                                                            show.metadata_lang_code)
                episode_model = TvEpisodeModel()
                m, dfr = common.application.resource_manager.get(episode_url,
                                                                 episode_model)
                dfr.addCallback(got_episode_metadata, episode)
                dfr.addErrback(got_error, episode, show_name)
            else:
                dfr = self.store.add(episode)
                dfr.addCallback(lambda result: episode)
            return dfr

        def got_show_count(count, results):
            if not count:
                lookup_url = thetvdb_utils.get_series_lookup_url(show_name)
                model, dfr = common.application.resource_manager.get(lookup_url)
                dfr.addCallback(got_show_metadata)
                dfr.addErrback(got_error, lookup_url, show_name)
            else:
                dfr = results.one()
                dfr.addCallback(got_show)
            return dfr

        def got_season_count(count, results, show):
            if not count:
                obj = TVSeason()
                obj.number = season_nb
                obj.tvshow_id = show.id
                dfr = self.store.add(obj)
                dfr.addCallback(got_season, show)
            else:
                dfr = results.one()
                dfr.addCallback(got_season, show)
            return dfr

        def got_episode_count(count, results, season, show):
            if not count:
                obj = TVEpisode()

                obj.file_path = file_path
                obj.number = episode_nb
                obj.season_id = season.id
                dfr = self.store.add(obj)
                dfr.addCallback(lookup_episode_metadata, season.number, show)
            else:
                dfr = results.one()
            return dfr

        def got_episode(results, season, show):
            dfr = results.count()
            dfr.addCallback(got_episode_count, results, season, show)
            return dfr

        def got_season(results, show):
            if isinstance(results, TVSeason):
                season = results
                dfr = self.store.find(TVEpisode,
                                      (TVEpisode.number == episode_nb) & \
                                      (TVEpisode.season_id == season.id))
                dfr.addCallback(got_episode, season, show)
            else:
                dfr = results.count()
                dfr.addCallback(got_season_count, results, show)
            return dfr

        def got_show(results):
            if isinstance(results, TVShow):
                show = results
                dfr = self.store.find(TVSeason,
                                      (TVSeason.number == season_nb) & \
                                      (TVSeason.tvshow_id == show.id))
                dfr.addCallback(got_season, show)
            else:
                dfr = results.count()
                dfr.addCallback(got_show_count, results)
            return dfr

        show_match = u'%%%s%%' % show_name.lower()
        dfr = self.store.find(TVShow,
                              TVShow.filesystem_name.like(show_match))
        dfr.addCallback(got_show)
        return dfr

    def parse(self, video):
        tvshow_match = self.try_tvshow_match(video.file_path)
        if tvshow_match:
            show_name, season_nb, episode_nb, remain = tvshow_match.groups()
            cleaned_video_name = self.clean_video_name(show_name)
            self.info("found TV show episode %r season %s episode %s",
                      cleaned_video_name, season_nb, episode_nb)
            if cleaned_video_name:
                dfr = self._lookup_tv_episode(video.file_path, season_nb,
                                              episode_nb, cleaned_video_name)
                return dfr
        else:
            # alright, consider this video as a movie
            self.info("Possible Movie at %r", video.file_path)
            cleaned_video_name = self.clean_video_name(video.file_path)
            if cleaned_video_name:
                dfr = self._lookup_movie(video.file_path, cleaned_video_name)
                return dfr
