# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2007-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.components.resource_provider import ResourceProvider, \
         ResourceNotFound
from elisa.core import media_uri
from elisa.extern.coherence import et

from elisa.plugins.http_client.http_client import ElisaAdvancedHttpClient
from twisted.web2 import responsecode
from twisted.web2.stream import BufferedStream

from twisted.internet import defer, task

from elisa.plugins.base.models.media import RawDataModel
from elisa.plugins.themoviedb.models import MoviesListModel, MovieModel
from elisa.plugins.themoviedb.constants import *

import re
import datetime

class TheMovieDBResourceProvider(ResourceProvider):
    """
    Provide access to resources served by TheMovieDB.org over HTTP.
    """

    log_category="themoviedb_resource_provider"
    # Queries to the API
    api_uri = 'http://%s/2.0/(.*)' % API_SERVER
    api_re = re.compile(api_uri)
    # Queries to the image server
    img_uri = 'http://%s/.*' % IMG_SERVER
    img_re = re.compile(img_uri)

    supported_uri = api_uri + '|' + img_uri

    def initialize(self):
        self._api_client = ElisaAdvancedHttpClient(host=API_SERVER)
        self._img_client = ElisaAdvancedHttpClient(host=IMG_SERVER)
        return super(TheMovieDBResourceProvider, self).initialize()

    def clean(self):
        """
        Close all the open HTTP connections.
        """
        close_dfrs = [self._api_client.close(), self._img_client.close()]
        dfr = defer.DeferredList(close_dfrs, consumeErrors=True)

        def parent_clean(result):
            return super(TheMovieDBResourceProvider, self).clean()

        dfr.addCallback(parent_clean)
        return dfr

    def get(self, uri, context_model=None):
        """
        Link to API docs: http://api.themoviedb.org/2.0/docs

        3 API calls currently supported:

        - Movie.search: will fill a
          L{elisa.plugins.themoviedb.models.MoviesListModel}
          containing L{elisa.plugins.themoviedb.models.MovieModel}
          instance, partially filled in.

        - Movie.getInfo: will complete a
          L{elisa.plugins.themoviedb.models.MovieModel} instance
          passed as context_model (required for this call). The uri to
          request can be retrieved with the api_url property of the
          MovieModel (or read the API docs).

        - Movie.imdbLookup: will fill or complete a
          L{elisa.plugins.themoviedb.models.MovieModel} instance
          passed as context_model. The uri to
          request can be retrieved with the imdb_url property of the
          MovieModel (or read the API docs).

        Movie posters hosted on themoviedb.org can also be retrieved
        and stored to L{elisa.plugins.base.models.media.RawDataModel}
        instances. Images are in jpeg format.

        @param uri:           URI pointing to the resource
        @type uri:            L{elisa.core.media_uri.MediaUri}
        @param context_model: optional movie model instance to fill (if uri
                              is a getInfo call)
        @type context_model:  L{elisa.plugins.themoviedb.models.MovieModel} or
                              C{None}

        @return:              a new model and a deferred fired when the model
                              is filled with the requested resource's data
        @rtype:               tuple of L{elisa.core.components.model.Model}
                              L{elisa.core.utils.defer.Deferred}
        """
        url = str(uri)
        self.debug("GET %s", url)
        http_client = None
        result_model = None
        dfr = None

        # Select the correct HTTP client to target
        match = self.api_re.match(url)
        if match is not None:
            # API call
            http_client = self._api_client
            method = uri.path[1:] # strip /
            if method == '2.0/Movie.search':
                result_model = MoviesListModel()
            elif method == '2.0/Movie.getInfo':
                result_model = context_model
            elif method == '2.0/Movie.imdbLookup':
                result_model = context_model or MovieModel()
            else:
                http_client = None
                dfr = defer.fail(NotImplementedError(method))
        else:
            match = self.img_re.match(url)
            if match is not None:
                # image retrieval
                http_client = self._img_client
                result_model = RawDataModel()

        if http_client:
            dfr = http_client.request(url)
            dfr.addCallback(self._request_done, result_model, url)
        elif not dfr:
            dfr = defer.fail(Exception('Unhandled uri: %r' % uri))

        return result_model, dfr

    def _fill_movie_model(self, movie, url, model=None):

        def node_value(node, node_name):
            text = ''
            text_node = node.find('.//%s' % node_name)
            if text_node is not None:
                text = text_node.text
                if text is None:
                    text = ''
            return unicode(text)

        # if nothing was found, the API returns a text message inside
        # one movie tag
        text = movie.text.strip()
        if text:
            return defer.fail(ResourceNotFound(url,text))

        if not model:
            model = MovieModel()

        model.score = float(node_value(movie, 'score'))
        try:
            model.popularity = int(node_value(movie, 'popularity'))
        except ValueError:
            pass
        model.title = node_value(movie, 'title')
        model.alternative_title = node_value(movie,
                                             'alternative_title')
        model.id = int(node_value(movie, 'id'))
        model.imdbid = node_value(movie, 'imdb')
        model.url = node_value(movie, 'url')
        model.short_overview = node_value(movie,
                                          'short_overview')

        release_date_str = node_value(movie, 'release')
        if release_date_str:
            year, month, day = [int(i) for i in release_date_str.split('-')]
            model.release_date = datetime.date(year, month, day)

        posters = {}
        for poster in movie.findall('.//poster'):
            url = media_uri.MediaUri(poster.text)
            size = poster.get('size')
            try:
                posters[size].append(url)
            except KeyError:
                posters[size] = [url,]
        model.posters = posters

        backdrops = {}
        for backdrop in movie.findall('.//backdrop'):
            url = media_uri.MediaUri(backdrop.text)
            size = backdrop.get('size')
            try:
                backdrops[size].append(url)
            except KeyError:
                backdrops[size] = [url,]
        model.backdrops = backdrops

        # from now on: try the optional values
        runtime = node_value(movie, 'runtime')
        if runtime:
            model.runtime = int(runtime)
            model.budget = int(node_value(movie, 'budget'))
            model.revenue = int(node_value(movie, 'revenue'))
            model.rating = float(node_value(movie, 'rating'))

            homepage = node_value(movie, 'homepage')
            if homepage:
                model.homepage = media_uri.MediaUri(homepage)

            trailers = {}
            for trailer in movie.findall('.//trailer'):
                source = trailer.get('source')
                url = media_uri.MediaUri(trailer.text)
                trailers[source] = url
            model.trailers = trailers

            prod_countries = []
            countries = movie.find('.//production_countries')
            if countries:
                for country in countries.findall('.//country'):
                    name = node_value(country, 'name')
                    short_name = node_value(country, 'short_name')
                    url = media_uri.MediaUri(node_value(country, 'url'))
                    prod_countries.append((name, short_name, url))
                model.production_countries = prod_countries

            people = {}
            xml_people = movie.find('.//people')
            if xml_people:
                for person in xml_people.findall('.//person'):
                    job = person.get('job')
                    name = node_value(person, 'name')
                    role = node_value(person, 'role')
                    url = media_uri.MediaUri(node_value(person, 'url'))
                    values = (name, role, url)
                    try:
                        people[job].append(values)
                    except KeyError:
                        people[job] = [values,]
                model.people = people

            categories = {}
            xml_categories = movie.find('.//categories')
            if xml_categories:
                for category in xml_categories.findall('.//category'):
                    name = node_value(category, 'name')
                    url = media_uri.MediaUri(node_value(category, 'url'))
                    categories[name] = url
                model.categories = categories

        return defer.succeed(model)

    def _response_read(self, response, result_model, url):
        # Parse the response and populate the model accordingly
        dfr = None
        if isinstance(result_model, MoviesListModel):
            xml_tree = et.parse_xml(response)
            movies = list(xml_tree.findall(".//movie"))

            def filled(model):
                result_model.movies.append(model)

            def iterate_movies():
                for movie in movies:
                    dfr = self._fill_movie_model(movie, url)
                    dfr.addCallback(filled)
                    yield dfr

            def all_done(result):
                return result_model

            dfr = task.coiterate(iterate_movies())
            dfr.addCallback(all_done)

        elif isinstance(result_model, MovieModel):
            xml_tree = et.parse_xml(response)
            movie = xml_tree.find('.//moviematches').find('.//movie')
            dfr = self._fill_movie_model(movie, url, result_model)
        elif isinstance(result_model, RawDataModel):
            result_model.data = response
            result_model.size = len(response)
            dfr = defer.succeed(result_model)
        return dfr

    def _request_done(self, response, model, url):
        if response.code == responsecode.OK:
            # Read the response stream
            read_dfr = BufferedStream(response.stream).readExactly()
            read_dfr.addCallback(self._response_read, model, url)
            return read_dfr
        elif response.code == responsecode.NOT_FOUND:
            # 404 error code: resource not found
            return defer.fail(ResourceNotFound(url))
        else:
            # Other HTTP response code
            return defer.fail(Exception('Received an %d HTTP response code' % response.code))
