# coding=utf-8
from __future__ import absolute_import

__author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"

import unittest
import mock

import octoprint.slicing

class TestSlicingManager(unittest.TestCase):

	def setUp(self):
		self.addCleanup(self.cleanUp)

		import tempfile
		self.profile_path = tempfile.mkdtemp()

		self.slicer_plugin = mock.MagicMock()
		self.slicer_plugin.get_slicer_properties.return_value = dict(type="mock", name="Mock", same_device=True)

		# mock plugin manager
		self.plugin_manager_patcher = mock.patch("octoprint.plugin.plugin_manager")
		self.plugin_manager = self.plugin_manager_patcher.start()
		self._mock_slicer_plugins(self.slicer_plugin)

		# mock settings
		self.settings_patcher = mock.patch("octoprint.slicing.settings")
		settings = self.settings_patcher.start()
		self.settings = settings.return_value

		self.slicing_manager = octoprint.slicing.SlicingManager(self.profile_path)

	def tearDown(self):
		import shutil
		shutil.rmtree(self.profile_path)

	def cleanUp(self):
		self.settings_patcher.stop()
		self.plugin_manager_patcher.stop()

	def _mock_slicer_plugins(self, *plugins):
		def get_implementations(*types):
			import octoprint.plugin
			if octoprint.plugin.SlicerPlugin in types:
				return dict(("slicer_" + plugin.get_slicer_properties()["type"], plugin) for plugin in plugins)
			return dict()
		self.plugin_manager.return_value.get_implementations.side_effect = get_implementations

	def test_registered_slicers(self):
		self.assertEquals(["mock"], self.slicing_manager.registered_slicers)

	def test_slicing_enabled(self):
		self.assertTrue(self.slicing_manager.slicing_enabled)

	def test_default_slicer(self):
		def get(path):
			if path == ["slicing", "defaultSlicer"]:
				return "mock"
			else:
				return None
		self.settings.get.side_effect = get

		self.assertEquals("mock", self.slicing_manager.default_slicer)

	def test_default_slicer_unknown(self):
		def get(path):
			if path == ["slicing", "defaultSlicer"]:
				return "unknown"
			else:
				return None
		self.settings.get.side_effect = get

		self.assertIsNone(self.slicing_manager.default_slicer)

	@mock.patch("threading.Thread")
	@mock.patch("tempfile.NamedTemporaryFile")
	@mock.patch("os.remove")
	def test_slice(self, mocked_os_remove, mocked_tempfile, mocked_thread):
		# mock temporary file
		temp_file = mock.MagicMock()
		temp_file.name = "tmp.file"
		mocked_tempfile.return_value = temp_file

		# mock retrieval of default profile
		def get(path):
			return dict()
		self.settings.get.side_effect = get

		default_profile = octoprint.slicing.SlicingProfile("mock", "default", dict(layer_height=0.2, fill_density=40))
		self.slicer_plugin.get_slicer_default_profile.return_value = default_profile

		# mock threading
		class MockThread(object):
			def __init__(self):
				self.target = None
				self.args = None
				self.mock = None

			def constructor(self, target=None, args=None):
				self.target = target
				self.args = args
				self.mock = mock.MagicMock()
				self.mock.start.side_effect = self.start
				return self.mock

			def start(self):
				self.target(*self.args)
		mock_thread = MockThread()
		mocked_thread.side_effect = mock_thread.constructor

		# mock slicing
		self.slicer_plugin.do_slice.return_value = True, None

		##~~ call tested method
		slicer_name = "mock"
		source_path = "prefix/source.file"
		dest_path = "prefix/dest.file"
		profile_name = "dummy_profile"
		callback = mock.MagicMock()
		callback_args = ("one", "two", "three")
		callback_kwargs = dict(foo="bar")
		overrides = dict(layer_height=0.5)

		self.slicing_manager.slice(slicer_name, source_path, dest_path, profile_name, callback, callback_args=callback_args, callback_kwargs=callback_kwargs, overrides=overrides)

		# assert that temporary profile was created properly
		self.slicer_plugin.save_slicer_profile.assert_called_once_with("tmp.file", default_profile, overrides=overrides)

		# assert that slicing thread was created properly
		mocked_thread.assert_called_once_with(target=mock.ANY, args=(self.slicer_plugin, source_path, dest_path, profile_name, overrides, callback, callback_args, callback_kwargs))
		self.assertTrue(mock_thread.mock.daemon)
		mock_thread.mock.start.assert_called_once()

		# assert that slicer was called correctly
		self.slicer_plugin.do_slice.assert_called_once_with("prefix/source.file", machinecode_path="prefix/dest.file", profile_path="tmp.file", on_progress=None, on_progress_args=None, on_progress_kwargs=None)

		# assert that temporary profile was deleted again
		mocked_os_remove.assert_called_once_with("tmp.file")

		# assert that callback was called property
		callback.assert_called_once_with(*callback_args, **callback_kwargs)
