import bpy
import bgl
import blf
import bpy_extras
import numpy as np
import gpu
from gpu_extras.batch import batch_for_shader
from math import(
	cos,
	sin,
	ceil,
	floor,
	)

from bpy_extras.view3d_utils import (
	region_2d_to_location_3d,
	location_3d_to_region_2d,
)

from .carver_utils import (
	draw_circle,
	draw_shader,
	objDiagonal,
	mini_grid,
	)

from mathutils import (
	Color,
	Euler,
	Vector,
	Quaternion,
)

def get_text_info(self, context, help_txt):
	""" Return the dimensions of each part of the text """

	#Extract the longest first option in sublist
	max_option = max(list(blf.dimensions(0, row[0])[0] for row in help_txt))

	#Extract the longest key in sublist
	max_key = max(list(blf.dimensions(0, row[1])[0] for row in help_txt))

	#Space between option and key  with a comma separator (" : ")
	comma = blf.dimensions(0, "_:_")[0]

	#Get a default height for all the letters
	line_height = (blf.dimensions(0, "gM")[1] * 1.45)

	#Get the total height of the text
	bloc_height = 0
	for row in help_txt:
		bloc_height += line_height

	return(help_txt, bloc_height, max_option, max_key, comma)

def draw_string(self, color1, color2, left, bottom, text, max_option, divide = 1):
	""" Draw the text like 'option : key' or just 'option' """

	font_id = 0
	ui_scale = bpy.context.preferences.system.ui_scale

	blf.enable(font_id,blf.SHADOW)
	blf.shadow(font_id, 0, 0.0, 0.0, 0.0, 1.0)
	blf.shadow_offset(font_id,2,-2)
	line_height = (blf.dimensions(font_id, "gM")[1] * 1.45)
	y_offset = 5

	# Test if the text is a list formatted like : ('option', 'key')
	if isinstance(text,list):
		spacer_text = " : "
		spacer_width = blf.dimensions(font_id, spacer_text)[0]
		for string in text:
			blf.position(font_id, (left), (bottom + y_offset), 0)
			blf.color(font_id, *color1)
			blf.draw(font_id, string[0])
			blf.position(font_id, (left + max_option), (bottom + y_offset), 0)
			blf.draw(font_id, spacer_text)
			blf.color(font_id, *color2)
			blf.position(font_id, (left + max_option + spacer_width), (bottom + y_offset), 0)
			blf.draw(font_id, string[1])
			y_offset += line_height
	else:
		# The text is formatted like : ('option')
		blf.position(font_id, left, (bottom + y_offset), 0)
		blf.color(font_id, *color1)
		blf.draw(font_id, text)
		y_offset += line_height

	blf.disable(font_id,blf.SHADOW)

# Opengl draw on screen
def draw_callback_px(self, context):
	font_id = 0
	region = context.region
	UIColor = (0.992, 0.5518, 0.0, 1.0)

	# Cut Type
	RECTANGLE = 0
	LINE = 1
	CIRCLE = 2
	self.carver_prefs = context.preferences.addons[__package__].preferences

	# Color
	color1 = (1.0, 1.0, 1.0, 1.0)
	color2 = UIColor

	#The mouse is outside the active region
	if not self.in_view_3d:
		color1 = color2 = (1.0, 0.2, 0.1, 1.0)

	# Primitives type
	PrimitiveType = "Rectangle"
	if self.CutType == CIRCLE:
		PrimitiveType = "Circle"
	if self.CutType == LINE:
		PrimitiveType = "Line"

	# Width screen
	overlap = context.preferences.system.use_region_overlap

	t_panel_width = 0
	if overlap:
		for region in context.area.regions:
			if region.type == 'TOOLS':
				t_panel_width = region.width

	# Initial position
	region_width = int(region.width / 2.0)
	y_txt = 10


	# Draw the center command from bottom to top

	# Get the size of the text
	text_size = 18 if region.width >= 850 else 12
	ui_scale = bpy.context.preferences.system.ui_scale
	blf.size(0, round(text_size * ui_scale), 72)

	# Help Display
	if (self.ObjectMode is False) and (self.ProfileMode is False):

		# Depth Cursor
		TypeStr = "Cursor Depth [" + self.carver_prefs.Key_Depth + "]"
		BoolStr = "(ON)" if self.snapCursor else "(OFF)"
		help_txt = [[TypeStr, BoolStr]]

		# Close poygonal shape
		if self.CreateMode and self.CutType == LINE:
			TypeStr = "Close [" + self.carver_prefs.Key_Close + "]"
			BoolStr = "(ON)" if self.Closed else "(OFF)"
			help_txt += [[TypeStr, BoolStr]]

		if self.CreateMode is False:
			# Apply Booleans
			TypeStr = "Apply Operations [" + self.carver_prefs.Key_Apply + "]"
			BoolStr = "(OFF)" if self.dont_apply_boolean else "(ON)"
			help_txt += [[TypeStr, BoolStr]]

			#Auto update for bevel
			TypeStr = "Bevel Update [" + self.carver_prefs.Key_Update + "]"
			BoolStr = "(ON)" if self.Auto_BevelUpdate else "(OFF)"
			help_txt += [[TypeStr, BoolStr]]

		# Circle subdivisions
		if self.CutType == CIRCLE:
			TypeStr = "Subdivisions [" + self.carver_prefs.Key_Subrem + "][" + self.carver_prefs.Key_Subadd + "]"
			BoolStr = str((int(360 / self.stepAngle[self.step])))
			help_txt += [[TypeStr, BoolStr]]

		if self.CreateMode:
			help_txt += [["Type [Space]", PrimitiveType]]
		else:
			help_txt += [["Cut Type [Space]", PrimitiveType]]

	else:
		# Instantiate
		TypeStr = "Instantiate [" + self.carver_prefs.Key_Instant + "]"
		BoolStr = "(ON)" if self.Instantiate else "(OFF)"
		help_txt = [[TypeStr, BoolStr]]

		# Random rotation
		if self.alt:
			TypeStr = "Random Rotation [" + self.carver_prefs.Key_Randrot + "]"
			BoolStr = "(ON)" if self.RandomRotation else "(OFF)"
			help_txt += [[TypeStr, BoolStr]]

		# Thickness
		if self.BrushSolidify:
			TypeStr = "Thickness [" + self.carver_prefs.Key_Depth + "]"
			if self.ProfileMode:
				BoolStr = str(round(self.ProfileBrush.modifiers["CT_SOLIDIFY"].thickness, 2))
			if self.ObjectMode:
				BoolStr = str(round(self.ObjectBrush.modifiers["CT_SOLIDIFY"].thickness, 2))
			help_txt += [[TypeStr, BoolStr]]

		# Brush depth
		if (self.ObjectMode):
			TypeStr = "Carve Depth [" + self.carver_prefs.Key_Depth + "]"
			BoolStr = str(round(self.ObjectBrush.data.vertices[0].co.z, 2))
			help_txt += [[TypeStr, BoolStr]]

			TypeStr = "Brush Depth [" + self.carver_prefs.Key_BrushDepth + "]"
			BoolStr = str(round(self.BrushDepthOffset, 2))
			help_txt += [[TypeStr, BoolStr]]

	help_txt, bloc_height, max_option, max_key, comma = get_text_info(self, context, help_txt)
	xCmd = region_width - (max_option + max_key + comma) / 2
	draw_string(self, color1, color2, xCmd, y_txt, help_txt, max_option, divide = 2)


	# Separator (Line)
	LineWidth = (max_option + max_key + comma) / 2
	if region.width >= 850:
		LineWidth = 140

	LineWidth = (max_option + max_key + comma)
	coords = [(int(region_width - LineWidth/2), y_txt + bloc_height + 8), \
			  (int(region_width + LineWidth/2), y_txt + bloc_height + 8)]
	draw_shader(self, UIColor, 1, 'LINES', coords, self.carver_prefs.LineWidth)

	# Command Display
	if self.CreateMode and ((self.ObjectMode is False) and (self.ProfileMode is False)):
		BooleanMode = "Create"
	else:
		if self.ObjectMode or self.ProfileMode:
			BooleanType = "Difference) [T]" if self.BoolOps == self.difference else "Union) [T]"
			BooleanMode = \
				"Object Brush (" + BooleanType if self.ObjectMode else "Profil Brush (" + BooleanType
		else:
			BooleanMode = \
				"Difference" if (self.shift is False) and (self.ForceRebool is False) else "Rebool"

	# Display boolean mode
	text_size = 40 if region.width >= 850 else 20
	blf.size(0, round(text_size * ui_scale), 72)

	draw_string(self, color2, color2, region_width - (blf.dimensions(0, BooleanMode)[0]) / 2, \
				y_txt + bloc_height + 16, BooleanMode, 0, divide = 2)

	if region.width >= 850:

		if self.AskHelp is False:
			# "H for Help" text
			blf.size(0, round(13 * ui_scale), 72)
			help_txt = "[" + self.carver_prefs.Key_Help + "] for help"
			txt_width = blf.dimensions(0, help_txt)[0]
			txt_height = (blf.dimensions(0, "gM")[1] * 1.45)

			# Draw a rectangle and put the text "H for Help"
			xrect = 40
			yrect = 40
			rect_vertices = [(xrect - 5, yrect - 5), (xrect + txt_width + 5, yrect - 5), \
							 (xrect + txt_width + 5, yrect + txt_height + 5), (xrect - 5, yrect + txt_height + 5)]
			draw_shader(self, (0.0, 0.0, 0.0),  0.3, 'TRI_FAN', rect_vertices, self.carver_prefs.LineWidth)
			draw_string(self, color1, color2, xrect, yrect, help_txt, 0)

		else:
			#Draw the help text
			xHelp = 30 + t_panel_width
			yHelp = 10

			if self.ObjectMode or self.ProfileMode:
				if self.ProfileMode:
					help_txt = [["Object Mode", self.carver_prefs.Key_Brush]]
				else:
					help_txt = [["Cut Mode", self.carver_prefs.Key_Brush]]

			else:
				help_txt =[
					  ["Profil Brush", self.carver_prefs.Key_Brush],\
					  ["Move Cursor", "Ctrl + LMB"]
					  ]

			if (self.ObjectMode is False) and (self.ProfileMode is False):
				if self.CreateMode is False:
					help_txt +=[
						   ["Create geometry", self.carver_prefs.Key_Create],\
						   ]
				else:
					help_txt +=[
						   ["Cut", self.carver_prefs.Key_Create],\
						   ]
				if self.CutMode == RECTANGLE:
					help_txt +=[
						   ["Dimension", "MouseMove"],\
						   ["Move all", "Alt"],\
						   ["Validate", "LMB"],\
						   ["Rebool", "Shift"]
						   ]

				elif self.CutMode == CIRCLE:
					help_txt +=[
						   ["Rotation and Radius", "MouseMove"],\
						   ["Move all", "Alt"],\
						   ["Subdivision", self.carver_prefs.Key_Subrem + " " + self.carver_prefs.Key_Subadd],\
						   ["Incremental rotation", "Ctrl"],\
						   ["Rebool", "Shift"]
						   ]

				elif self.CutMode == LINE:
					help_txt +=[
						   ["Dimension", "MouseMove"],\
						   ["Move all", "Alt"],\
						   ["Validate", "Space"],\
						   ["Rebool", "Shift"],\
						   ["Snap", "Ctrl"],\
						   ["Scale Snap", "WheelMouse"],\
						   ]
			else:
				# ObjectMode
				help_txt +=[
					   ["Difference", "Space"],\
					   ["Rebool", "Shift + Space"],\
					   ["Duplicate", "Alt + Space"],\
					   ["Scale", self.carver_prefs.Key_Scale],\
					   ["Rotation", "LMB + Move"],\
					   ["Step Angle", "CTRL + LMB + Move"],\
					   ]

				if self.ProfileMode:
					help_txt +=[["Previous or Next Profile", self.carver_prefs.Key_Subadd + " " + self.carver_prefs.Key_Subrem]]

				help_txt +=[
					   ["Create / Delete rows",  chr(8597)],\
					   ["Create / Delete cols",  chr(8596)],\
					   ["Gap for rows or columns",  self.carver_prefs.Key_Gapy + " " + self.carver_prefs.Key_Gapx]
					   ]

			blf.size(0, round(15 * ui_scale), 72)
			help_txt, bloc_height, max_option, max_key, comma = get_text_info(self, context, help_txt)
			draw_string(self, color1, color2, xHelp, yHelp, help_txt, max_option)

	if self.ProfileMode:
		xrect = region.width - t_panel_width - 80
		yrect = 80
		coords = [(xrect, yrect), (xrect+60, yrect), (xrect+60, yrect-60), (xrect, yrect-60)]

		# Draw rectangle background in the lower right
		draw_shader(self, (0.0, 0.0, 0.0),  0.3, 'TRI_FAN', coords, size=self.carver_prefs.LineWidth)

		# Use numpy to get the vertices and indices of the profile object to draw
		WidthProfil = 50
		location = Vector((region.width - t_panel_width - WidthProfil, 50, 0))
		ProfilScale = 20.0
		coords = []
		mesh = bpy.data.meshes[self.Profils[self.nProfil][0]]
		mesh.calc_loop_triangles()
		vertices = np.empty((len(mesh.vertices), 3), 'f')
		indices = np.empty((len(mesh.loop_triangles), 3), 'i')
		mesh.vertices.foreach_get("co", np.reshape(vertices, len(mesh.vertices) * 3))
		mesh.loop_triangles.foreach_get("vertices", np.reshape(indices, len(mesh.loop_triangles) * 3))

		for idx, vals in enumerate(vertices):
			coords.append([
			vals[0] * ProfilScale + location.x,
			vals[1] * ProfilScale + location.y,
			vals[2] * ProfilScale + location.z
			])

		#Draw the silhouette of the mesh
		draw_shader(self, UIColor,  0.5, 'TRIS', coords, size=self.carver_prefs.LineWidth, indices=indices)


	if self.CutMode:

		if len(self.mouse_path) > 1:
			x0 = self.mouse_path[0][0]
			y0 = self.mouse_path[0][1]
			x1 = self.mouse_path[1][0]
			y1 = self.mouse_path[1][1]

		# Cut rectangle
		if self.CutType == RECTANGLE:
			coords = [
			(x0 + self.xpos, y0 + self.ypos), (x1 + self.xpos, y0 + self.ypos), \
			(x1 + self.xpos, y1 + self.ypos), (x0 + self.xpos, y1 + self.ypos)
			]
			indices = ((0, 1, 2), (2, 0, 3))

			self.rectangle_coord = coords

			draw_shader(self, UIColor, 1, 'LINE_LOOP', coords, size=self.carver_prefs.LineWidth)

			#Draw points
			draw_shader(self, UIColor, 1, 'POINTS', coords, size=3)

			if self.shift or self.CreateMode:
				draw_shader(self, UIColor, 0.5, 'TRIS', coords, size=self.carver_prefs.LineWidth, indices=indices)

			# Draw grid (based on the overlay options) to show the incremental snapping
			if self.ctrl:
				mini_grid(self, context, UIColor)

		# Cut Line
		elif self.CutType == LINE:
			coords = []
			indices = []
			top_grid = False

			for idx, vals in enumerate(self.mouse_path):
				coords.append([vals[0] + self.xpos, vals[1] + self.ypos])
				indices.append([idx])

			# Draw lines
			if self.Closed:
				draw_shader(self, UIColor, 1.0, 'LINE_LOOP', coords, size=self.carver_prefs.LineWidth)
			else:
				draw_shader(self, UIColor, 1.0, 'LINE_STRIP', coords, size=self.carver_prefs.LineWidth)

			# Draw points
			draw_shader(self, UIColor, 1.0, 'POINTS', coords, size=3)

			# Draw polygon
			if (self.shift) or (self.CreateMode and self.Closed):
				draw_shader(self, UIColor, 0.5, 'TRI_FAN', coords, size=self.carver_prefs.LineWidth)

			# Draw grid (based on the overlay options) to show the incremental snapping
			if self.ctrl:
				mini_grid(self, context, UIColor)

		# Circle Cut
		elif self.CutType == CIRCLE:
			# Create a circle using a tri fan
			tris_coords, indices = draw_circle(self, x0, y0)

			# Remove the vertex in the center to get the outer line of the circle
			line_coords = tris_coords[1:]
			draw_shader(self, UIColor, 1.0, 'LINE_LOOP', line_coords, size=self.carver_prefs.LineWidth)

			if self.shift or self.CreateMode:
				draw_shader(self, UIColor, 0.5, 'TRIS', tris_coords, size=self.carver_prefs.LineWidth, indices=indices)

	if (self.ObjectMode or self.ProfileMode) and len(self.CurrentSelection) > 0:
		if self.ShowCursor:
			region = context.region
			rv3d = context.space_data.region_3d

			if self.ObjectMode:
				ob = self.ObjectBrush
			if self.ProfileMode:
				ob = self.ProfileBrush
			mat = ob.matrix_world

			# 50% alpha, 2 pixel width line
			bgl.glEnable(bgl.GL_BLEND)

			bbox = [mat @ Vector(b) for b in ob.bound_box]
			objBBDiagonal = objDiagonal(self.CurrentSelection[0])

			if self.shift:
				gl_size = 4
				UIColor = (0.5, 1.0, 0.0, 1.0)
			else:
				gl_size = 2
				UIColor = (1.0, 0.8, 0.0, 1.0)

			line_coords = []
			idx = 0
			CRadius = ((bbox[7] - bbox[0]).length) / 2
			for i in range(int(len(self.CLR_C) / 3)):
				vector3d = (self.CLR_C[idx * 3] * CRadius + self.CurLoc.x, \
							self.CLR_C[idx * 3 + 1] * CRadius + self.CurLoc.y, \
							self.CLR_C[idx * 3 + 2] * CRadius + self.CurLoc.z)
				vector2d = bpy_extras.view3d_utils.location_3d_to_region_2d(region, rv3d, vector3d)
				if vector2d is not None:
					line_coords.append((vector2d[0], vector2d[1]))
				idx += 1
			if len(line_coords) > 0 :
				draw_shader(self, UIColor, 1.0, 'LINE_LOOP', line_coords, size=gl_size)

			# Object display
			if self.quat_rot is not None:
				ob.location = self.CurLoc
				v = Vector()
				v.x = v.y = 0.0
				v.z = self.BrushDepthOffset
				ob.location += self.quat_rot @ v

				e = Euler()
				e.x = 0.0
				e.y = 0.0
				e.z = self.aRotZ / 25.0

				qe = e.to_quaternion()
				qRot = self.quat_rot @ qe
				ob.rotation_mode = 'QUATERNION'
				ob.rotation_quaternion = qRot
				ob.rotation_mode = 'XYZ'

				if self.ProfileMode:
					if self.ProfileBrush is not None:
						self.ProfileBrush.location = self.CurLoc
						self.ProfileBrush.rotation_mode = 'QUATERNION'
						self.ProfileBrush.rotation_quaternion = qRot
						self.ProfileBrush.rotation_mode = 'XYZ'

	# Opengl defaults
	bgl.glLineWidth(1)
	bgl.glDisable(bgl.GL_BLEND)
