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

#	Copyright 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 unittest

class CheckCall:
	def __init__(self, *args, **kwargs):
		super().__init__(*args, **kwargs)

		self.reset()

	def __call__(self, *args, **kwargs):
		self.called = True
		self.args = args
		self.kwargs = kwargs

	def reset(self):
		self.called = False
		self.args = ()
		self.kwargs = {}

class UiEnablerWatcher:
	def __init__(self, controller, *args, **kwargs):
		super().__init__(*args, **kwargs)

		self._onEnableCheck = CheckCall()
		controller.enableCheck.handle(self._onEnableCheck)
		self._onEnableSkip = CheckCall()
		controller.enableSkip.handle(self._onEnableSkip)
		self._onEnableInput = CheckCall()
		controller.enableInput.handle(self._onEnableInput)

	def assertUiHasBeenEnabled(self):
		assert self._onEnableCheck.called
		assert self._onEnableSkip.called
		assert self._onEnableInput.called

class UiDisablerWatcher:
	def __init__(self, controller, *args, **kwargs):
		super().__init__(*args, **kwargs)

		self._onDisableCheck = CheckCall()
		controller.disableCheck.handle(self._onDisableCheck)
		self._onDisableSkip = CheckCall()
		controller.disableSkip.handle(self._onDisableSkip)
		self._onDisableInput = CheckCall()
		controller.disableInput.handle(self._onDisableInput)

	def assertUiHasBeenDisabled(self):
		assert self._onDisableCheck.called
		assert self._onDisableSkip.called
		assert self._onDisableInput.called

class TestCase(unittest.TestCase):
	@property
	def _list(self):
		return {
			"tests": [],
			"items": [
				{
					"id": 0,
					"questions": [["een"]],
					"answers": [["one"]],
				},
				{
					"id": 1,
					"questions": [["twee"]],
					"answers": [["two"]],
				},
			],
		}

	def setUp(self):
		self._lessonTypeMod = next(iter(self._mm.mods("active", type="lessonType", testName="allOnce")))

	@property
	def _lessonType(self):
		return self._lessonTypeMod.createLessonType(self._list, [0, 1])

	@property
	def _lessonType2(self):
		return self._lessonTypeMod.createLessonType(self._list, [1])

	def _getControllers(self):
		for c in self._getControllersWithoutLessonType():
			c.lessonType = self._lessonType
			yield c

	def _getControllersWithoutLessonType(self):
		"""A bunch of tests don't work with the JS version anymore, as the tests
		were written with a better JS<->Python bridge in mind. Adding support
		for Python objects in general might improve the situation a bit, but the
		inability to affect Python objects (other than through side effects of
		function calls) from JS will probably still break these tests up.

		As a result, the stuff below isn't also run on:

		self._mm.mods("active", type="jsInputTypingLogic")

		"""
		for mod in self._mm.mods("active", type="inputTypingLogic"):
			yield mod.createController()

	def testCallingMethodsWithoutLessonType(self):
		for controller in self._getControllersWithoutLessonType():
			with self.assertRaises(AttributeError):
				controller.checkTriggered("shouldn't matter")
			with self.assertRaises(AttributeError):
				controller.correctAnywayTriggered()
			with self.assertRaises(AttributeError):
				controller.skipTriggered()
			with self.assertRaises(AttributeError):
				controller.userIsTyping()

	def testCallingMethodsWithoutStartingLesson(self):
		for controller in self._getControllers():
			with self.assertRaises(ValueError):
				controller.checkTriggered("whatever")
			with self.assertRaises(ValueError):
				controller.correctAnywayTriggered()
			with self.assertRaises(ValueError):
				controller.skipTriggered()
			with self.assertRaises(ValueError):
				controller.userIsTyping()

	def testSettingOtherLessonTypeWhileShowingACorrection(self):
		for controller in self._getControllers():
			self._makeUnusedControllerShowACorrection(controller)

			uiWatcher = UiEnablerWatcher(controller)
			onHideCorrection = CheckCall()
			controller.hideCorrection.handle(onHideCorrection)

			controller.lessonType = self._lessonType2

			self.assertTrue(onHideCorrection.called)
			uiWatcher.assertUiHasBeenEnabled()

			#start again to test if everything was cleaned up properly
			controller.lessonType.start()

	def _makeUnusedControllerShowACorrection(self, controller):
		onShowCorrection = CheckCall()
		controller.showCorrection.handle(onShowCorrection)
		onHideCorrection = CheckCall()
		controller.hideCorrection.handle(onHideCorrection)
		uiWatcher = UiDisablerWatcher(controller)

		controller.lessonType.start()
		controller.checkTriggered(u"a wrong answer")

		self.assertTrue(onShowCorrection.called)
		self.assertEqual(onShowCorrection.args, (u"one",))
		self.assertFalse(onHideCorrection.called)
		uiWatcher.assertUiHasBeenDisabled()

	def testMethodsWhileShowingCorrection(self):
		for controller in self._getControllers():
			self._makeUnusedControllerShowACorrection(controller)

			with self.assertRaises(ValueError):
				controller.checkTriggered(u"this answer doesn't matter")
			with self.assertRaises(ValueError):
				controller.skipTriggered()
			with self.assertRaises(ValueError):
				controller.userIsTyping()

	def testCorrectAnywayWhileShowingCorrection(self):
		for controller in self._getControllers():
			self._makeUnusedControllerShowACorrection(controller)

			onHideCorrection = CheckCall()
			controller.hideCorrection.handle(onHideCorrection)

			uiWatcher = UiEnablerWatcher(controller)

			controller.correctAnywayTriggered()
			self.assertTrue(onHideCorrection.called)
			uiWatcher.assertUiHasBeenEnabled()

	def testSkip(self):
		for controller in self._getControllers():
			onNewItem = CheckCall()
			controller.lessonType.newItem.handle(onNewItem)

			controller.lessonType.start()
			self.assertEqual((self._list["items"][0],), onNewItem.args)
			controller.skipTriggered()
			self.assertEqual((self._list["items"][1],), onNewItem.args)

	def testCompleteLesson(self):
		for controller in self._getControllers():
			controller.lessonType.start()
			#right answer both times
			controller.checkTriggered(u"one")
			controller.checkTriggered(u"two")

	def testCallingCorrectionShowingDoneWhileNoCorrectionIsShown(self):
		for controller in self._getControllers():
			controller.lessonType.start()
			with self.assertRaises(ValueError):
				controller.correctionShowingDone()

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

		self.type = "test"
		self.requires = (
			self._mm.mods(type="inputTypingLogic"),
			self._mm.mods(type="lessonType", testName="allOnce"),
		)

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

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

def init(moduleManager):
	return TestModule(moduleManager)
