import math
import FreeCADGui as Gui
import FreeCAD as App

import ahb_utils

class AHB_View_Annotate:
	def GetResources(self):
		return {"MenuText": "Annotate view",
				"ToolTip": "Annotates a TechDraw view with object names",
				"Pixmap": ""
				}

	def IsActive(self):
		return True

	def Activated(self):
		workbench = Gui.getWorkbench("AssemblyHandbookWorkbench") #: :type workbench: AssemblyHandbookWorkbench
		
		if len(Gui.Selection.getSelection()) == 0:
			page = workbench.techDrawExtensions.getActivePage()
			workbench.techDrawExtensions.refreshOverlays(page)
			return
		
		if len(Gui.Selection.getSelection()) != 1:
			raise Exception("Please select exactly one TechDraw view")

		view = Gui.Selection.getSelection()[0]
		if view.TypeId == 'TechDraw::DrawPage':
			workbench.techDrawExtensions.refreshOverlays(view)
			return
		elif view.TypeId != 'TechDraw::DrawViewPart':
			raise Exception("Selected object is not a TechDraw view")
		
		overlay_view = workbench.techDrawExtensions.getOverlayView(view)
		
		doc = view.Document
		
		page = workbench.techDrawExtensions.getViewPage(view)
		if page is None:
			raise Exception("Can't find page in which the selected view is located")
		
		def list_sub_parts(parts):
			result = []
			for part in parts:
				if part.TypeId == 'App::Link':
					result.append(part)
				elif part.TypeId == 'Part::FeaturePython' and hasattr(part, 'LinkedObject'): # variant link
					result.append(part)
				
				if hasattr(part, 'Group'):
					result.extend(list_sub_parts(part.Group))
			
			return result	
		
		all_parts = list_sub_parts(view.XSource)
		
		# Remove balloons referencing missing objects
		for balloon in page.Views:
			if balloon.TypeId == 'TechDraw::DrawViewBalloon' and "Assembly_handbook_Source" in balloon.PropertiesList:
				if balloon.SourceView != view and balloon.SourceView != overlay_view: continue
				partLink = workbench.techDrawExtensions.getBalloonSourcePart(balloon)
				if partLink is None or partLink not in all_parts:
					ref_name = "<no ref>"
					try:
						ref_name = balloon.Assembly_handbook_Source[1]
					except:
						pass
					print(balloon.Name + " references missing object " + ref_name + ", removing balloon")
					doc.removeObject(balloon.Name)
		
		balloonsCreated = []
		for partLink in view.XSource:
			balloonsCreated.extend(workbench.techDrawExtensions.add_or_update_balloon(view, partLink, ''))
			
		if len(balloonsCreated) > 0:
			regroupedBalloons = self.RegroupNearestSimilarBalloons(balloonsCreated)
			#self.PlaceBalloonsInCircle(regroupedBalloons)
			self.PlaceBalloonsInCircle(balloonsCreated)
	
		workbench.techDrawExtensions.refreshOverlays(page)

		
	def CalculatePointsCenter(self, balloons):
		totalX = 0
		totalY = 0
		for balloon in balloons:
			realBalloon = balloon[0] if type(balloon) is list else balloon
			totalX = totalX + int(realBalloon.OriginX)
			totalY = totalY + int(realBalloon.OriginY)
		return App.Vector(totalX / len(balloons), totalY / len(balloons))

	def IsSimilarBalloonNear(self, balloonA, balloonB):
		MAX_DISTANCE_BETWEEN_REGROUPED_BALLOONS = 50
		if balloonA.Text == balloonB.Text:
			pos = App.Vector(balloonA.OriginX, balloonA.OriginY)
			dist = pos.distanceToPoint(App.Vector(balloonB.OriginX, balloonB.OriginY))
			return dist < MAX_DISTANCE_BETWEEN_REGROUPED_BALLOONS
		else:
			return False

	def RegroupNearestSimilarBalloons(self, balloons):
		regroupedBalloons = []
		for balloon in balloons:
			nearestBalloons = []
			for otherBalloon in balloons:
				if otherBalloon != balloon and self.IsSimilarBalloonNear(balloon, otherBalloon):
					nearestBalloons.append(otherBalloon)
					balloons.remove(otherBalloon)
			if len(nearestBalloons) == 0:
				regroupedBalloons.append(balloon)
			else:
				nearestBalloons.append(balloon)
				regroupedBalloons.append(nearestBalloons)
		return regroupedBalloons

	def PlaceBalloonsInCircle(self, balloons):
		center = self.CalculatePointsCenter(balloons)
		nbBalloons = len(balloons)
		balloonPosStep = (math.pi * 2) / nbBalloons
		for i in range(nbBalloons):
			xPos = round(center.x + 600 * math.cos(balloonPosStep * i))
			yPos = round(center.y + 600 * math.sin(balloonPosStep * i))
			balloonPos = App.Vector(xPos, yPos)
			# Find nearest arrow to avoid arrow crossing each other
			smallestDistance = 0
			balloonToUse = None
			for balloon in balloons:
				realBalloon = balloon[0] if type(balloon) is list else balloon
				dist = balloonPos.distanceToPoint(App.Vector(realBalloon.OriginX, realBalloon.OriginY))
				if smallestDistance == 0 or dist < smallestDistance:
					smallestDistance = dist
					balloonToUse = balloon

			if balloonToUse is not None:
				balloons.remove(balloonToUse)
				if type(balloonToUse) is list:
					for realBalloon in balloonToUse:
						realBalloon.X = balloonPos.x
						realBalloon.Y = balloonPos.y
				else:
					balloonToUse.X = balloonPos.x
					balloonToUse.Y = balloonPos.y

from ahb_command import AHB_CommandWrapper
AHB_CommandWrapper.addGuiCommand('AHB_view_annotate', AHB_View_Annotate())