#! /usr/bin/env python3
# -*- coding: utf-8 -*-

#	Copyright 2012-2013, Marten de Vries
#
#	This file is part of OpenTeacher.
#
#	OpenTeacher is free software: you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation, either version 3 of the License, or
#	(at your option) any later version.
#
#	OpenTeacher is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with OpenTeacher.  If not, see <http://www.gnu.org/licenses/>.

import os
import unittest
import logging
import moduleManager

logger = logging.getLogger(__name__)

MODES = ("all", "moduleManager")

class TestCase(unittest.TestCase):
	"""Tries to enable() and disable() all modules with different
	   amounts of modules in uses enabled. Checks if modules reset their
	   state on disable(). This segfaults sometimes, but it shouldn't
	   give any other errors. (And if anyone manages to remove the
	   segfault, that would be very welcome!)

	"""
	def setUp(self):
		if not self.mode in MODES:
			self.skipTest("Too heavy for this test mode.")
		self._mm = moduleManager.ModuleManager(self._masterModuleManager.modulesPath)

	def _enableIncludingDependenciesIfNotActive(self, mod, minimalDependencies):
		#the fast exit so the recursiveness isn't forever
		if getattr(mod, "active", False):
			return True, []
		return self._enableIncludingDependencies(mod, minimalDependencies)

	def _enableIncludingDependencies(self, mod, minimalDependencies):
		success, enabledMods = self._enableDependencies(mod, minimalDependencies)
		if not success:
			return False, enabledMods

		#enable
		if hasattr(mod, "enable"):
			logger.debug("enabling " + mod.__class__.__file__)
			mod.enable()
		enabled = getattr(mod, "active", False)
		if enabled:
			enabledMods.append(mod)
		return enabled, enabledMods

	def _enableDependencies(self, mod, minimalDependencies):
		enabledMods = []

		for selector in getattr(mod, "requires", []):
			#remove recursiveness
			selected = set(selector) - set([mod])
			success, enabledMods = self._enableDependencySelector(selected, minimalDependencies, enabledMods)
			if not success:
				#fast exit
				return False, enabledMods

		if not minimalDependencies:
			for selector in getattr(mod, "uses", []):
				#remove recursiveness
				selected = set(selector) - set([mod])
				success, enabledMods = self._enableDependencySelector(selected, minimalDependencies, enabledMods)
		return True, enabledMods

	def _enableDependencySelector(self, selector, minimalDependencies, enabledMods):
		success = False
		for requirement in selector:
			subSuccess, otherMods = self._enableIncludingDependenciesIfNotActive(requirement, minimalDependencies)
			enabledMods.extend(otherMods)
			if subSuccess:
				success = True
				if minimalDependencies:
					#dependencies met for this selector, stop trying
					break
		#if reaching the end of the loop, dependencies couldn't be
		#satisfied
		return success, enabledMods

	def _disableDependencyTree(self, mods):
		for mod in reversed(mods):
			if hasattr(mod, "disable"):
				logger.debug("disabling " + mod.__class__.__file__)
				mod.disable()

	def _fakeExecuteModule(self):
		#initialize settings (normally they're used by the execute
		#module)
		next(iter(self._mm.mods(type="settings"))).initialize()

		#inject an alternative execute module into the module system,
		#because we can't use the real one. (Every module capable of
		#doing that registers at startRunning.)
		class ExecuteMod:
			type = "execute"
			active = True
			startRunning = next(iter(self._mm.mods(type="event"))).createEvent()
			__file__ = os.path.join(self._mm.modulesPath, "org/openteacher/logic/execute/execute.py")
		self._mm._modules.remove(next(iter(self._mm.mods(type="execute"))))
		self._mm._modules.add(ExecuteMod())
		#Set a profile. The execute module does that normally.
		next(iter(self._mm.mods(type="modules"))).profile = "default"

	def _removeGtkModule(self):
		"""Removes the GTK module from the module manager, because that
		   one can't be enabled at the same time with Qt. Normally the
		   profiles take care of that, but this tests circumvents the
		   profiles.

		"""
		theMod = next(iter(self._mm.mods(type="gtkGui")))
		self._mm._modules.remove(theMod)

	def _doTest(self, minimalDependencies):
		self._fakeExecuteModule()
		self._removeGtkModule()

		for mod in self._mm.mods:
			startVars = set(vars(mod).keys()) - set(["active"])
			try:
				success, enabledMods = self._enableIncludingDependenciesIfNotActive(mod, minimalDependencies)
			except RuntimeError:
				print(mod)
				raise
			self._disableDependencyTree(enabledMods)
			endVars = set(vars(mod).keys()) - set(["active"])
			try:
				self.assertEqual(startVars, endVars)
			except AssertionError: # pragma: no cover
				print(mod)
				raise
			logger.debug("")

	def testMinimalDependencies(self):
		self._doTest(True)

	def testWithFullDependencies(self):
		self._doTest(False)

class TestModule:
	def __init__(self, moduleManager, *args, **kwargs):
		super().__init__(*args, **kwargs)
		self._mm = moduleManager

		self.type = "test"

	def enable(self):
		self.TestCase = TestCase
		self.TestCase._masterModuleManager = self._mm
		self.active = True

	def disable(self):
		self.active = False
		del self.TestCase

def init(moduleManager):
	return TestModule(moduleManager)
