'''
    SPDX-FileCopyrightText: 2025 Agata Cacko <cacko.azh@gmail.com>

    This file is part of Fast Sketch Cleanup Plugin for Krita

    SPDX-License-Identifier: GPL-3.0-or-later
'''


from PyQt5.QtCore import QPointF, QRectF, pyqtSignal, Qt
from PyQt5.QtGui import QBrush, QLinearGradient, QPainter, QPainterPath, QPen, QPolygonF
from PyQt5.QtWidgets import QAbstractSlider, qApp
from numpy import sqrt


class LevelsWidget(QAbstractSlider):

	lowerValue = 0.0
	upperValue = 1.0


	grabbedHandle = -1



	# widget painting constants:
	triangleWidth = 10
	triangleHeight = 15
	sliderHeight = 20
	#triangleLowerBoundMargin = 2
	verticalMargins = 5
	verticalMarginForText = 25
	minimumTextWidth = 50
	#textRectangle = QRectF(0, 0, minimumTextWidth, verticalMarginForText)
	
	# other constants:
	closeToHandleMargin = 20
	noHandleId = -1
	lowerHandleId = 1
	upperHandleId = 2

	# updated on painting
	lowerValueHandleLocation : QPointF = QPointF()
	upperValueHandleLocation : QPointF = QPointF()
	lowerRangeEndX : int = 0
	upperRangeEndX : int = 100
	
	
	changedValues = pyqtSignal(float, float)


	def __init__(self, parent = None):
		super().__init__(parent)
		height = self.sliderHeight + self.triangleHeight + 2*self.verticalMargins + self.verticalMarginForText
		self.setMinimumHeight(height)

	def paintTriangle(self, painter: QPainter, location: QPointF, width: float, height: float, lineColor, fillColor):
		triangle = QPainterPath()
		polygon = QPolygonF()
		location = location + QPointF(0, height)

		polygon.append(QPointF(location.x() - width/2, location.y()))
		polygon.append(QPointF(location.x(), location.y() - height))
		polygon.append(QPointF(location.x() + width/2, location.y()))
		polygon.append(QPointF(location.x() - width/2, location.y()))
		
		triangle.addPolygon(polygon)
		painter.fillPath(triangle, fillColor)
		painter.strokePath(triangle, lineColor)

	def clipToRange(self, value, minV, maxV):
		value = min(value, maxV)
		value = max(value, minV)
		return value
	
	def idealTextLocation(self, handleLocationX, sliderRect, left):
		if left:
			return self.clipToRange(handleLocationX, sliderRect.x() + self.minimumTextWidth/2, sliderRect.right() - 1.5*self.minimumTextWidth)
		else:
			return self.clipToRange(handleLocationX, sliderRect.x() + 1.5*self.minimumTextWidth, sliderRect.right() - 0.5*self.minimumTextWidth)


	def paintText(self, painter: QPainter, handleLocation: QPointF, value: float, textColor, left: bool, sliderRect: QRectF, theOtherHandleLocation):
		x = self.idealTextLocation(handleLocation.x(), sliderRect, left)
		otherX = self.idealTextLocation(theOtherHandleLocation.x(), sliderRect, not left)

		diff = abs(x - otherX)
		if diff < self.minimumTextWidth:
			if (left):
				x = x - (self.minimumTextWidth/2 - diff/2)
			else:
				x = x + (self.minimumTextWidth/2 - diff/2)
		
		bottomMiddle = QPointF(x, handleLocation.y() - sliderRect.height())
		rectangle = QRectF(bottomMiddle.x() - self.minimumTextWidth/2, bottomMiddle.y() - self.verticalMarginForText, self.minimumTextWidth, self.verticalMarginForText)
		painter.save()
		painter.setPen(textColor)
		painter.drawText(rectangle, Qt.AlignCenter, f"{value:.2f}")
		painter.drawRect(rectangle)
		painter.restore()



	def paintEvent(self, paintEvent):
		
		super().paintEvent(paintEvent)
		painter = QPainter(self)
		painter.save()

		try:
			painter.setRenderHint(QPainter.Antialiasing)

			roundingMargin = 3

			fullRect = self.rect()

			path = QPainterPath()

			sliderWidth = fullRect.width() - 2*self.triangleWidth
			middleVertical = (fullRect.top() - self.triangleHeight + self.verticalMarginForText + fullRect.bottom())/2
			middleHorizontal = (fullRect.right() - fullRect.left())/2
			sliderRect = QRectF(middleHorizontal - sliderWidth/2, middleVertical - self.sliderHeight/2, sliderWidth, self.sliderHeight)
			path.addRoundedRect(sliderRect, roundingMargin, roundingMargin)

			painter.fillPath(path, qApp.palette().base())

			gradient = QLinearGradient(QPointF(0, 0), QPointF(sliderWidth, 0))
			
			gradientBrush = QBrush(gradient)

			upperGradientRect = sliderRect.adjusted(0, 0, 0, -roundingMargin) 
			lowerGradientRect = sliderRect.adjusted(0, self.sliderHeight/2, 0, 0)

			path.clear()
			path.addRoundedRect(upperGradientRect, roundingMargin, roundingMargin)
			painter.fillPath(path, gradientBrush)

			lowerValueSliderRatio = self.lowerValue
			upperValueSliderRatio = self.upperValue

			self.lowerValueHandleLocation = QPointF(middleHorizontal - sliderWidth/2 + sliderWidth*lowerValueSliderRatio, middleVertical + self.sliderHeight/2)
			self.upperValueHandleLocation = QPointF(middleHorizontal - sliderWidth/2 + sliderWidth*upperValueSliderRatio, middleVertical + self.sliderHeight/2)
			
			path.clear()
			gradient = QLinearGradient(QPointF(self.lowerValueHandleLocation.x(), 0), QPointF(self.upperValueHandleLocation.x(), 0))
			if self.lowerValueHandleLocation.x() == self.upperValueHandleLocation.x():
				gradient = QLinearGradient(QPointF(self.lowerValueHandleLocation.x(), 0), QPointF(self.upperValueHandleLocation.x() + 0.5, 0))
			path.addRoundedRect(lowerGradientRect, roundingMargin, roundingMargin)
			gradientBrush = QBrush(gradient)
			painter.fillPath(path, gradientBrush)

			path.clear()
			path.addRoundedRect(sliderRect, roundingMargin, roundingMargin)
			pen = QPen(qApp.palette().base(), 1)
			painter.strokePath(path, pen)


			self.paintTriangle(painter, self.lowerValueHandleLocation, self.triangleWidth, self.triangleHeight, qApp.palette().base().color(), qApp.palette().text())
			self.paintTriangle(painter, self.upperValueHandleLocation, self.triangleWidth, self.triangleHeight, qApp.palette().base().color(), qApp.palette().text())
			
			self.paintText(painter, self.lowerValueHandleLocation, self.lowerValue, qApp.palette().text().color(), True, sliderRect, self.upperValueHandleLocation)
			self.paintText(painter, self.upperValueHandleLocation, self.upperValue, qApp.palette().text().color(), False, sliderRect, self.lowerValueHandleLocation)
			
			self.lowerRangeEndX = middleHorizontal - sliderWidth/2
			self.upperRangeEndX = middleHorizontal + sliderWidth/2

		except Exception as e:
			print(f"Exception in Levels Widget: {e}")

		painter.restore()
			
		


	def grabClosestHandle(self, mouseLocation:QPointF):
		def distance(pointF: QPointF):
			return sqrt(pointF.x()*pointF.x() + pointF.y()*pointF.y())
		
		distanceToLowerHandle = distance((mouseLocation - self.lowerValueHandleLocation))
		distanceToUpperHandle = distance((mouseLocation - self.upperValueHandleLocation))

		if (distanceToLowerHandle < self.closeToHandleMargin and distanceToUpperHandle < self.closeToHandleMargin):
			# which is closer
			if (distanceToLowerHandle < distanceToUpperHandle):
				return self.lowerHandleId
			else:
				return self.upperHandleId
		
		if (distanceToLowerHandle < self.closeToHandleMargin):
			return self.lowerHandleId
		
		if (distanceToUpperHandle < self.closeToHandleMargin):
			return self.upperHandleId
		
		return self.noHandleId
		

		


		
	def mousePressEvent(self, mouseEvent):
		closestHandle = self.grabClosestHandle(mouseEvent.pos())
		if closestHandle == self.noHandleId:
			return
		self.grabbedHandle = closestHandle

	def mouseMoveEvent(self, mouseEvent):
		if self.grabbedHandle != self.noHandleId:
			value = 0
			distanceFromStart = mouseEvent.pos().x() - self.lowerRangeEndX
			if distanceFromStart <= 0:
				value = 0
			else:
				# TODO: make it universal, with different range than 0-1
				value = distanceFromStart/(self.upperRangeEndX - self.lowerRangeEndX)
				if value > 1.0:
					value = 1.0
			
			if self.grabbedHandle == self.lowerHandleId:
				self.lowerValue = value
				if (self.upperValue < self.lowerValue):
					self.upperValue = value
			
			elif self.grabbedHandle == self.upperHandleId:
				self.upperValue = value
				if (self.upperValue < self.lowerValue):
					self.lowerValue = value
		
			self.repaint()



	def mouseReleaseEvent(self, mouseEvent):

		self.grabbedHandle = self.noHandleId
		self.changedValues.emit(self.lowerValue, self.upperValue)
	


	def setValues(self, lower, upper) -> None:
		if lower < 0.0:
			lower = 0.0
		if upper > 1.0:
			upper = 1.0
		if lower > upper:
			lower = upper
		self.lowerValue = lower
		self.upperValue = upper

