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

"""
Test cases for the database updater
"""

from twisted.trial.unittest import TestCase
from twisted.internet import defer

from storm.locals import create_database
from elisa.extern.storm_wrapper import store

from elisa.plugins.database.database_updater import DatabaseUpdaterClassic, DatabaseUpdaterNG

class TestDatabaseUpdaterClassic(TestCase):
    """
    The classic database updating mechanismn does only alter the tables until
    they look like they have to do now.
    """
    def setUp(self):
        self.db = create_database('sqlite:')
        self.store = store.DeferredStore(self.db)
        self.updater = DatabaseUpdaterClassic(self.store)
        return self.store.start()

    def tearDown(self):
        return self.store.stop()

    def table_check(self, result, table_name, *fields):
        
        def check(columns):
            for col in fields:
                self.assertIn(col, columns)
        
        dfr = self.store.execute("PRAGMA table_info('%s')" % table_name)
        dfr.addCallback(lambda res: res.get_all())
        dfr.addCallback(lambda table_result: [ x[1] for x in table_result])
        dfr.addCallback(check)
        return dfr
        
    def get_table_info(table_name):
        dfr = self.sqlexec("PRAGMA table_info('%s')" % table_name)
        dfr.addCallback(self.get_all)
        dfr.addCallback(self.get_columns)

    def test_old_images_table(self):
        # ignore the section update
        self.updater.update_image_section = lambda x: x
        table_str  = "CREATE TABLE images" \
                " (path VARCHAR PRIMARY KEY, source VARCHAR," \
                " modification_time INTEGER DEFAULT 0, mime_type VARCHAR," \
                " deleted INTEGER);"
        dfr = self.store.execute(table_str)
        dfr.addCallback(self.updater.do_images_update)
        dfr.addCallback(self.table_check, 'images',
            'shot_time', 'with_flash', 'orientation',
            'gps_altitude', 'gps_latitude', 'gps_longitude',
            'album_name', 'section')
        return dfr

    def test_old_files_table(self):
        table_str  = "CREATE TABLE files" \
                " (path VARCHAR PRIMARY KEY, source VARCHAR," \
                " modification_time INTEGER DEFAULT 0, mime_type VARCHAR," \
                " deleted INTEGER);"
        dfr = self.store.execute(table_str)
        dfr.addCallback(self.updater.do_files_update)
        dfr.addCallback(self.table_check, 'files',
                'playcount', 'last_played', 'hidden')
        return dfr

    def test_favorites_update(self):
        table_str = "CREATE TABLE IF NOT EXISTS favorites"\
                " (id INTEGER PRIMARY KEY, section TEXT,"\
                " added_time TEXT, title TEXT, subtitle,"\
                " foreign_id TEXT, foreign_class TEXT, uri TEXT);"

        dfr = self.store.execute(table_str)
        dfr.addCallback(self.updater.do_favorites_update)
        dfr.addCallback(self.table_check, 'favorites',
                'type',)
        return dfr 

    def test_music_albums(self):
        table_str  = "CREATE TABLE IF NOT EXISTS music_albums" \
                " (name VARCHAR PRIMARY KEY, cover_uri VARCHAR)"
        dfr = self.store.execute(table_str)
        dfr.addCallback(self.updater.do_albums_update)
        dfr.addCallback(self.table_check, 'music_albums',
                'release_date')
        return dfr

    def test_bumps_user_version(self):
        self.called = 0
        def update_call(res=None):
            self.called += 1
            return defer.succeed(res)

        def check_called(res):
            self.assertEquals(self.called, 5)

        def check_user_version(res):
            dfr = self.store.execute("Pragma user_version");
            dfr.addCallback(lambda x: x.get_all())
            dfr.addCallback(self.assertEquals, [(1,)])
            return dfr

        # we want to have each of these called
        self.updater.do_albums_update = update_call
        self.updater.do_favorites_update = update_call
        self.updater.do_files_update = update_call
        self.updater.do_images_update = update_call
        self.updater.do_videos_update = update_call

        dfr = self.updater.update_to_latest()
        dfr.addCallback(check_called)
        dfr.addCallback(check_user_version)
        return dfr


class DbUpNGDummy(DatabaseUpdaterNG):
    def __init__(self, store):
        self.store = store
        self.conditional_updates = {}
        self.called = []

    def create_schema(self):
        self.called.append('create schema')
        return defer.succeed(True)

    def update_from(self, version):
        self.called.append('update from %s' % version)
        return defer.succeed(True)

    def do_conditional_updates(self, old_result=None):
        self.called.append('conditional updates')
        return defer.succeed(True)

    # we want to preserve some originals though
    _orig_update_from = DatabaseUpdaterNG.update_from

    _orig_do_conditional_updates = DatabaseUpdaterNG.do_conditional_updates

    def _run_classic(self):
        self.called.append('classic')
        return defer.succeed(True)

    def _upgrade_to_10(self):
        self.called.append('upgrade to 10')
        return defer.succeed(True)

    def _upgrade_to_11(self):
        self.called.append('upgrade to 11')
        return defer.succeed(True)

    def _upgrade_to_12(self):
        self.called.append('upgrade to 12')
        return defer.succeed(True)
     

class TestUpdaterNG(TestCase):
    # so far there is not much to do.

    def setUp(self):
        self.db = create_database('sqlite:')
        self.store = store.DeferredStore(self.db)
        self.updater = DbUpNGDummy(self.store)
        return self.store.start()

    def tearDown(self):
        return self.store.stop()

    def test_totally_new(self):

        def check(result):
            self.assertEquals(self.updater.called, ['create schema'])

        dfr = self.updater.update_db()
        dfr.addCallback(check)
        return dfr

    def test_current_version_found(self):
        def check(result):
            self.assertEquals(self.updater.called, ['conditional updates'])

        dfr = self.store.execute("PRAGMA user_version=%s" %
                self.updater.current_version)

        dfr.addCallback(lambda x: self.updater.update_db())
        dfr.addCallback(check)
        return dfr

    def test_old_version_old_schema(self):
        def check(result):
            self.assertEquals(self.updater.called, ['classic', 'update from 1',
                    'conditional updates'])

        dfr = self.store.execute("CREATE TABLE IF NOT EXISTS music_albums"
                " (name VARCHAR PRIMARY KEY, cover_uri VARCHAR,"
                " release_date VARCHAR);")

        dfr.addCallback(lambda x: self.updater.update_db())
        dfr.addCallback(check)
        return dfr

    def test_three_versions(self):
        
        def check(result):
            self.assertEquals(self.updater.called, ["upgrade to 10",
                    "upgrade to 11", "upgrade to 12", 'conditional updates'])
            dfr = self.store.execute("PRAGMA user_version")
            dfr.addCallback(lambda x: x.get_all())
            dfr.addCallback(self.assertEquals, [(12,)])
            return dfr

        # we also want to test the upgrade_from method, so we write it back
        self.updater.update_from = self.updater._orig_update_from

        self.updater.current_version = 12 # we fake an old version
        dfr = self.store.execute("PRAGMA user_version=9")

        dfr.addCallback(lambda x: self.updater.update_db())
        dfr.addCallback(check)
        return dfr

    def test_conditional_updates_saved_proper(self):

        def return_true(store):
            return defer.succeed(True)

        def return_false(store):
            return defer.succeed(False)

        # our setup
        self.updater.conditional_updates = {'test_a' : return_true,
            'test_b' : return_true, 'test_c' : return_false,
            'test_d' : return_false, 'test_e': return_true}

        create_table = "CREATE TABLE %s" \
                " (name VARCHAR PRIMARY KEY, update_stamp INTEGER);"
        dfr = self.store.execute(create_table % self.updater.database_log_table)
        dfr.addCallback(self.updater._orig_do_conditional_updates)
        dfr.addCallback(lambda x: self.store.execute('SELECT * from' \
                ' %s ORDER BY name;' % self.updater.database_log_table))
        dfr.addCallback(lambda x: x.get_all())
        dfr.addCallback(lambda x: [y[0] for y in x])
        dfr.addCallback(self.assertEquals,  ['test_a', 'test_b', 'test_e'])
        return dfr
