# -*- coding: utf-8 -*-
from PyQt5 import QtCore, QtGui, uic, QtWidgets
import math
import hashlib
import re

ITEM_UNKNOWN = 0
ITEM_VARIABLE = 1
ITEM_CLASS = 2
ITEM_FUNCTION = 3

def name2color(name):
	hashVal = int(hashlib.md5(name.encode("utf8")).hexdigest(),16) & 0xffffffff
	h = (hashVal & 0xff) / 255.0
	s = ((hashVal >> 8) & 0xff) / 255.0
	l = ((hashVal >> 16)& 0xff) / 255.0
	return QtGui.QColor.fromHslF(h, 0.35+s*0.3, 0.4+l*0.15)

class CodeUIItem(QtWidgets.QGraphicsItem):
	def __init__(self, uniqueName, parent = None, scene = None):
		super(CodeUIItem, self).__init__(parent)
		self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
		self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable)
		self.setFlag(QtWidgets.QGraphicsItem.ItemIsFocusable)
		self.setAcceptDrops(True)
		self.setAcceptHoverEvents(True)
		self.uniqueName = uniqueName
		from db.DBManager import DBManager
		from UIManager import UIManager
		scene = UIManager.instance().getScene()
		dbObj = DBManager.instance().getDB()
		entity = dbObj.searchFromUniqueName(self.uniqueName)
		self.name = ''
		self.displayName = ''
		self.lines = 0
		self.kindName = ''
		self.kind = ITEM_UNKNOWN
		self.titleFont = QtGui.QFont('tahoma', 8)
		self.fontSize = QtCore.QSize()
		self.commentSize = QtCore.QSize()
		self.lineHeight = 0
		self.isConnectedToFocusNode = False
		if entity:
			self.setToolTip(entity.longname())
			self.name = entity.name()
			self.buildDisplayName(self.name)
			comment = scene.itemDataDict.get(self.uniqueName, {}).get('comment','')
			self.buildCommentSize(comment)
			self.kindName = entity.kindname()
			metricRes = entity.metric(('CountLine',))
			metricLine = metricRes.get('CountLine',1)
			if metricLine:
				self.lines = metricLine

		kindStr = self.kindName.lower()
		# 自定义数据
		self.customData = {}

		if kindStr.find('function') != -1 or kindStr.find('method') != -1:
			self.kind = ITEM_FUNCTION
			# 找出调用者和被调用者数目
			callerList = dbObj.searchRefEntity(self.uniqueName, 'callby','function, method', True)[0]
			calleeList = dbObj.searchRefEntity(self.uniqueName, 'call','function, method', True)[0]
			self.customData['nCaller'] = len(callerList)
			self.customData['nCallee'] = len(calleeList)
			self.customData['callerR'] = self.getCallerRadius(len(callerList))
			self.customData['calleeR'] = self.getCallerRadius(len(calleeList))
		elif kindStr.find('attribute') != -1 or kindStr.find('variable') != -1 or kindStr.find('object') != -1:
			self.kind = ITEM_VARIABLE
			self.color = QtGui.QColor(255,198,217)
		elif kindStr.find('class') != -1 or kindStr.find('struct') != -1:
			self.kind = ITEM_CLASS
			self.color = QtGui.QColor(154,177,209)
		else:
			self.kind = ITEM_UNKNOWN
			self.color = QtGui.QColor(195,195,195)

		if self.kind == ITEM_FUNCTION or self.kind == ITEM_VARIABLE:
			if not entity:
				self.color = QtGui.QColor(190,228,73)
			else:
				defineList, defineRefList = dbObj.searchRefEntity(uniqueName, 'definein')
				name = ''
				hasDefinition = True
				if not defineList:
					defineList, defineRefList = dbObj.searchRefEntity(uniqueName, 'declarein')
					hasDefinition = False
				# if 'pure' in self.kindName:
				# 	hasDefinition = False
				self.customData['hasDef'] = hasDefinition
				if defineList:
					declareEnt = defineList[0]
					if declareEnt.kindname().lower().find('class') != -1 or \
						declareEnt.kindname().lower().find('struct') != -1:
						name = declareEnt.name()
						self.customData['className'] = name
				self.color = name2color(name)
		elif self.kind == ITEM_CLASS:
			self.color = name2color(self.name)

		self.displayScore = 0
		self.targetPos = self.pos()	# 用于动画目标
		self.isHover = False
		self.selectCounter = 0
		self.selectTimeStamp = 0

	def getColor(self):
		return self.color

	def getClassName(self):
		if self.kind == ITEM_CLASS:
			return self.name
		return self.customData.get('className','')

	def buildDisplayName(self, name):
		p = re.compile(r'([A-Z]*[a-z0-9]*_*~*)')
		nameList = p.findall(name)
		partLength = 0
		self.displayName = ''
		fontMetrics = QtGui.QFontMetricsF(self.titleFont)
		for i, part in enumerate(nameList):
			self.displayName += part
			partLength += len(part)
			if partLength > 13:
				self.displayName += '\n'
				partLength = 0
		self.displayName = self.displayName.strip()
		nLine = self.displayName.count('\n')+1
		self.fontSize = fontMetrics.size(QtCore.Qt.TextSingleLine, self.name)
		self.lineHeight = fontMetrics.height()
		self.fontSize.setHeight((fontMetrics.lineSpacing()*nLine - fontMetrics.leading()))

	def buildCommentSize(self, comment):
		if not comment:
			self.commentSize = QtCore.QSize()
			return

		fontMetrics = QtGui.QFontMetricsF(self.titleFont)
		lineHeight = fontMetrics.lineSpacing()
		width = fontMetrics.width(comment)
		lines = math.ceil(width/100)
		self.commentSize = QtCore.QSize(100, (fontMetrics.lineSpacing()*lines - fontMetrics.leading()))

	def isFunction(self):
		return self.kind == ITEM_FUNCTION

	def setTargetPos(self, pos):
		self.targetPos = pos

	def dispToTarget(self):
		return self.targetPos - self.pos()

	def moveToTarget(self, ratio):
		self.setPos(self.pos()* (1.0-ratio) + self.targetPos * ratio)

	def getKind(self):
		return self.kind

	def getUniqueName(self):
		return self.uniqueName

	def getEntity(self):
		from db.DBManager import DBManager
		return DBManager.instance().getDB().searchFromUniqueName(self.uniqueName)

	def getRadius(self):
		r = 8
		if self.kind != ITEM_VARIABLE:
			r = math.pow(float(self.lines+1), 0.3) * 5.0
		if self.isFunction():
			r = max(r, self.customData['callerR'] * 0.4, self.customData['calleeR'] * 0.4)
		return r

	def getBodyRadius(self):
		r = 8
		if self.kind != ITEM_VARIABLE:
			r = math.pow(float(self.lines+1), 0.3) * 5.0
		return r

	def getHeight(self):
		h = (self.fontSize.height() + self.commentSize.height())*1.67
		return h

	def getLeftSlotPos(self):
		l = self.getBodyRadius()
		if self.isFunction():
			l += self.customData['callerR']
		return self.pos() + QtCore.QPointF(-l, 0)

	def getRightSlotPos(self):
		l = self.getBodyRadius()
		if self.isFunction():
			l += self.customData['calleeR']
		return self.pos() + QtCore.QPointF(l, 0)

	def boundingRect(self):
		r = self.getBodyRadius()
		adj = r * 2 # 10
		if self.isFunction():
			adj = max(self.customData['callerR'], self.customData['calleeR'], adj)
		return QtCore.QRectF(-r-adj, -r-adj, r*2 + adj*2, r*2 + adj*2)

	def shape(self):
		r = self.getBodyRadius()
		path = QtGui.QPainterPath()
		path.addEllipse(-r,-r,r*2,r*2)
		return path

	def getCallerRadius(self, num):
		return math.log2(float(num+1.0)) * 5.0

	def paint(self, painter, styleOptionGraphicsItem, widget_widget=None):
		#super(CodeUIItem, self).paint(painter, styleOptionGraphicsItem, widget_widget)

		painter.setRenderHint(QtGui.QPainter.Antialiasing)
		painter.setRenderHint(QtGui.QPainter.TextAntialiasing)
		r = self.getBodyRadius()
 
		trans = painter.worldTransform()
		lod = QtWidgets.QStyleOptionGraphicsItem().levelOfDetailFromTransform(trans)

		selectedOrHover = self.isSelected() or self.isHover

		if r * lod > 1.0:
			if selectedOrHover:
				pen = QtGui.QPen(QtGui.QBrush(QtGui.QColor(255,157,38,255)), 20.0)#, QtCore.Qt.SolidLine, QtCore.Qt.SquareCap, QtCore.Qt.RoundJoin)
				painter.setPen(pen)
				self.drawShape(painter)

			painter.setPen(QtCore.Qt.NoPen)

			clr = self.color
			if self.isFunction():
				painter.setBrush(clr)
				nCaller = self.customData.get('nCaller', 0)
				nCallee = self.customData.get('nCallee', 0)
				if nCaller > 0:
					cr = self.customData['callerR']
					painter.drawPie(-r-cr, -cr, cr*2, cr*2, 160*16, 40*16)
				if nCallee > 0:
					cr = self.customData['calleeR']
					painter.drawPie(r-cr, -cr, cr*2, cr*2, -20*16, 40*16)

			clr = self.color
			painter.setBrush(clr)
			self.drawShape(painter)

			if self.kind == ITEM_FUNCTION and (self.lines == 0 or self.customData.get('hasDef') == False):
				painter.setBrush(QtGui.QColor(50,50,50,255))
				painter.setPen(QtCore.Qt.NoPen)
				painter.drawEllipse(QtCore.QPointF(0,0),2.5,2.5)

		if r * lod > 2 or selectedOrHover:
			painter.scale(1.0/lod, 1.0/lod)
			painter.setPen(QtGui.QPen())
			painter.setFont(self.titleFont)
			if self.kind == ITEM_VARIABLE:
				rect = QtCore.QRectF(r, self.lineHeight*-0.5, self.fontSize.width(), self.fontSize.height())
			else:
				rect = QtCore.QRectF(0, 0, self.fontSize.width(), self.fontSize.height())

			dx = 1.0
			painter.setPen(QtGui.QPen(QtGui.QColor(50,50,50)))
			rect0 = rect.translated(dx,dx)
			painter.drawText(rect0, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop, self.displayName)

			painter.setPen(QtGui.QPen(QtGui.QColor(255,239,183)))
			painter.drawText(rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop, self.displayName)

			scene = self.scene()
			commentData = scene.itemDataDict.get(self.uniqueName, {}).get('comment')
			if commentData:
				painter.setPen(QtGui.QPen(QtGui.QColor(166,241,27)))
				rect.moveTop(rect.bottom())
				rect.setSize(QtCore.QSizeF(100,500))
				painter.drawText(rect, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop | QtCore.Qt.TextWordWrap, commentData)

	def drawShape(self, painter):
		r = self.getBodyRadius()
		if self.kind == ITEM_FUNCTION:
			painter.drawEllipse(-r,-r,r*2,r*2)
		elif self.kind == ITEM_VARIABLE:
			painter.drawPolygon(QtCore.QPoint(-r,0), QtCore.QPoint(r,-r), QtCore.QPoint(r,r))
		elif self.kind == ITEM_CLASS:
			painter.drawRect(-r,-r,r*2,r*2)
		else:
			painter.drawEllipse(-r,-r,r*2,r*2)

	def contextMenuEvent(self, event):
		from UIManager import UIManager
		itemMenu = UIManager.instance().getMainUI().getItemMenu()
		itemMenu.exec(event.screenPos())

	def mousePressEvent(self, event):
		super(CodeUIItem, self).mousePressEvent(event)
		self.displayScore += 1
		from UIManager import UIManager
		scene = UIManager.instance().getScene()
		# if scene:
		# 	scene.autoFocus = False

		# if event.button() == QtCore.Qt.MidButton:
		# 	self.setCursor(QtCore.Qt.ClosedHandCursor)

	def mouseReleaseEvent(self, event):
		super(CodeUIItem, self).mouseReleaseEvent(event)
		from UIManager import UIManager
		scene = UIManager.instance().getScene()
		# if scene:
		# 	scene.autoFocus = True

		# if event.button() == QtCore.Qt.MidButton:
		# 	self.setCursor(QtCore.Qt.OpenHandCursor)

	def mouseDoubleClickEvent(self, event):
		super(CodeUIItem, self).mouseDoubleClickEvent(event)

		from UIManager import UIManager
		scene = UIManager.instance().getScene()
		if scene:
			scene.showInEditor()


	def mouseMoveEvent(self, event):
		super(CodeUIItem, self).mouseMoveEvent(event)
		if self.isSelected():
			# update target positions of all dragging items
			from UIManager import UIManager
			scene = UIManager.instance().getScene()
			for uname, node in scene.itemDict.items():
				if node.isSelected():
					node.targetPos = QtCore.QPointF(node.pos().x(), node.pos().y())

		if event.buttons().__int__() & QtCore.Qt.MidButton or event.buttons().__int__() & QtCore.Qt.RightButton:
			print('event button:', event.buttons().__int__())
			drag = QtWidgets.QDrag(event.widget())
			mime = QtCore.QMimeData()
			mime.setText(self.uniqueName)
			drag.setMimeData(mime)
			drag.exec()
			#self.setCursor(QtCore.Qt.OpenHandCursor)

	def hoverLeaveEvent(self, QGraphicsSceneHoverEvent):
		super(CodeUIItem, self).hoverLeaveEvent(QGraphicsSceneHoverEvent)
		self.isHover = False

	def hoverEnterEvent(self, QGraphicsSceneHoverEvent):
		super(CodeUIItem, self).hoverEnterEvent(QGraphicsSceneHoverEvent)
		self.isHover = True

	def dragEnterEvent(self, event):
		event.setAccepted(True)

	def dropEvent(self, event):
		super(CodeUIItem, self).dropEvent(event)
		from UIManager import UIManager
		scene = UIManager.instance().getScene()
		if not scene:
			return

		mouseButtons = event.buttons()
		if mouseButtons & QtCore.Qt.MiddleButton:
			srcName = event.mimeData().text()
			srcItem = scene.getNode(srcName)
			if not srcItem:
				return
			if not srcItem.isFunction() or not self.isFunction():
				return
			scene.addCallPaths(srcName, self.uniqueName)
		elif mouseButtons & QtCore.Qt.RightButton:
			srcName = event.mimeData().text()
			srcItem = scene.getNode(srcName)
			if not srcItem:
				return
			scene.addCustomEdge(srcName, self.uniqueName, {})

if __name__ == "__main__":
	import re
	p = re.compile(r'([A-Z]*[a-z0-9]*_*)')
	s = 'aa_bbAbbbAAbbb_aa_123__'
	print(p.findall(s))