# -*- 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.


import sys
import gobject
import os.path

from twisted.trial.unittest import TestCase
from pkg_resources import resource_filename

from elisa.plugins.pigment.widgets.theme import Theme
from elisa.plugins.pigment.widgets.const import *
from elisa.core.utils.misc import pkg_resources_copy_dir
import pgm

from twisted.python.filepath import FilePath

class TestTheme(TestCase):

    def setUp(self):
        # mock the start_monitoring 
        self.monitoring_mock = Theme.start_monitoring
        Theme.start_monitoring = lambda x: x

    def tearDown(self):
        Theme.start_monitoring = self.monitoring_mock

    def test_init_bare(self):
        theme = Theme()

        self.assertEquals(len(theme.widget_styles.items()), 0)
        self.assertEquals(len(theme.stock_resources.items()), 0)

    def test_init_style(self):
        styles_conf = resource_filename('elisa.plugins.pigment.tests',
                                        'data/widgets/styles.conf')
        theme = Theme(styles_conf=styles_conf)

        self.failIf(len(theme.widget_styles.items()) == 0)
        self.failIf(len(theme.stock_resources.items()) != 0)

    def test_init_resources(self):
        module_name = 'elisa.plugins.pigment.tests.data.widgets'
        resources_conf = resource_filename(module_name, 'resources.conf')
        theme = Theme(module_name=module_name, resources_conf=resources_conf)

        self.failIf(len(theme.widget_styles.items()) != 0)
        self.failIf(len(theme.stock_resources.items()) == 0)

    def test_update(self):
        styles_conf = resource_filename('elisa.plugins.pigment.tests',
                                        'data/widgets/styles.conf')
        t1 = Theme(styles_conf=styles_conf)
        module_name = 'elisa.plugins.pigment.tests.data.widgets'
        resources_conf = resource_filename(module_name, 'resources.conf')
        t2 = Theme(module_name=module_name, resources_conf=resources_conf)

        t1.update(t2)

        self.failIf(len(t1.widget_styles.items()) == 0)
        self.failIf(len(t1.stock_resources.items()) == 0)

    def test_merge(self):
        styles_conf = resource_filename('elisa.plugins.pigment.tests',
                                        'data/widgets/styles.conf')
        t1 = Theme(styles_conf=styles_conf)
        module_name = 'elisa.plugins.pigment.tests.data.widgets'
        resources_conf = resource_filename(module_name, 'resources.conf')
        t2 = Theme(module_name=module_name, resources_conf=resources_conf)

        t3 = t1.merge(t2)
        self.failIfEqual(len(t3.widget_styles.items()), 0)
        self.failIfEqual(len(t3.stock_resources.items()), 0)

        t4 = t2.merge(t1)
        self.failIfEqual(len(t4.widget_styles.items()), 0)
        self.failIfEqual(len(t4.stock_resources.items()), 0)

    def test_get_style_for_widget(self):
        theme = Theme()

        self.failUnlessEqual(len(theme.fallback_themes), 0)

        module = 'elisa.plugins.pigment.widgets.button'
        widget = '%s.Button' % module
        style = theme.get_style_for_widget(widget)
        self.failIfIdentical(style, None)
        self.failUnlessEqual(len(theme.fallback_themes), 1)
        self.failIfIdentical(theme.fallback_themes.get(module), None)

        module = 'elisa.plugins.pigment.widgets.list'
        widget = '%s.List' % module
        style = theme.get_style_for_widget(widget)
        self.failUnlessIdentical(style, None)

        # search for a non-existant Widget
        widget = 'elisa.plugins.pigment.widgets.widget.NoWidget'
        style = theme.get_style_for_widget(widget)
        self.failUnlessIdentical(style, None)

        # search for a non-existant module
        widget = 'wwwwwwwww.Widget'
        style = theme.get_style_for_widget(widget)
        self.failUnlessIdentical(style, None)

    def test_get_resource(self):
        theme = Theme.load_from_module('elisa.plugins.pigment.tests.data.widgets')

        resource = theme.get_resource('elisa.plugins.my_ui.resource1')
        path = resource_filename('elisa.plugins.pigment.tests',
                                 'data/widgets/resource1.png')

        self.failUnlessEqual(os.path.abspath(resource), os.path.abspath(path))

    def test_get_default(self):
        self.failUnless(Theme.get_default())

    def test_set_default(self):
        theme = Theme()

        self.failIf(theme == Theme.get_default())

        Theme.set_default(theme)

        self.failUnlessEqual(theme, Theme.get_default())

    def test_fallback_resources(self):
        theme = Theme()
        Theme.set_default(theme)

        # check the resource is not in default theme
        self.failIf(theme.get_resource('elisa.plugins.my_ui.resource1'))

        theme.fallback_theme = Theme.load_from_module('elisa.plugins.pigment.tests.data.widgets')

        # the theme will look in its fallback theme
        self.failUnless(theme.get_resource('elisa.plugins.my_ui.resource1'))

    def test_fallback_style(self):
        theme = Theme()
        Theme.set_default(theme)

        # check the style is not in default theme
        self.failIf(theme.get_style_for_widget('i.dont.exist'))

        # setup a fallback theme
        theme.fallback_theme = Theme()
        theme.fallback_theme.widget_styles = {'i.dont.exist': {None: {'padding': 0}}}

        # the theme will look in its fallback theme
        self.failUnless(theme.get_style_for_widget('i.dont.exist'))

    def test_guess_style_value_type(self):
        theme = Theme()
        tests = (('10', None, 10),
                 ('2.25', None, 2.25),
                 ('2, 3', None, (2, 3)),
                 ('1, 2, 0', None, (1, 2, 0)),
                 ('0, 0, 0, 0', None, (0, 0, 0, 0)),
                 ('2.0, 3.5', None, (2.0, 3.5)),
                 ('1.2, 2, 0.001', None, (1.2, 2, 0.001)),
                 ('0, 2.55, 0.0, 8.1', None, (0, 2.55, 0.0, 8.1)),
                 ('0, 0, 0, 0, 0', None, '0, 0, 0, 0, 0'),
                 ('center', 'alignment', 'center'),
                 ('zoomed', 'layout', 'zoomed'),
                 ('north', 'gravity', 'north'),
                 ('wrong', 'invalid', 'wrong'),
                 ('true', None, True),
                 ('tRue', None, True),
                 ('false', None, False),
                 ('False', None, False),
                 ('"something"', None, 'something'))
        for value, key, expected in tests:
            self.failUnlessEqual(theme._guess_style_value_type(value, key),
                                 expected)

    def test_guess_style_value_type_url(self):
        """
        Test url resolution in theme
        """
        # Using testing module full path as theme's module
        theme = Theme(module_name=self.__module__)
        theme_path = resource_filename(self.__module__, '')

        file_path = 'file.png'
        expected = os.path.join(theme_path, file_path)

        result = theme._guess_style_value_type(value='url(%s)' % file_path)
        self.failUnlessEqual(result, expected)

        file_path = os.path.join('abit', 'deeper', 'file.png')
        expected = os.path.join(theme_path, file_path)

        result = theme._guess_style_value_type(value='url(%s)' % file_path)
        self.failUnlessEqual(result, expected)

def get_theme_cache_test_directory():
    return os.path.join(os.getcwd(), 'test_theme_cache')

def bump_mtime(filename):
    st = os.stat(filename)
    os.utime(filename, (st.st_atime, st.st_mtime+1))

def clean_memory_cache():
    Theme._cache = {}

class ThemeCacheMock(Theme):
    def _init(self, *args, **kw):
        self.load_css_called = 0
        Theme._init(self, *args, **kw)

    def _get_cache_directory(self):
        cache_dir = os.path.join(get_theme_cache_test_directory(), 'css_cache')
        try:
            FilePath(cache_dir).makedirs()
        except OSError, e:
            if e.errno != 17:
                raise

        return cache_dir

    def _load_css(self, filename):
        self.load_css_called += 1
        return Theme._load_css(self, filename)

class TestThemeCache(TestCase):
    def setUp(self):
        # mock the start_monitoring 
        self.monitoring_mock = Theme.start_monitoring
        Theme.start_monitoring = lambda x: x

        self.test_dir = get_theme_cache_test_directory()
        pkg_resources_copy_dir('elisa.plugins.pigment.tests',
                'data', os.path.join(self.test_dir, 'data'))
        self.styles_conf = os.path.join(self.test_dir,
                'data', 'widgets', 'styles.conf')
        self.resources_conf = os.path.join(self.test_dir,
                'data', 'widgets', 'resources.conf')

    def tearDown(self):
        Theme.start_monitoring = self.monitoring_mock
        FilePath(self.test_dir).remove()
        clean_memory_cache()

    def test_first_run(self): 
        module_name = 'elisa.plugins.pigment.tests.data.widgets'
        theme = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)
       
        self.failUnlessEqual(theme.load_css_called, 2)

    def test_hot_cache(self):
        module_name = 'elisa.plugins.pigment.tests.data.widgets'
        theme = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)
        
        self.failUnlessEqual(theme.load_css_called, 2)
        
        theme1 = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)
        self.failUnlessEqual(theme1.load_css_called, 0)
    
    def test_hot_cache_disk(self):
        module_name = 'elisa.plugins.pigment.tests.data.widgets'
        theme = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)
        
        self.failUnlessEqual(theme.load_css_called, 2)
        
        clean_memory_cache()

        theme1 = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)
        self.failUnlessEqual(theme1.load_css_called, 0)

    def test_old_cache(self):
        module_name = 'elisa.plugins.pigment.tests.data.widgets'
        theme = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)
        
        self.failUnlessEqual(theme.load_css_called, 2)

        bump_mtime(self.styles_conf)
        bump_mtime(self.resources_conf)

        theme1 = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)

        self.failUnlessEqual(theme1.load_css_called, 2)

    def test_broken_cache(self):
        module_name = 'elisa.plugins.pigment.tests.data.widgets'
        theme = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)
        
        self.failUnlessEqual(theme.load_css_called, 2)

        styles_conf_cache = theme._get_cached_file(self.styles_conf)
        resources_conf_cache = theme._get_cached_file(self.resources_conf)

        # break cache files
        FilePath(styles_conf_cache).setContent('asd')
        FilePath(resources_conf_cache).setContent('sad')
        
        # clean Theme._cache so that the files are unpickled
        clean_memory_cache()
        
        theme1 = ThemeCacheMock(module_name=module_name, styles_conf=self.styles_conf,
                resources_conf=self.resources_conf)

        self.failUnlessEqual(theme1.load_css_called, 2)

