import bpy
import bpy_extras
import sys
from bpy.props import (
	BoolProperty,
	IntProperty,
	PointerProperty,
	StringProperty,
	EnumProperty,
	)
from mathutils import (
	Vector,
	)

from bpy_extras.view3d_utils import (
	region_2d_to_vector_3d,
	region_2d_to_origin_3d,
	region_2d_to_location_3d,
	location_3d_to_region_2d,
)
from .carver_profils import (
	Profils
	)

from .carver_utils import (
	duplicateObject,
	UndoListUpdate,
	createMeshFromData,
	SelectObject,
	Selection_Save_Restore,
	Selection_Save,
	Selection_Restore,
	update_grid,
	objDiagonal,
	Undo,
	UndoAdd,
	Pick,
	rot_axis_quat,
	MoveCursor,
	Picking,
	CreateCutSquare,
	CreateCutCircle,
	CreateCutLine,
	boolean_operation,
	update_bevel,
	CreateBevel,
	Rebool,
	Snap_Cursor,
	)

from .carver_draw import draw_callback_px

# Modal Operator
class CARVER_OT_operator(bpy.types.Operator):
	bl_idname = "carver.operator"
	bl_label = "Carver"
	bl_description = "Cut or create Meshes in Object mode"
	bl_options = {'REGISTER', 'UNDO'}

	def __init__(self):
		context = bpy.context
		# Carve mode: Cut, Object, Profile
		self.CutMode = False
		self.CreateMode = False
		self.ObjectMode = False
		self.ProfileMode = False

		# Create mode
		self.ExclusiveCreateMode = False
		if len(context.selected_objects) == 0:
			self.ExclusiveCreateMode = True
			self.CreateMode = True

		# Cut type (Rectangle, Circle, Line)
		self.rectangle = 0
		self.line = 1
		self.circle = 2

		# Cut Rectangle coordinates
		self.rectangle_coord = []

		# Selected type of cut
		self.CutType = 0

		# Boolean operation
		self.difference = 0
		self.union = 1

		self.BoolOps = self.difference

		self.CurrentSelection = context.selected_objects.copy()
		self.CurrentActive = context.active_object
		self.all_sel_obj_list = context.selected_objects.copy()
		self.save_active_obj = None

		args = (self, context)
		self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_PIXEL')

		self.mouse_path = [(0, 0), (0, 0)]

		# Keyboard event
		self.shift = False
		self.ctrl = False
		self.alt = False

		self.dont_apply_boolean = context.scene.mesh_carver.DontApply
		self.Auto_BevelUpdate = True

		# Circle variables
		self.stepAngle = [2, 4, 5, 6, 9, 10, 15, 20, 30, 40, 45, 60, 72, 90]
		self.step = 4

		# Primitives Position
		self.xpos = 0
		self.ypos = 0
		self.InitPosition = False

		# Close polygonal shape
		self.Closed = False

		# Depth Cursor
		self.snapCursor = context.scene.mesh_carver.DepthCursor

		# Help
		self.AskHelp = False

		# Working object
		self.OpsObj = context.active_object

		# Rebool forced (cut line)
		self.ForceRebool = False

		self.ViewVector = Vector()
		self.CurrentObj = None

		# Brush
		self.BrushSolidify = False
		self.WidthSolidify = False
		self.CarveDepth = False
		self.BrushDepth = False
		self.BrushDepthOffset = 0.0
		self.snap = False

		self.ObjectScale = False

		#Init create circle primitive
		self.CLR_C = []

		# Cursor location
		self.CurLoc = Vector((0.0, 0.0, 0.0))
		self.SavCurLoc = Vector((0.0, 0.0, 0.0))

		# Mouse region
		self.mouse_region = -1, -1
		self.SavMousePos = None
		self.xSavMouse = 0

		# Scale, rotate object
		self.ascale = 0
		self.aRotZ = 0
		self.nRotZ = 0
		self.quat_rot_axis = None
		self.quat_rot = None

		self.RandomRotation = context.scene.mesh_carver.ORandom

		self.ShowCursor = True

		self.Instantiate = context.scene.mesh_carver.OInstanciate

		self.ProfileBrush = None
		self.ObjectBrush = None

		self.InitBrush = {
		'location' : None,
		'scale' : None,
		'rotation_quaternion' : None,
		'rotation_euler' : None,
		'display_type' : 'WIRE',
		'show_in_front' : False
		}

		# Array variables
		self.nbcol = 1
		self.nbrow = 1
		self.gapx = 0
		self.gapy = 0
		self.scale_x = 1
		self.scale_y = 1
		self.GridScaleX = False
		self.GridScaleY = False

	@classmethod
	def poll(cls, context):
		ob = None
		if len(context.selected_objects) > 0:
			ob = context.selected_objects[0]
		# Test if selected object or none (for create mode)
		return (
			(ob and ob.type == 'MESH' and context.mode == 'OBJECT') or
			(context.mode == 'OBJECT' and ob is None) or
			(context.mode == 'EDIT_MESH'))

	def modal(self, context, event):
		PI = 3.14156
		region_types = {'WINDOW', 'UI'}
		win = context.window

		# Find the limit of the view3d region
		self.check_region(context,event)

		for area in win.screen.areas:
			if area.type == 'VIEW_3D':
				for region in area.regions:
					if not region_types or region.type in region_types:
						region.tag_redraw()

		# Change the snap increment value using the wheel mouse
		if self.CutMode:
			if self.alt is False:
				if self.ctrl and (self.CutType in (self.line, self.rectangle)):
					# Get the VIEW3D area
					for i, a in enumerate(context.screen.areas):
						if a.type == 'VIEW_3D':
							space = context.screen.areas[i].spaces.active
					grid_scale = space.overlay.grid_scale
					grid_subdivisions = space.overlay.grid_subdivisions

					if event.type == 'WHEELUPMOUSE':
						 space.overlay.grid_subdivisions += 1
					elif event.type == 'WHEELDOWNMOUSE':
						 space.overlay.grid_subdivisions -= 1

		if event.type in {
				'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE',
				'NUMPAD_1', 'NUMPAD_2', 'NUMPAD_3', 'NUMPAD_4', 'NUMPAD_6',
				'NUMPAD_7', 'NUMPAD_8', 'NUMPAD_9', 'NUMPAD_5'}:
			return {'PASS_THROUGH'}

		try:
			# [Shift]
			self.shift = True if event.shift else False

			# [Ctrl]
			self.ctrl = True if event.ctrl else False

			# [Alt]
			self.alt = False

			# [Alt] press : Init position variable before moving the cut brush with LMB
			if event.alt:
				if self.InitPosition is False:
					self.xpos = 0
					self.ypos = 0
					self.last_mouse_region_x = event.mouse_region_x
					self.last_mouse_region_y = event.mouse_region_y
					self.InitPosition = True
				self.alt = True

			# [Alt] release : update the coordinates
			if self.InitPosition and self.alt is False:
				for i in range(0, len(self.mouse_path)):
					l = list(self.mouse_path[i])
					l[0] += self.xpos
					l[1] += self.ypos
					self.mouse_path[i] = tuple(l)

				self.xpos = self.ypos = 0
				self.InitPosition = False

			if event.type == 'SPACE' and event.value == 'PRESS':
				# If object or profile mode is TRUE : Confirm the cut
				if self.ObjectMode or self.ProfileMode:
					# If array, remove double with intersect meshes
					if ((self.nbcol + self.nbrow) > 3):
						# Go in edit mode mode
						bpy.ops.object.mode_set(mode='EDIT')
						# Remove duplicate vertices
						bpy.ops.mesh.remove_doubles()
						# Return in object mode
						bpy.ops.object.mode_set(mode='OBJECT')

					if self.alt:
						# Save selected objects
						self.all_sel_obj_list = context.selected_objects.copy()
						if len(context.selected_objects) > 0:
							bpy.ops.object.select_all(action='TOGGLE')

						if self.ObjectMode:
							SelectObject(self, self.ObjectBrush)
						else:
							SelectObject(self, self.ProfileBrush)
						duplicateObject(self)
					else:
						# Brush Cut
						self.Cut()
						# Save selected objects
						if self.ObjectMode:
							if len(self.ObjectBrush.children) > 0:
								self.all_sel_obj_list = context.selected_objects.copy()
								if len(context.selected_objects) > 0:
									bpy.ops.object.select_all(action='TOGGLE')

								if self.ObjectMode:
									SelectObject(self, self.ObjectBrush)
								else:
									SelectObject(self, self.ProfileBrush)
								duplicateObject(self)

						UndoListUpdate(self)

					# Save cursor position
					self.SavMousePos = self.CurLoc
				else:
					if self.CutMode is False:
						# Cut Mode
						self.CutType += 1
						if self.CutType > 2:
							self.CutType = 0
					else:
						if self.CutType == self.line:
							# Cuts creation
							CreateCutLine(self, context)
							if self.CreateMode:
								# Object creation
								self.CreateGeometry()
								bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
								# Cursor Snap
								context.scene.mesh_carver.DepthCursor = self.snapCursor
								# Object Instantiate
								context.scene.mesh_carver.OInstanciate = self.Instantiate
								# Random rotation
								context.scene.mesh_carver.ORandom = self.RandomRotation

								return {'FINISHED'}
							else:
								self.Cut()
								UndoListUpdate(self)


#-----------------------------------------------------
# Object creation
#-----------------------------------------------------


			# Object creation
			if event.type == self.carver_prefs.Key_Create and event.value == 'PRESS':
				if self.ExclusiveCreateMode is False:
					self.CreateMode = not self.CreateMode

			# Auto Bevel Update
			if event.type == self.carver_prefs.Key_Update and event.value == 'PRESS':
				self.Auto_BevelUpdate = not self.Auto_BevelUpdate

			# Boolean operation type
			if event.type == self.carver_prefs.Key_Bool and event.value == 'PRESS':
				if (self.ProfileMode is True) or (self.ObjectMode is True):
					if self.BoolOps == self.difference:
						self.BoolOps = self.union
					else:
						self.BoolOps = self.difference

			# Brush Mode
			if event.type == self.carver_prefs.Key_Brush and event.value == 'PRESS':
				self.dont_apply_boolean = False
				if (self.ProfileMode is False) and (self.ObjectMode is False):
					self.ProfileMode = True
				else:
					self.ProfileMode = False
					if self.ObjectBrush is not None:
						if self.ObjectMode is False:
							self.ObjectMode = True
							self.BrushSolidify = False
							self.CList = self.OB_List

							Selection_Save_Restore(self)
							context.scene.mesh_carver.nProfile = self.nProfil
						else:
							self.ObjectMode = False
					else:
						self.BrushSolidify = False
						Selection_Save_Restore(self)

				if self.ProfileMode:
					createMeshFromData(self)
					self.ProfileBrush = bpy.data.objects["CT_Profil"]
					Selection_Save(self)
					self.BrushSolidify = True

					bpy.ops.object.select_all(action='TOGGLE')
					self.ProfileBrush.select_set(True)
					context.view_layer.objects.active = self.ProfileBrush
					# Set xRay
					self.ProfileBrush.show_in_front = True

					bpy.ops.object.modifier_add(type='SOLIDIFY')
					context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"
					context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1

					Selection_Restore(self)

					self.CList = self.CurrentSelection
				else:
					if self.ObjectBrush is not None:
						if self.ObjectMode is False:
							if self.ObjectBrush is not None:
								self.ObjectBrush.location = self.InitBrush['location']
								self.ObjectBrush.scale = self.InitBrush['scale']
								self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
								self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
								self.ObjectBrush.display_type = self.InitBrush['display_type']
								self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']

								#Store active and selected objects
								Selection_Save(self)

								#Remove Carver modifier
								self.BrushSolidify = False
								bpy.ops.object.select_all(action='TOGGLE')
								self.ObjectBrush.select_set(True)
								context.view_layer.objects.active = self.ObjectBrush
								bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")

								#Restore selected and active object
								Selection_Restore(self)
						else:
							if self.SolidifyPossible:
								#Store active and selected objects
								Selection_Save(self)
								self.BrushSolidify = True
								bpy.ops.object.select_all(action='TOGGLE')
								self.ObjectBrush.select_set(True)
								context.view_layer.objects.active = self.ObjectBrush
								# Set xRay
								self.ObjectBrush.show_in_front = True
								bpy.ops.object.modifier_add(type='SOLIDIFY')
								context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"
								context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1

								#Restore selected and active object
								Selection_Restore(self)

			# Help display
			if event.type == self.carver_prefs.Key_Help and event.value == 'PRESS':
				self.AskHelp = not self.AskHelp

			# Instantiate object
			if event.type == self.carver_prefs.Key_Instant and event.value == 'PRESS':
				self.Instantiate = not self.Instantiate

			# Close polygonal shape
			if event.type == self.carver_prefs.Key_Close and event.value == 'PRESS':
				if self.CreateMode:
					self.Closed = not self.Closed

			if event.type == self.carver_prefs.Key_Apply and event.value == 'PRESS':
				self.dont_apply_boolean = not self.dont_apply_boolean

			# Scale object
			if event.type == self.carver_prefs.Key_Scale and event.value == 'PRESS':
				if self.ObjectScale is False:
					self.mouse_region = event.mouse_region_x, event.mouse_region_y
				self.ObjectScale = True

			# Grid : Snap on grid
			if event.type == self.carver_prefs.Key_Snap and event.value == 'PRESS':
				self.snap = not self.snap

			# Array : Add column
			if event.type == 'UP_ARROW' and event.value == 'PRESS':
				self.nbrow += 1
				update_grid(self, context)

			# Array : Delete column
			elif event.type == 'DOWN_ARROW' and event.value == 'PRESS':
				self.nbrow -= 1
				update_grid(self, context)

			# Array : Add row
			elif event.type == 'RIGHT_ARROW' and event.value == 'PRESS':
				self.nbcol += 1
				update_grid(self, context)

			# Array : Delete row
			elif event.type == 'LEFT_ARROW' and event.value == 'PRESS':
				self.nbcol -= 1
				update_grid(self, context)

			# Array : Scale gap between columns
			if event.type == self.carver_prefs.Key_Gapy and event.value == 'PRESS':
				if self.GridScaleX is False:
					self.mouse_region = event.mouse_region_x, event.mouse_region_y
				self.GridScaleX = True

			# Array : Scale gap between rows
			if event.type == self.carver_prefs.Key_Gapx and event.value == 'PRESS':
				if self.GridScaleY is False:
					self.mouse_region = event.mouse_region_x, event.mouse_region_y
				self.GridScaleY = True

			# Cursor depth or solidify pattern
			if event.type == self.carver_prefs.Key_Depth and event.value == 'PRESS':
				if (self.ObjectMode is False) and (self.ProfileMode is False):
					self.snapCursor = not self.snapCursor
				else:
					# Solidify

					if (self.ObjectMode or self.ProfileMode) and (self.SolidifyPossible):
						solidify = True

						if self.ObjectMode:
							z = self.ObjectBrush.data.vertices[0].co.z
							ErrorMarge = 0.01
							for v in self.ObjectBrush.data.vertices:
								if abs(v.co.z - z) > ErrorMarge:
									solidify = False
									self.CarveDepth = True
									self.mouse_region = event.mouse_region_x, event.mouse_region_y
									break

						if solidify:
							if self.ObjectMode:
								for mb in self.ObjectBrush.modifiers:
									if mb.type == 'SOLIDIFY':
										AlreadySoldify = True
							else:
								for mb in self.ProfileBrush.modifiers:
									if mb.type == 'SOLIDIFY':
										AlreadySoldify = True

							if AlreadySoldify is False:
								Selection_Save(self)
								self.BrushSolidify = True

								bpy.ops.object.select_all(action='TOGGLE')
								if self.ObjectMode:
									self.ObjectBrush.select_set(True)
									context.view_layer.objects.active = self.ObjectBrush
									# Active le xray
									self.ObjectBrush.show_in_front = True
								else:
									self.ProfileBrush.select_set(True)
									context.view_layer.objects.active = self.ProfileBrush
									# Active le xray
									self.ProfileBrush.show_in_front = True

								bpy.ops.object.modifier_add(type='SOLIDIFY')
								context.object.modifiers["Solidify"].name = "CT_SOLIDIFY"

								context.object.modifiers["CT_SOLIDIFY"].thickness = 0.1

								Selection_Restore(self)

							self.WidthSolidify = not self.WidthSolidify
							self.mouse_region = event.mouse_region_x, event.mouse_region_y

			if event.type == self.carver_prefs.Key_BrushDepth and event.value == 'PRESS':
				if self.ObjectMode:
					self.CarveDepth = False

					self.BrushDepth = True
					self.mouse_region = event.mouse_region_x, event.mouse_region_y

			# Random rotation
			if event.type == 'R' and event.value == 'PRESS':
				self.RandomRotation = not self.RandomRotation

			# Undo
			if event.type == 'Z' and event.value == 'PRESS':
				if self.ctrl:
					if (self.CutType == self.line) and (self.CutMode):
						if len(self.mouse_path) > 1:
							self.mouse_path[len(self.mouse_path) - 1:] = []
					else:
						Undo(self)

			# Mouse move
			if event.type == 'MOUSEMOVE' :
				if self.ObjectMode or self.ProfileMode:
					fac = 50.0
					if self.shift:
						fac = 500.0
					if self.WidthSolidify:
						if self.ObjectMode:
							bpy.data.objects[self.ObjectBrush.name].modifiers[
								"CT_SOLIDIFY"].thickness += (event.mouse_region_x - self.mouse_region[0]) / fac
						elif self.ProfileMode:
							bpy.data.objects[self.ProfileBrush.name].modifiers[
								"CT_SOLIDIFY"].thickness += (event.mouse_region_x - self.mouse_region[0]) / fac
						self.mouse_region = event.mouse_region_x, event.mouse_region_y
					elif self.CarveDepth:
						for v in self.ObjectBrush.data.vertices:
							v.co.z += (event.mouse_region_x - self.mouse_region[0]) / fac
						self.mouse_region = event.mouse_region_x, event.mouse_region_y
					elif self.BrushDepth:
						self.BrushDepthOffset += (event.mouse_region_x - self.mouse_region[0]) / fac
						self.mouse_region = event.mouse_region_x, event.mouse_region_y
					else:
						if (self.GridScaleX):
							self.gapx += (event.mouse_region_x - self.mouse_region[0]) / 50
							self.mouse_region = event.mouse_region_x, event.mouse_region_y
							update_grid(self, context)
							return {'RUNNING_MODAL'}

						elif (self.GridScaleY):
							self.gapy += (event.mouse_region_x - self.mouse_region[0]) / 50
							self.mouse_region = event.mouse_region_x, event.mouse_region_y
							update_grid(self, context)
							return {'RUNNING_MODAL'}

						elif self.ObjectScale:
							self.ascale = -(event.mouse_region_x - self.mouse_region[0])
							self.mouse_region = event.mouse_region_x, event.mouse_region_y

							if self.ObjectMode:
								self.ObjectBrush.scale.x -= float(self.ascale) / 150.0
								if self.ObjectBrush.scale.x <= 0.0:
									self.ObjectBrush.scale.x = 0.0
								self.ObjectBrush.scale.y -= float(self.ascale) / 150.0
								if self.ObjectBrush.scale.y <= 0.0:
									self.ObjectBrush.scale.y = 0.0
								self.ObjectBrush.scale.z -= float(self.ascale) / 150.0
								if self.ObjectBrush.scale.z <= 0.0:
									self.ObjectBrush.scale.z = 0.0

							elif self.ProfileMode:
								if self.ProfileBrush is not None:
									self.ProfileBrush.scale.x -= float(self.ascale) / 150.0
									self.ProfileBrush.scale.y -= float(self.ascale) / 150.0
									self.ProfileBrush.scale.z -= float(self.ascale) / 150.0
						else:
							if self.LMB:
								if self.ctrl:
									self.aRotZ = - \
										((int((event.mouse_region_x - self.xSavMouse) / 10.0) * PI / 4.0) * 25.0)
								else:
									self.aRotZ -= event.mouse_region_x - self.mouse_region[0]
								self.ascale = 0.0

								self.mouse_region = event.mouse_region_x, event.mouse_region_y
							else:
								target_hit, target_normal, target_eul_rotation = Pick(context, event, self)
								if target_hit is not None:
									self.ShowCursor = True
									up_vector = Vector((0.0, 0.0, 1.0))
									quat_rot_axis = rot_axis_quat(up_vector, target_normal)
									self.quat_rot = target_eul_rotation @ quat_rot_axis
									MoveCursor(quat_rot_axis, target_hit, self)
									self.SavCurLoc = target_hit
									if self.ctrl:
										if self.SavMousePos is not None:
											xEcart = abs(self.SavMousePos.x - self.SavCurLoc.x)
											yEcart = abs(self.SavMousePos.y - self.SavCurLoc.y)
											zEcart = abs(self.SavMousePos.z - self.SavCurLoc.z)
											if (xEcart > yEcart) and (xEcart > zEcart):
												self.CurLoc = Vector(
													(target_hit.x, self.SavMousePos.y, self.SavMousePos.z))
											if (yEcart > xEcart) and (yEcart > zEcart):
												self.CurLoc = Vector(
													(self.SavMousePos.x, target_hit.y, self.SavMousePos.z))
											if (zEcart > xEcart) and (zEcart > yEcart):
												self.CurLoc = Vector(
													(self.SavMousePos.x, self.SavMousePos.y, target_hit.z))
											else:
												self.CurLoc = target_hit
									else:
										self.CurLoc = target_hit
				else:
					if self.CutMode:
						if self.alt is False:
							if self.ctrl :
								# Find the closest position on the overlay grid and snap the mouse on it
								# Draw a mini grid around the cursor
								mouse_pos = [[event.mouse_region_x, event.mouse_region_y]]
								Snap_Cursor(self, context, event, mouse_pos)

							else:
								if len(self.mouse_path) > 0:
									self.mouse_path[len(self.mouse_path) -
													1] = (event.mouse_region_x, event.mouse_region_y)
						else:
							# [ALT] press, update position
							self.xpos += (event.mouse_region_x - self.last_mouse_region_x)
							self.ypos += (event.mouse_region_y - self.last_mouse_region_y)

							self.last_mouse_region_x = event.mouse_region_x
							self.last_mouse_region_y = event.mouse_region_y

			elif event.type == 'LEFTMOUSE' and event.value == 'PRESS':
				if self.ObjectMode or self.ProfileMode:
					if self.LMB is False:
						target_hit, target_normal, target_eul_rotation  = Pick(context, event, self)
						if target_hit is not None:
							up_vector = Vector((0.0, 0.0, 1.0))
							self.quat_rot_axis = rot_axis_quat(up_vector, target_normal)
							self.quat_rot = target_eul_rotation @ self.quat_rot_axis
						self.mouse_region = event.mouse_region_x, event.mouse_region_y
						self.xSavMouse = event.mouse_region_x

						if self.ctrl:
							self.nRotZ = int((self.aRotZ / 25.0) / (PI / 4.0))
							self.aRotZ = self.nRotZ * (PI / 4.0) * 25.0

					self.LMB = True

			# LEFTMOUSE
			elif event.type == 'LEFTMOUSE' and event.value == 'RELEASE' and self.in_view_3d:
				if self.ObjectMode or self.ProfileMode:
					# Rotation and scale
					self.LMB = False
					if self.ObjectScale is True:
						self.ObjectScale = False

					if self.GridScaleX is True:
						self.GridScaleX = False

					if self.GridScaleY is True:
						self.GridScaleY = False

					if self.WidthSolidify:
						self.WidthSolidify = False

					if self.CarveDepth is True:
						self.CarveDepth = False

					if self.BrushDepth is True:
						self.BrushDepth = False

				else:
					if self.CutMode is False:
						if self.ctrl:
							Picking(context, event)

						else:

							if self.CutType == self.line:
								if self.CutMode is False:
									self.mouse_path.clear()
									self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
									self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))
							else:
								self.mouse_path[0] = (event.mouse_region_x, event.mouse_region_y)
								self.mouse_path[1] = (event.mouse_region_x, event.mouse_region_y)
							self.CutMode = True
					else:
						if self.CutType != self.line:
							# Cut creation
							if self.CutType == self.rectangle:
								CreateCutSquare(self, context)
							if self.CutType == self.circle:
								CreateCutCircle(self, context)
							if self.CutType == self.line:
								CreateCutLine(self, context)

							if self.CreateMode:
								# Object creation
								self.CreateGeometry()
								bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
								# Depth Cursor
								context.scene.mesh_carver.DepthCursor = self.snapCursor
								# Instantiate object
								context.scene.mesh_carver.OInstanciate = self.Instantiate
								# Random rotation
								context.scene.mesh_carver.ORandom = self.RandomRotation
								# Apply operation
								context.scene.mesh_carver.DontApply = self.dont_apply_boolean

								# if Object mode, set initiale state
								if self.ObjectBrush is not None:
									self.ObjectBrush.location = self.InitBrush['location']
									self.ObjectBrush.scale = self.InitBrush['scale']
									self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
									self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
									self.ObjectBrush.display_type = self.InitBrush['display_type']
									self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']

									# remove solidify
									Selection_Save(self)
									self.BrushSolidify = False

									bpy.ops.object.select_all(action='TOGGLE')
									self.ObjectBrush.select_set(True)
									context.view_layer.objects.active = self.ObjectBrush

									bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")

									Selection_Restore(self)

									context.scene.mesh_carver.nProfile = self.nProfil

								return {'FINISHED'}
							else:
								self.Cut()
								UndoListUpdate(self)
						else:
							# Line
							self.mouse_path.append((event.mouse_region_x, event.mouse_region_y))

			# Change brush profil or circle subdivisions
			elif (event.type == 'COMMA' and event.value == 'PRESS') or \
						(event.type == self.carver_prefs.Key_Subrem and event.value == 'PRESS'):
				# Brush profil
				if self.ProfileMode:
					self.nProfil += 1
					if self.nProfil >= self.MaxProfil:
						self.nProfil = 0
					createMeshFromData(self)
				# Circle subdivisions
				if self.CutType == self.circle:
					self.step += 1
					if self.step >= len(self.stepAngle):
						self.step = len(self.stepAngle) - 1
			# Change brush profil or circle subdivisions
			elif (event.type == 'PERIOD' and event.value == 'PRESS') or \
						(event.type == self.carver_prefs.Key_Subadd and event.value == 'PRESS'):
				# Brush profil
				if self.ProfileMode:
					self.nProfil -= 1
					if self.nProfil < 0:
						self.nProfil = self.MaxProfil - 1
					createMeshFromData(self)
				# Circle subdivisions
				if self.CutType == self.circle:
					if self.step > 0:
						self.step -= 1
			# Quit
			elif event.type in {'RIGHTMOUSE', 'ESC'}:
				# Depth Cursor
				context.scene.mesh_carver.DepthCursor = self.snapCursor
				# Instantiate object
				context.scene.mesh_carver.OInstanciate = self.Instantiate
				# Random Rotation
				context.scene.mesh_carver.ORandom = self.RandomRotation
				# Apply boolean operation
				context.scene.mesh_carver.DontApply = self.dont_apply_boolean

				# Reset Object
				if self.ObjectBrush is not None:
					self.ObjectBrush.location = self.InitBrush['location']
					self.ObjectBrush.scale = self.InitBrush['scale']
					self.ObjectBrush.rotation_quaternion = self.InitBrush['rotation_quaternion']
					self.ObjectBrush.rotation_euler = self.InitBrush['rotation_euler']
					self.ObjectBrush.display_type = self.InitBrush['display_type']
					self.ObjectBrush.show_in_front = self.InitBrush['show_in_front']

					# Remove solidify modifier
					Selection_Save(self)
					self.BrushSolidify = False

					bpy.ops.object.select_all(action='TOGGLE')
					self.ObjectBrush.select_set(True)
					context.view_layer.objects.active = self.ObjectBrush

					bpy.ops.object.modifier_remove(modifier="CT_SOLIDIFY")
					bpy.ops.object.select_all(action='TOGGLE')

					Selection_Restore(self)

				Selection_Save_Restore(self)
				context.view_layer.objects.active = self.CurrentActive
				context.scene.mesh_carver.nProfile = self.nProfil

				bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')

				# Remove Copy Object Brush
				if bpy.data.objects.get("CarverBrushCopy") is not None:
					brush = bpy.data.objects["CarverBrushCopy"]
					self.ObjectBrush.data = bpy.data.meshes[brush.data.name]
					bpy.ops.object.select_all(action='DESELECT')
					bpy.data.objects["CarverBrushCopy"].select_set(True)
					bpy.ops.object.delete()

				return {'FINISHED'}

			return {'RUNNING_MODAL'}

		except:
			print("\n[Carver MT ERROR]\n")
			import traceback
			traceback.print_exc()

			context.window.cursor_modal_set("DEFAULT")
			context.area.header_text_set(None)
			bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')

			self.report({'WARNING'},
						"Operation finished. Failure during Carving (Check the console for more info)")

			return {'FINISHED'}

	def cancel(self, context):
		# Note: used to prevent memory leaks on quitting Blender while the modal operator
		# is still running, gets called on return {"CANCELLED"}
		bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')

	def invoke(self, context, event):
		if context.area.type != 'VIEW_3D':
			self.report({'WARNING'},
						"View3D not found or not currently active. Operation Cancelled")
			self.cancel(context)
			return {'CANCELLED'}

		# test if some other object types are selected that are not meshes
		for obj in context.selected_objects:
			if obj.type != "MESH":
				self.report({'WARNING'},
				"Some selected objects are not of the Mesh type. Operation Cancelled")
				self.cancel(context)
				return {'CANCELLED'}

		if context.mode == 'EDIT_MESH':
			bpy.ops.object.mode_set(mode='OBJECT')

		#Load the Carver preferences
		self.carver_prefs = bpy.context.preferences.addons[__package__].preferences

		# Get default patterns
		self.Profils = []
		for p in Profils:
			self.Profils.append((p[0], p[1], p[2], p[3]))

		for o in context.scene.objects:
			if not o.name.startswith(self.carver_prefs.ProfilePrefix):
				continue
			# In-scene profiles may have changed, remove them to refresh
			for m in bpy.data.meshes:
				if m.name.startswith(self.carver_prefs.ProfilePrefix):
					bpy.data.meshes.remove(m)

			vertices = []
			for v in o.data.vertices:
				vertices.append((v.co.x, v.co.y, v.co.z))

			faces = []
			for f in o.data.polygons:
				face = []
				for v in f.vertices:
					face.append(v)

				faces.append(face)

			self.Profils.append(
				(o.name,
				Vector((o.location.x, o.location.y, o.location.z)),
				vertices, faces)
			)

		self.nProfil = context.scene.mesh_carver.nProfile
		self.MaxProfil = len(self.Profils)


		# reset selected profile if last profile exceeds length of array
		if self.nProfil >= self.MaxProfil:
			self.nProfil = context.scene.mesh_carver.nProfile = 0

		if len(context.selected_objects) > 1:
			self.ObjectBrush = context.active_object

			# Copy the brush object
			ob = bpy.data.objects.new("CarverBrushCopy", context.object.data.copy())
			ob.location = self.ObjectBrush.location
			context.collection.objects.link(ob)
			context.view_layer.update()

			# Save default variables
			self.InitBrush['location'] = self.ObjectBrush.location.copy()
			self.InitBrush['scale'] = self.ObjectBrush.scale.copy()
			self.InitBrush['rotation_quaternion'] = self.ObjectBrush.rotation_quaternion.copy()
			self.InitBrush['rotation_euler'] = self.ObjectBrush.rotation_euler.copy()
			self.InitBrush['display_type'] = self.ObjectBrush.display_type
			self.InitBrush['show_in_front'] = self.ObjectBrush.show_in_front

			# Test if flat object
			z = self.ObjectBrush.data.vertices[0].co.z
			ErrorMarge = 0.01
			self.SolidifyPossible = True
			for v in self.ObjectBrush.data.vertices:
				if abs(v.co.z - z) > ErrorMarge:
					self.SolidifyPossible = False
					break

		self.CList = []
		self.OPList = []
		self.RList = []
		self.OB_List = []

		for obj in context.selected_objects:
			if obj != self.ObjectBrush:
				self.OB_List.append(obj)

		# Left button
		self.LMB = False

		# Undo Variables
		self.undo_index = 0
		self.undo_limit = context.preferences.edit.undo_steps
		self.undo_list = []

		# Boolean operations type
		self.BooleanType = 0

		self.UList = []
		self.UList_Index = -1
		self.UndoOps = []

		context.window_manager.modal_handler_add(self)
		return {'RUNNING_MODAL'}

	#Get the region area where the operator is used
	def check_region(self,context,event):
		if context.area != None:
			if context.area.type == "VIEW_3D" :
				for region in context.area.regions:
					if region.type == "TOOLS":
						t_panel = region
					elif region.type == "UI":
						ui_panel = region

				view_3d_region_x = Vector((context.area.x + t_panel.width, context.area.x + context.area.width - ui_panel.width))
				view_3d_region_y = Vector((context.region.y, context.region.y+context.region.height))

				if (event.mouse_x > view_3d_region_x[0] and event.mouse_x < view_3d_region_x[1] \
				and event.mouse_y > view_3d_region_y[0] and event.mouse_y < view_3d_region_y[1]):
					self.in_view_3d = True
				else:
					self.in_view_3d = False
			else:
				self.in_view_3d = False

	def CreateGeometry(self):
		context = bpy.context
		in_local_view = False

		for area in context.screen.areas:
			if area.type == 'VIEW_3D':
				if area.spaces[0].local_view is not None:
					in_local_view = True

		if in_local_view:
			bpy.ops.view3d.localview()

		if self.ExclusiveCreateMode:
			# Default width
			objBBDiagonal = 0.5
		else:
			ActiveObj = self.CurrentSelection[0]
			if ActiveObj is not None:
				# Object dimensions
				objBBDiagonal = objDiagonal(ActiveObj) / 4
		subdivisions = 2

		if len(context.selected_objects) > 0:
			bpy.ops.object.select_all(action='TOGGLE')

		context.view_layer.objects.active = self.CurrentObj

		bpy.data.objects[self.CurrentObj.name].select_set(True)
		bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')

		bpy.ops.object.mode_set(mode='EDIT')
		bpy.ops.mesh.select_all(action='SELECT')
		bpy.ops.mesh.select_mode(type="EDGE")
		if self.snapCursor is False:
			bpy.ops.transform.translate(value=self.ViewVector * objBBDiagonal * subdivisions)
		bpy.ops.mesh.extrude_region_move(
			TRANSFORM_OT_translate={"value": -self.ViewVector * objBBDiagonal * subdivisions * 2})

		bpy.ops.mesh.select_all(action='SELECT')
		bpy.ops.mesh.normals_make_consistent()
		bpy.ops.object.mode_set(mode='OBJECT')

		saved_location_0 = context.scene.cursor.location.copy()
		bpy.ops.view3d.snap_cursor_to_active()
		saved_location = context.scene.cursor.location.copy()
		bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
		context.scene.cursor.location = saved_location
		bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
		context.scene.cursor.location = saved_location_0

		bpy.data.objects[self.CurrentObj.name].select_set(True)
		bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')

		for o in self.all_sel_obj_list:
			bpy.data.objects[o.name].select_set(True)

		if in_local_view:
			bpy.ops.view3d.localview()

		self.CutMode = False
		self.mouse_path.clear()
		self.mouse_path = [(0, 0), (0, 0)]

	def Cut(self):
		context = bpy.context

		# Local view ?
		in_local_view = False
		for area in context.screen.areas:
			if area.type == 'VIEW_3D':
				if area.spaces[0].local_view is not None:
					in_local_view = True

		if in_local_view:
			bpy.ops.view3d.localview()

		# Save cursor position
		CursorLocation = context.scene.cursor.location.copy()

		#List of selected objects
		selected_obj_list = []

		#Cut Mode with line
		if (self.ObjectMode is False) and (self.ProfileMode is False):

			#Compute the bounding Box
			objBBDiagonal = objDiagonal(self.CurrentSelection[0])
			if self.dont_apply_boolean:
				subdivisions = 1
			else:
				subdivisions = 32

			# Get selected objects
			selected_obj_list = context.selected_objects.copy()

			bpy.ops.object.select_all(action='TOGGLE')

			context.view_layer.objects.active = self.CurrentObj

			bpy.data.objects[self.CurrentObj.name].select_set(True)
			bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')

			bpy.ops.object.mode_set(mode='EDIT')
			bpy.ops.mesh.select_all(action='SELECT')
			bpy.ops.mesh.select_mode(type="EDGE")
			#Translate the created mesh away from the view
			if (self.snapCursor is False) or (self.ForceRebool):
				bpy.ops.transform.translate(value=self.ViewVector * objBBDiagonal * subdivisions)
			#Extrude the mesh region and move the result
			bpy.ops.mesh.extrude_region_move(
				TRANSFORM_OT_translate={"value": -self.ViewVector * objBBDiagonal * subdivisions * 2})
			bpy.ops.mesh.select_all(action='SELECT')
			bpy.ops.mesh.normals_make_consistent()
			bpy.ops.object.mode_set(mode='OBJECT')
		else:
			# Create list
			if self.ObjectMode:
				for o in self.CurrentSelection:
					if o != self.ObjectBrush:
						selected_obj_list.append(o)
				self.CurrentObj = self.ObjectBrush
			else:
				selected_obj_list = self.CurrentSelection
				self.CurrentObj = self.ProfileBrush

		for obj in self.CurrentSelection:
			UndoAdd(self, "MESH", obj)

		# List objects create with rebool
		lastSelected = []

		for ActiveObj in selected_obj_list:
			context.scene.cursor.location = CursorLocation

			if len(context.selected_objects) > 0:
				bpy.ops.object.select_all(action='TOGGLE')

			# Select cut object
			bpy.data.objects[self.CurrentObj.name].select_set(True)
			context.view_layer.objects.active = self.CurrentObj

			bpy.ops.object.mode_set(mode='EDIT')
			bpy.ops.mesh.select_all(action='SELECT')
			bpy.ops.object.mode_set(mode='OBJECT')

			# Select object to cut
			bpy.data.objects[ActiveObj.name].select_set(True)
			context.view_layer.objects.active = ActiveObj

			bpy.ops.object.mode_set(mode='EDIT')
			bpy.ops.mesh.select_all(action='DESELECT')
			bpy.ops.object.mode_set(mode='OBJECT')

			# Boolean operation
			if (self.shift is False) and (self.ForceRebool is False):
				if self.ObjectMode or self.ProfileMode:
					if self.BoolOps == self.union:
						boolean_operation(bool_type="UNION")
					else:
						boolean_operation(bool_type="DIFFERENCE")
				else:
					boolean_operation(bool_type="DIFFERENCE")

				# Apply booleans
				if self.dont_apply_boolean is False:
					BMname = "CT_" + self.CurrentObj.name
					for mb in ActiveObj.modifiers:
						if (mb.type == 'BOOLEAN') and (mb.name == BMname):
							try:
								bpy.ops.object.modifier_apply(apply_as='DATA', modifier=BMname)
							except:
								bpy.ops.object.modifier_remove(modifier=BMname)
								exc_type, exc_value, exc_traceback = sys.exc_info()
								self.report({'ERROR'}, str(exc_value))

				bpy.ops.object.select_all(action='TOGGLE')
			else:
				if self.ObjectMode or self.ProfileMode:
					for mb in self.CurrentObj.modifiers:
						if (mb.type == 'SOLIDIFY') and (mb.name == "CT_SOLIDIFY"):
							try:
								bpy.ops.object.modifier_apply(apply_as='DATA', modifier="CT_SOLIDIFY")
							except:
								exc_type, exc_value, exc_traceback = sys.exc_info()
								self.report({'ERROR'}, str(exc_value))

				# Rebool
				Rebool(context, self)

				# Test if not empty object
				if context.selected_objects[0]:
					rebool_RT = context.selected_objects[0]
					if len(rebool_RT.data.vertices) > 0:
						# Create Bevel for new objects
						CreateBevel(context, context.selected_objects[0])

						UndoAdd(self, "REBOOL", context.selected_objects[0])

						context.scene.cursor.location = ActiveObj.location
						bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
					else:
						bpy.ops.object.delete(use_global=False)

				context.scene.cursor.location = CursorLocation

				if self.ObjectMode:
					context.view_layer.objects.active = self.ObjectBrush
				if self.ProfileMode:
					context.view_layer.objects.active = self.ProfileBrush

			if self.dont_apply_boolean is False:
				# Apply booleans
				BMname = "CT_" + self.CurrentObj.name
				for mb in ActiveObj.modifiers:
					if (mb.type == 'BOOLEAN') and (mb.name == BMname):
						try:
							bpy.ops.object.modifier_apply(apply_as='DATA', modifier=BMname)
						except:
							bpy.ops.object.modifier_remove(modifier=BMname)
							exc_type, exc_value, exc_traceback = sys.exc_info()
							self.report({'ERROR'}, str(exc_value))
				# Get new objects created with rebool operations
				if len(context.selected_objects) > 0:
					if self.shift is True:
						# Get the last object selected
						lastSelected.append(context.selected_objects[0])

		context.scene.cursor.location = CursorLocation

		if self.dont_apply_boolean is False:
			# Remove cut object
			if (self.ObjectMode is False) and (self.ProfileMode is False):
				if len(context.selected_objects) > 0:
					bpy.ops.object.select_all(action='TOGGLE')
				bpy.data.objects[self.CurrentObj.name].select_set(True)
				bpy.ops.object.delete(use_global=False)
			else:
				if self.ObjectMode:
					self.ObjectBrush.display_type = self.InitBrush['display_type']

		if len(context.selected_objects) > 0:
			bpy.ops.object.select_all(action='TOGGLE')

		# Select cut objects
		for obj in lastSelected:
			bpy.data.objects[obj.name].select_set(True)

		for ActiveObj in selected_obj_list:
			bpy.data.objects[ActiveObj.name].select_set(True)
			context.view_layer.objects.active = ActiveObj
		# Update bevel
		list_act_obj = context.selected_objects.copy()
		if self.Auto_BevelUpdate:
			update_bevel(context)

		# Re-select initial objects
		bpy.ops.object.select_all(action='TOGGLE')
		if self.ObjectMode:
			# Re-select brush
			self.ObjectBrush.select_set(True)
		for ActiveObj in selected_obj_list:
			bpy.data.objects[ActiveObj.name].select_set(True)
			context.view_layer.objects.active = ActiveObj

		# If object has children, set "Wire" draw type
		if self.ObjectBrush is not None:
			if len(self.ObjectBrush.children) > 0:
				self.ObjectBrush.display_type = "WIRE"
		if self.ProfileMode:
			self.ProfileBrush.display_type = "WIRE"

		if in_local_view:
			bpy.ops.view3d.localview()

		# Reset variables
		self.CutMode = False
		self.mouse_path.clear()
		self.mouse_path = [(0, 0), (0, 0)]

		self.ForceRebool = False

		# bpy.ops.mesh.customdata_custom_splitnormals_clear()


class CarverProperties(bpy.types.PropertyGroup):
	DepthCursor: BoolProperty(
		name="DepthCursor",
		default=False
	)
	OInstanciate: BoolProperty(
		name="Obj_Instantiate",
		default=False
	)
	ORandom: BoolProperty(
		name="Random_Rotation",
		default=False
	)
	DontApply: BoolProperty(
		name="Dont_Apply",
		default=False
	)
	nProfile: IntProperty(
		name="Num_Profile",
		default=0
	)


def register():
	from bpy.utils import register_class
	bpy.utils.register_class(CARVER_OT_operator)
	bpy.utils.register_class(CarverProperties)
	bpy.types.Scene.mesh_carver = bpy.props.PointerProperty(type=CarverProperties)

def unregister():
	from bpy.utils import unregister_class
	bpy.utils.unregister_class(CarverProperties)
	bpy.utils.unregister_class(CARVER_OT_operator)
	del bpy.types.Scene.mesh_carver
