# -*- 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.
#
# Authors: Benjamin Kampmann <benjamin@fluendo.com>
#          Olivier Tilloy <olivier@fluendo.com>

from elisa.core import common
from elisa.core.media_uri import MediaUri
from elisa.core.metadata_manager import IncompleteMetadataResponse
from elisa.core.utils import locale_helper, defer
from elisa.core.utils.i18n import install_translation

from elisa.plugins.base.models import image as image_model

from elisa.plugins.poblesec.base.list import BaseListController, \
                                             GenericListViewMode
from elisa.plugins.poblesec.base.list_switcher import ListSwitcherController
from elisa.plugins.poblesec.base.preview_list import \
    MenuItemPreviewListController, DoubleLineMenuItemPreviewListController
from elisa.plugins.poblesec.base.coverflow import \
    ImageWithReflectionCoverflowController
from elisa.plugins.poblesec.base.grid import GridItemGridController

from elisa.plugins.database.models import Image, PhotoAlbum, File, \
                                          PICTURES_SECTION

from elisa.plugins.database.actions import PlayPictureAction, \
                                           PictureMoreOptionsAction, \
                                           PlayPictureAlbumAction, \
                                           ListAlbumPicturesAction, \
                                           PictureAlbumMoreOptionsAction, \
                                           PlayPictureTimeAction, \
                                           ListPictureTimeAction

from twisted.internet import task

import datetime
import os.path

from gtk import gdk


_ = install_translation('database')


def _translated_strftime(date, format):
        try:
            return date.strftime(format)
        except TypeError:
            # strftime in python <=2.5 requires a str
            return date.strftime(format.encode("utf-8", "replace"))
    

class ThumbnailMixin(object):

    def __init__(self):
        # FIXME: we need the frontend to get a reference to the gst_metadata
        # instance. This a cheap - UGLY - way to get the frontend without
        # changing a lot of client code. It is really ugly as we assume there
        # is only one frontend, which might not be the case in the future...
        frontend = common.application.interface_controller.frontends.values()[0]
        # Retrieve and store a reference to gst_metadata
        controllers = frontend.retrieve_controllers('/poblesec')
        try:
            self.gst_metadata = controllers[0].gst_metadata
        except AttributeError:
            msg = 'GstMetadata missing: thumbnails will not be generated.'
            self.warning(msg)
            self.gst_metadata = None

    def _updated_thumbnail(self, thumbnail, item):
        # apply the image rotation if there's any
        # FIXME: this is ugly, we get a thumbnail from gst_metadata and
        #        a new one is generated...
        if item.orientation:
            path, ext = os.path.splitext(thumbnail)
            # FIXME: not compliant with XDG thumbnail spec:
            rotated_thumbnail = "%s-%s%s" % (path, item.orientation, ext)

            # decode thumbnail path to unicode so that gdk-pixbuf can write it
            rotated_thumbnail = rotated_thumbnail.decode(locale_helper.system_encoding())

            if not os.path.exists(rotated_thumbnail):
                # decode thumbnail path to unicode so that gdk-pixbuf can read it
                decoded_filename = thumbnail.decode(locale_helper.system_encoding())
                pixbuf = gdk.pixbuf_new_from_file(decoded_filename)
                pixbuf_rotations = {image_model.ROTATED_0: gdk.PIXBUF_ROTATE_NONE,
                                    image_model.ROTATED_90_CCW: gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE,
                                    image_model.ROTATED_180: gdk.PIXBUF_ROTATE_UPSIDEDOWN,
                                    image_model.ROTATED_90_CW:  gdk.PIXBUF_ROTATE_CLOCKWISE}
                rotated = pixbuf.rotate_simple(pixbuf_rotations[item.orientation])
                rotated.save(rotated_thumbnail, ext[1:])
            thumbnail = rotated_thumbnail.encode(locale_helper.system_encoding())

        item.thumbnail = thumbnail
        return thumbnail

    def _request_thumbnail(self, path):
        if self.gst_metadata is None:
            msg = 'No gst_metadata.'
            return defer.fail(IncompleteMetadataResponse(msg))

        def got_metadata(metadata):
            return metadata['thumbnail']

        metadata = {'uri': MediaUri({'scheme': 'file', 'path': path}),
                    'thumbnail': None,}
        dfr = self.gst_metadata.get_metadata(metadata)
        dfr.addCallback(got_metadata)
        return dfr


class PhotoViewMode(GenericListViewMode, ThumbnailMixin):

    def get_label(self, item):
        basename = os.path.basename(item.file_path)
        return defer.succeed(os.path.splitext(basename)[0])

    def get_default_image(self, item):
        return 'elisa.plugins.poblesec.glyphs.small.pictures'

    def get_image(self, item, theme):
        if hasattr(item, 'thumbnail') and item.thumbnail != None:
            return defer.succeed(item.thumbnail)

        dfr = self._request_thumbnail(item.file_path)
        dfr.addCallback(self._updated_thumbnail, item)
        return dfr

    def get_preview_image(self, item, theme):
        if hasattr(item, 'thumbnail'):
            return item.thumbnail
        return None

    def get_contextual_background(self, item):
        return defer.succeed(item.file_path)

class GenericPhotosController(BaseListController):

    empty_label = _('There are no photos in this section')

    def initialize(self, album=None):
        self.album = album
        return super(GenericPhotosController, self).initialize()

    def create_actions(self):
        default = PlayPictureAction(self)
#        more = PictureMoreOptionsAction(self)
        return default, []


class PhotosController(GenericPhotosController):

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

        def sort_images(result_set):
            result_set.order_by(Image.shot_time, Image.file_path)
            return result_set.all()

        if self.album is not None:
            dfr = store.find(Image, Image.album_name == self.album.name,
                             Image.file_path == File.path,
                             File.hidden == False)
        else:
            dfr = store.find(Image, Image.file_path == File.path,
                             File.hidden == False,
                             Image.section == PICTURES_SECTION)

        dfr.addCallback(sort_images)
        return dfr


class PhotosMonthController(GenericPhotosController):

    def initialize(self, month=None, year=None):
        self.date = datetime.datetime(year, month, 1)
        return super(PhotosMonthController, self).initialize()

    def populate_model(self):
        store = common.application.store
        try:
            last = datetime.datetime(self.date.year, self.date.month + 1, 1)
        except ValueError:
            # We reached the end of the year, skip to January of the following
            # year.
            last = datetime.datetime(self.date.year + 1, 1, 1)

        def order(result_set):
            result_set.order_by(Image.shot_time, Image.file_path)
            return result_set.all()

        dfr = store.find(Image, Image.section == PICTURES_SECTION,
                         Image.shot_time >= self.date,
                         Image.shot_time < last,
                         Image.file_path == File.path, File.hidden == False)
        dfr.addCallback(order)
        return dfr


class PhotoAlbumViewMode(GenericListViewMode):

    def get_label(self, item):
        return defer.succeed(item.name)

    def get_default_image(self, item):
        return 'elisa.plugins.poblesec.glyphs.small.pictures'

    def get_image(self, item, theme):
        return None

    def get_preview_image(self, item, theme):
        return None

    def get_contextual_background(self, item):
        def a_photo_chosen(photo):
            return photo.file_path

        # return the file path of one photo of the album
        dfr = item.photos.any()
        dfr.addCallback(a_photo_chosen)
        return dfr

class GenericPhotoAlbumsController(BaseListController):

    empty_label = _('There are no albums in this section')

    def create_actions(self):
        default = PlayPictureAlbumAction(self)
        list_pictures = ListAlbumPicturesAction(self)
        #more = PictureAlbumMoreOptionsAction(self)
        return default, [list_pictures,]


class PhotoTimesViewMode(GenericListViewMode):

    def get_label(self, item):
        return defer.succeed(_translated_strftime(item, _('%B, %Y')))

    def get_default_image(self, item):
        return 'elisa.plugins.poblesec.glyphs.small.pictures'

    def get_image(self, item, theme):
        return None

    def get_preview_image(self, item, theme):
        return None


class PhotosTimesController(BaseListController):

    query = "SELECT DISTINCT STRFTIME('%m %Y', shot_time)" \
            " from images, files where shot_time not NULL" \
            + (" and images.section = %d" % PICTURES_SECTION) + \
            " and files.path = images.file_path and files.hidden = 0" \
            " order by shot_time;"

    empty_label = _('There are no photos in this section')

    def populate_model(self):
        store = common.application.store
        times = []

        def iterate_rows(rows, times):
            for row in rows:
                month, year = row[0].split()
                date = datetime.datetime(int(year), int(month), 1)
                times.append(date)
                yield None

        dfr = store.execute(self.query)
        dfr.addCallback(lambda result_set: result_set.get_all())
        dfr.addCallback(lambda rows: task.coiterate(iterate_rows(rows, times)))
        dfr.addCallback(lambda result: times)
        return dfr

    def create_actions(self):
        default = PlayPictureTimeAction(self)
        list_pictures = ListPictureTimeAction(self)
        return default, [list_pictures]


class PhotoAlbumsController(GenericPhotoAlbumsController):

    def populate_model(self):
        def sort_albums(result_set):
            result_set.order_by(PhotoAlbum.name)
            return result_set.all()

        store = common.application.store
        dfr = store.find(PhotoAlbum)
        dfr.addCallback(sort_albums)
        return dfr


# Photo
class PhotoVerticalWithPreview(PhotosController, MenuItemPreviewListController):
    view_mode = PhotoViewMode
    fastscroller_enabled = True

    def get_shortcut_for_item(self, item):
        # NOTE: This is a strftime format
        return _translated_strftime(item.shot_time, _('%b\'%y'))

class PhotoCoverflow(PhotosController, ImageWithReflectionCoverflowController):
    view_mode = PhotoViewMode

class PhotoGrid(PhotosController, GridItemGridController):
    view_mode = PhotoViewMode
    fastscroller_enabled = True

    def get_shortcut_for_item(self, item):
        shot_time = item.shot_time
        if shot_time is None:
            # This should never happen, but the database may, for some reason,
            # be corrupted (see http://bugs.launchpad.net/elisa/+bug/388910).
            return None
        else:
            # NOTE: This is a strftime format
            return _translated_strftime(shot_time, _('%B \'%y'))

class PhotoListSwitcherController(ListSwitcherController):
    modes = [PhotoVerticalWithPreview,
             PhotoCoverflow,
             PhotoGrid]
    default_mode = PhotoGrid


# Photo by month
class PhotoMonthVerticalWithPreview(PhotosMonthController, MenuItemPreviewListController):
    view_mode = PhotoViewMode

class PhotoMonthCoverflow(PhotosMonthController, ImageWithReflectionCoverflowController):
    view_mode = PhotoViewMode

class PhotoMonthGrid(PhotosMonthController, GridItemGridController):
    view_mode = PhotoViewMode

class PhotoMonthListSwitcherController(ListSwitcherController):
    modes = [PhotoMonthVerticalWithPreview,
             PhotoMonthCoverflow,
             PhotoMonthGrid]
    default_mode = PhotoMonthVerticalWithPreview


# Photo Times Overview
class PhotoTimesVerticalWithPreview(PhotosTimesController, MenuItemPreviewListController):
    view_mode = PhotoTimesViewMode
    item_widget_kwargs = {'with_artwork_box': False}

class PhotoTimesCoverflow(PhotosTimesController, ImageWithReflectionCoverflowController):
    view_mode = PhotoTimesViewMode

class PhotoTimesGrid(PhotosTimesController, GridItemGridController):
    view_mode = PhotoTimesViewMode

class PhotoTimesListSwitcherController(ListSwitcherController):
    modes = [PhotoTimesVerticalWithPreview,
             PhotoTimesCoverflow,
             PhotoTimesGrid]
    default_mode = PhotoTimesVerticalWithPreview


# Albums
class AlbumsVerticalWithPreview(PhotoAlbumsController, MenuItemPreviewListController):
    view_mode = PhotoAlbumViewMode
    item_widget_kwargs = {'with_artwork_box': False}

class AlbumsCoverflow(PhotoAlbumsController, ImageWithReflectionCoverflowController):
    view_mode = PhotoAlbumViewMode

class AlbumsGrid(PhotoAlbumsController, GridItemGridController):
    view_mode = PhotoAlbumViewMode

class AlbumsListSwitcherController(ListSwitcherController):
    modes = [AlbumsVerticalWithPreview,
             AlbumsCoverflow,
             AlbumsGrid]
    default_mode = AlbumsVerticalWithPreview
