# -*- 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 the media scanning
"""

from twisted.trial.unittest import TestCase

from elisa.plugins.database.media_scanner import MediaScanner
from elisa.plugins.base.models.file import FileModel, DirectoryModel

from elisa.plugins.database.scanner_models import ScanResource 

from twisted.internet import defer, reactor

from elisa.core import common

from elisa.core.media_uri import MediaUri

from collections import deque

import os, platform, shutil

class BusMock:

    def register(self, cb, msg_type):
        pass

    def unregister(self, cb):
        pass

class Application(object):
    bus = BusMock()

class DummyParser(object):
    def query_model(self, *args):
        return defer.succeed(None)
    def mark_deleted(self, *args):
        return defer.succeed(None)
    def delete_files(self, *args):
        return defer.succeed(None)
    def clean(self):
        return defer.succeed(None)

class DummyResult(object):
    result = None
    def get_all(self):
        return defer.succeed(self.result)

class DummyStore(object):
    def execute(self, *args):
        result = True
        if args[0].startswith('PRAGMA'):
            result = DummyResult()
            result.result = [(0,'playcount'),(1,'last_played'),\
                              (3,'shot_time'),(2,'release_date')]
        return defer.succeed(result)
    def commit(self, *args):
        return defer.succeed(True)

class DummyConfig(object):
    def get_section(self, name):
        return {}

class TestMediaScanner(TestCase):
    """
    Test the media scanning part
    """
    stat_uri = MediaUri('media_scanner://localhost/statistic')
    queue_uri = MediaUri('media_scanner://localhost/queue')
    # rescan after ~ 4 secs
    config = {'delay' : 0.2, 'scan_every' : 0.001}

    def setUp(self):
        self._dbus_init = MediaScanner._initialize_dbus
        self._dbus_clean = MediaScanner._clean_dbus

        MediaScanner._initialize_dbus = lambda x,y :x
        MediaScanner._clean_dbus = lambda x :x

        self.tempdir = os.path.abspath(self.mktemp())
        tree = {'root': {'usr': None,
                         'home': {'meeko': None,
                                  'chubaka': None,
                                  'ben': None,
                                 },
                         'lib': None,
                        },
                'usr': {'lib': {'doc': None,
                                'share': None,
                                'python': None,
                               },
                         'share': {'python': None,
                                   'usr': None,
                                   'test': None,
                                  },
                         'log': None,
                         'lock': None,
                        },
                'var': {'log': {'x11': None,
                                'gdm': None,
                                'test': None,
                               },
                         'empty': {},
                         'file1': None,
                         'file3': None,
                        },
               }
        self._create_structure(self.tempdir, tree)

        def set_scanner(result):
            self.scanner = result
            # overwrite the Parser and stop the old one
            parser = self.scanner.parser
            self.scanner.parser = DummyParser()
            self.scanner._scan_directories = lambda: None
            return parser.clean()

        self._patch_application()
        return MediaScanner.create(self.config).addCallback(set_scanner)

    def _patch_application(self):
        self._application = common.application
        common.application = Application()
        common.application.store = DummyStore()
        common.application.config = DummyConfig()

    def _unpatch_application(self):
        common.application = self._application

    def _create_structure(self, path, node):
        if node is None:
            # file
            open(path, 'w').close()
        elif type(node) is dict:
            # directory
            os.makedirs(path)
            for key, value in node.iteritems():
                self._create_structure(os.path.join(path, key), value)

    def tearDown(self):
        def reset_dbus(result):
            MediaScanner._initialize_dbus = self._dbus_init
            MediaScanner._clean_dbus = self._dbus_clean
            return result

        dfr  = self.scanner.clean()
        dfr.addCallback(lambda result: self._unpatch_application())
        dfr.addCallback(lambda result: shutil.rmtree(self.tempdir))
        dfr.addCallback(reset_dbus)
        return dfr

    def test_get_stats(self):
        model, dfr = self.scanner.get(MediaUri(self.stat_uri))
        self.assertEquals(model, self.scanner.scan_stat)

    def test_recursive_scan(self):
        scan_resource = ScanResource()
        scan_resource.root_uri = \
            MediaUri('file://%s/' % os.path.join(self.tempdir, 'root'))

        def check_result(result):
            stat, dfr = self.scanner.get(self.stat_uri)
            self.assertFalse(self.scanner.running)
            self.assertFalse(stat.running)

            self.assertEquals(scan_resource.files_scanned, 5)
            self.assertEquals(scan_resource.files_failed, [])
            return dfr

        return self.scanner._scan_recursive(scan_resource).addCallback(check_result)

    def test_scan_auto_restart(self):

        # we don't really want to scan. we just emulate it
        wait_dfr = defer.Deferred()
        self.scanner._scan_recursive = lambda x: wait_dfr

        # fake the _rescan method
        self._rescan_called = False
        def rescan(*args):
            self._rescan_called = True

        self.scanner._rescan = rescan

        # let's assume, we are the last one to scan
        while not self.scanner.scan_stat.queue.empty():
            self.scanner.scan_stat.queue.get()

        s = ScanResource()
        s.root_uri = MediaUri("file:///root/")
        
        self.scanner.scan_stat.queue.put(s)
            
        # start the 'scanning' process
        self.scanner._scan()

        scan_dfr = self.scanner._current_scan_deferred
        wait_dfr.callback(s)

        def check(result):
            self.assertTrue(self._rescan_called)

        def wait_for_second_scan(result):
            # rescan was not called yet!
            self.assertFalse(self._rescan_called)

            dfr = defer.Deferred()
            dfr.addCallback(check)

            reactor.callLater(5, dfr.callback, None)

            return dfr

        scan_dfr.addCallback(wait_for_second_scan)
        return scan_dfr

    test_scan_auto_restart.skip = "Rewrite this in readable/understandable code"
    test_scan_auto_restart.timeout = 10

    def test_reschedule(self):
        class ScanResourceDummy(object):
            def __init__(self, name):
                self.name = name
                self.root_uri = MediaUri('file:///tmp/source%s' % name)

        scan_stat = self.scanner.scan_stat
        for i in xrange(5):
            scan_stat.scanned.append(ScanResourceDummy(i))

        self.assertTrue(scan_stat.queue.empty())

        self.scanner._reschedule_scanned()

        # scanned is cleared
        self.assertEquals(scan_stat.scanned, [])

        for i in xrange(5):
            self.assertEquals(scan_stat.queue.get_nowait().name, i)
        
        self.assertTrue(scan_stat.queue.empty())
        

    def test_put_deleting(self):
        """
        Try to put some requests into the media scanner and check if it works
        """
        marked = []

        # we don't want them really start to scan, so overwrite the later
        # processes
        self.scanner._file_found = lambda model, stat: defer.succeed(model)
        self.scanner._count_files = lambda path: defer.succeed(19)
        self.scanner._scan = lambda: None

        stat, dfr = self.scanner.get(self.stat_uri)

        # overwrite the mark_deleted method
        self.scanner.parser.mark_deleted = lambda x: defer.succeed(marked.append(x))
        dfrs = []
        paths = ('root', 'usr', 'var')
        for path in paths:
            uri = MediaUri('file://%s/' % os.path.join(self.tempdir, path))
            dfrs.append(self.scanner.put(uri, self.queue_uri))

        # as soon as they are put, they should be marked as deleted
        expected = [MediaUri('file://%s/' % \
                             os.path.join(self.tempdir, path)).path \
                             for path in paths]
        self.assertEquals(marked, expected)

    test_put_deleting.timeout = 4


class TestFileCounting(TestCase):

    def setUpClass(self):
        if platform.system() == 'Windows':
            import pythoncom
            from win32com.shell import shell
            # Stuff to create shorcuts (.lnk files)
            self._sh = pythoncom.CoCreateInstance(shell.CLSID_ShellLink, None,
                        pythoncom.CLSCTX_INPROC_SERVER, shell.IID_IShellLink)
            self._persist = self._sh.QueryInterface(pythoncom.IID_IPersistFile)

    def setUp(self):
        self.tempdir = os.path.abspath(self.mktemp())
        # We test only one given method, no need to initialize()
        self.scanner = MediaScanner()

    def tearDown(self):
        shutil.rmtree(self.tempdir)

    def _create_link(self, path, target):
        system = platform.system()
        if system == 'Linux':
            os.symlink(target, path)
        elif system == 'Windows':
            self._sh.SetPath(target)
            self._persist.Save(path + '.lnk', 1)

    def _create_structure(self, path, node):
        if node is None:
            # file
            open(path, 'w').close()
        elif type(node) is str:
            # link
            target = os.path.join(self.tempdir, node)
            self._create_link(path, target)
        elif type(node) is dict:
            # directory
            os.makedirs(path)
            for key, value in node.iteritems():
                self._create_structure(os.path.join(path, key), value)

    def test_simple_tree(self):
        tree = {'dir1': {'file1': None},
                'dir2': {'file2': None,
                         'file3': None,
                         'file4': None,
                        },
                'dir3': {'dir4': {'file5': None,
                                  'file6': None,
                                 },
                         'file7': None,
                        },
               }
        self._create_structure(self.tempdir, tree)
        dfr = self.scanner._count_files(self.tempdir)
        dfr.addCallback(self.assertEquals, 7)
        return dfr

    def test_tree_with_links(self):
        tree1 = {'dir1': {'file1': None,
                          'file2': None,
                         },
                 'file3': None,
                }
        tree2 = {'dir2': {'file4': None},
                'dir3': {'file5': None,
                         'flink1': os.path.join('tree1', 'file3'),
                         'file6': None,
                        },
                'dir4': {'dir5': {'file7': None,
                                  'file8': None,
                                 },
                         'dlink1': os.path.join('tree1', 'dir1'),
                         'file9': None,
                        },
               }
        tree1path = os.path.join(self.tempdir, 'tree1')
        self._create_structure(tree1path, tree1)
        tree2path = os.path.join(self.tempdir, 'tree2')
        self._create_structure(tree2path, tree2)
        dfr = self.scanner._count_files(tree2path)
        dfr.addCallback(self.assertEquals, 9)
        return dfr
