from PySide6.QtGui import (
	QScreen,
)
from typing import (
	Optional,
	Callable,
	Any,
)


class Monitor:
	def __init__(
		self,
		index: int,
		screen: QScreen,
	):
		"""
		Constructor for the Monitor class.

		Parameters
		----------
		index : int
		    Index of the monitor.
		screen : QScreen
		    QScreen object of the monitor.
		"""
		super().__init__()
		self.index = index
		self.screen = screen

	def capture(
		self,
		save_path: str,
		x: Optional[int] = None,
		y: Optional[int] = None,
		width: Optional[int] = None,
		height: Optional[int] = None,
	):
		"""
		Captures the entire desktop screen.

		Parameters
		----------
		save_path : str
		    Path to save the captured image. If not specified, it will be saved in the current directory.
		x : int, optional
		    x-coordinate of the area to capture. Default is None.
		y : int, optional
		    y-coordinate of the area to capture. Default is None.
		width : int, optional
		    Width of the area to capture. Default is None.
		height : int, optional
		    Height of the area to capture. Default is None.

		Returns
		-------
		str or None
		    Returns the path of the saved image. Returns None if an error occurs.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		save_path = monitor.capture('screenshot.png')
		print(f'Screenshot saved at: {save_path}')
		```
		"""
		try:
			screenshot = self.screen.grabWindow(
				0,
				x,
				y,
				width,
				height,
			)
			screenshot.save(save_path)
			return save_path

		except Exception as e:
			print(f'Error occurred while capturing the desktop: {e}')
			return None

	def info(
		self,
	) -> dict[
		str,
		Any,
	]:
		"""
		Returns information about the monitor.

		Returns
		-------
		dict[str, Any]
		    A dictionary containing monitor information.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		info = monitor.info()
		print(
		    'Monitor Info:',
		    info,
		)
		```

		Output Example
		--------------
		```
		{
		    'index': 0,
		    'name': 'Primary Monitor',
		    'manufacturer': 'Dell',
		    'model': 'U2718Q',
		    'serial_number': 'SN123456789',
		    'x': 0,
		    'y': 0,
		    'width': 2560,
		    'height': 1440,
		    'is_primary': True,
		    'geometry': {
		        'x': 0,
		        'y': 0,
		        'width': 2560,
		        'height': 1440,
		    },
		    'size': {
		        'width': 2560,
		        'height': 1440,
		    },
		    'available_geometry': {
		        'x': 0,
		        'y': 0,
		        'width': 2560,
		        'height': 1400,
		    },
		    'available_size': {
		        'width': 2560,
		        'height': 1400,
		    },
		    'virtual_geometry': {
		        'x': 0,
		        'y': 0,
		        'width': 5120,
		        'height': 1440,
		    },
		    'virtual_size': {
		        'width': 5120,
		        'height': 1440,
		    },
		    'available_virtual_geometry': {
		        'x': 0,
		        'y': 0,
		        'width': 5120,
		        'height': 1400,
		    },
		    'available_virtual_size': {
		        'width': 5120,
		        'height': 1400,
		    },
		    'physical_size': {
		        'width': 600,
		        'height': 340,
		    },
		    'depth': 24,
		    'device_pixel_ratio': 1.0,
		    'logical_dots_per_inch': 96.0,
		    'logical_dots_per_inch_x': 96.0,
		    'logical_dots_per_inch_y': 96.0,
		    'orientation': 'Landscape',
		    'physical_dots_per_inch': 109.0,
		    'physical_dots_per_inch_x': 109.0,
		    'physical_dots_per_inch_y': 109.0,
		    'refresh_rate': 60.0,
		}
		```
		"""
		monitor = self.screen

		monitor_data = {
			'index': self.index,
			'name': monitor.name(),
			'manufacturer': monitor.manufacturer(),
			'model': monitor.model(),
			'serial_number': monitor.serialNumber(),
			'x': monitor.geometry().x(),
			'y': monitor.geometry().y(),
			'width': monitor.size().width(),
			'height': monitor.size().height(),
			'is_primary': self.is_primary(),
			'geometry': {
				'x': monitor.geometry().x(),
				'y': monitor.geometry().y(),
				'width': monitor.geometry().width(),
				'height': monitor.geometry().height(),
			},
			'size': {
				'width': monitor.size().width(),
				'height': monitor.size().height(),
			},
			'available_geometry': {
				'x': monitor.availableGeometry().x(),
				'y': monitor.availableGeometry().y(),
				'width': monitor.availableGeometry().width(),
				'height': monitor.availableGeometry().height(),
			},
			'available_size': {
				'width': monitor.availableSize().width(),
				'height': monitor.availableSize().height(),
			},
			'virtual_geometry': {
				'x': monitor.virtualGeometry().x(),
				'y': monitor.virtualGeometry().y(),
				'width': monitor.virtualGeometry().width(),
				'height': monitor.virtualGeometry().height(),
			},
			'virtual_size': {
				'width': monitor.virtualSize().width(),
				'height': monitor.virtualSize().height(),
			},
			'available_virtual_geometry': {
				'x': monitor.availableVirtualGeometry().x(),
				'y': monitor.availableVirtualGeometry().y(),
				'width': monitor.availableVirtualGeometry().width(),
				'height': monitor.availableVirtualGeometry().height(),
			},
			'available_virtual_size': {
				'width': monitor.availableVirtualSize().width(),
				'height': monitor.availableVirtualSize().height(),
			},
			'physical_size': {
				'width': monitor.physicalSize().width(),
				'height': monitor.physicalSize().height(),
			},
			'depth': monitor.depth(),
			'device_pixel_ratio': monitor.devicePixelRatio(),
			'logical_dots_per_inch': monitor.logicalDotsPerInch(),
			'logical_dots_per_inch_x': monitor.logicalDotsPerInchX(),
			'logical_dots_per_inch_y': monitor.logicalDotsPerInchY(),
			'orientation': monitor.orientation().name,
			'physical_dots_per_inch': monitor.physicalDotsPerInch(),
			'physical_dots_per_inch_x': monitor.physicalDotsPerInchX(),
			'physical_dots_per_inch_y': monitor.physicalDotsPerInchY(),
			'refresh_rate': monitor.refreshRate(),
		}

		return monitor_data

	def is_primary(
		self,
	) -> bool:
		"""
		Checks if the monitor is the primary monitor.

		Returns
		-------
		bool
		    Returns True if the monitor is the primary monitor, otherwise False.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_all_monitors()[1]
		is_primary = monitor.is_primary()
		print(f'Is primary monitor: {is_primary}')  # False
		```
		"""
		return self.index == 0

	def size(
		self,
	) -> dict[
		str,
		int,
	]:
		"""
		Returns the size of the monitor.

		Returns
		-------
		dict[str, int]
		    A dictionary containing the width and height of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		size = monitor.size()
		print(
		    'Monitor Size:',
		    size,
		)
		```
		"""
		monitor = self.screen
		return (
			{
				'width': monitor.size().width(),
				'height': monitor.size().height(),
			},
		)

	def geometry(
		self,
	) -> dict[
		str,
		int,
	]:
		"""
		Returns the geometry of the monitor.

		Returns
		-------
		dict[str, int]
		    A dictionary containing the x and y coordinates, width, and height of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		geometry = monitor.geometry()
		print(
		    'Monitor Geometry:',
		    geometry,
		)
		```
		"""
		monitor = self.screen
		return {
			'x': monitor.geometry().x(),
			'y': monitor.geometry().y(),
			'width': monitor.geometry().width(),
			'height': monitor.geometry().height(),
		}

	def available_geometry(
		self,
	) -> dict[
		str,
		int,
	]:
		"""
		Returns the available geometry of the monitor.

		The available geometry refers to the portion of the monitor's geometry that is not occupied by system UI elements such as taskbars or docks.

		Returns
		-------
		dict[str, int]
		    A dictionary containing the x and y coordinates, width, and height of the available geometry.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		available_geometry = monitor.available_geometry()
		print(
		    'Available Geometry:',
		    available_geometry,
		)
		```
		"""
		monitor = self.screen
		return {
			'x': monitor.availableGeometry().x(),
			'y': monitor.availableGeometry().y(),
			'width': monitor.availableGeometry().width(),
			'height': monitor.availableGeometry().height(),
		}

	def available_size(
		self,
	) -> dict[
		str,
		int,
	]:
		"""
		Returns the available size of the monitor.

		The available size refers to the portion of the monitor's size that is not occupied by system UI elements such as taskbars or docks.

		Returns
		-------
		dict[str, int]
		    A dictionary containing the width and height of the available size.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		available_size = monitor.available_size()
		print(
		    'Available Size:',
		    available_size,
		)
		```
		"""
		monitor = self.screen
		return {
			'width': monitor.availableSize().width(),
			'height': monitor.availableSize().height(),
		}

	def virtual_geometry(
		self,
	) -> dict[
		str,
		int,
	]:
		"""
		Returns the virtual geometry of the monitor.

		The virtual geometry refers to the combined geometry of all monitors in a multi-monitor setup.

		Returns
		-------
		dict[str, int]
		    A dictionary containing the x and y coordinates, width, and height of the virtual geometry.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		virtual_geometry = monitor.virtual_geometry()
		print(
		    'Virtual Geometry:',
		    virtual_geometry,
		)
		```
		"""
		monitor = self.screen
		return {
			'x': monitor.virtualGeometry().x(),
			'y': monitor.virtualGeometry().y(),
			'width': monitor.virtualGeometry().width(),
			'height': monitor.virtualGeometry().height(),
		}

	def virtual_size(
		self,
	) -> dict[
		str,
		int,
	]:
		"""
		Returns the virtual size of the monitor.

		The virtual size refers to the combined size of all monitors in a multi-monitor setup.

		Returns
		-------
		dict[str, int]
		    A dictionary containing the width and height of the virtual size.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		virtual_size = monitor.virtual_size()
		print(
		    'Virtual Size:',
		    virtual_size,
		)
		```
		"""
		monitor = self.screen
		return {
			'width': monitor.virtualSize().width(),
			'height': monitor.virtualSize().height(),
		}

	def available_virtual_geometry(
		self,
	) -> dict[
		str,
		int,
	]:
		"""
		Returns the available virtual geometry of the monitor.

		The available virtual geometry refers to the portion of the virtual geometry that is not occupied by system UI elements such as taskbars or docks.

		Returns
		-------
		dict[str, int]
		    A dictionary containing the x and y coordinates, width, and height of the available virtual geometry.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		available_virtual_geometry = monitor.available_virtual_geometry()
		print(
		    'Available Virtual Geometry:',
		    available_virtual_geometry,
		)
		```
		"""
		monitor = self.screen
		return {
			'x': monitor.availableVirtualGeometry().x(),
			'y': monitor.availableVirtualGeometry().y(),
			'width': monitor.availableVirtualGeometry().width(),
			'height': monitor.availableVirtualGeometry().height(),
		}

	def available_virtual_size(
		self,
	) -> dict[
		str,
		int,
	]:
		"""
		Returns the available virtual size of the monitor.

		The available virtual size refers to the portion of the virtual size that is not occupied by system UI elements such as taskbars or docks.

		Returns
		-------
		dict[str, int]
		    A dictionary containing the width and height of the available virtual size.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		available_virtual_size = monitor.available_virtual_size()
		print(
		    'Available Virtual Size:',
		    available_virtual_size,
		)
		```
		"""
		monitor = self.screen
		return {
			'width': monitor.availableVirtualSize().width(),
			'height': monitor.availableVirtualSize().height(),
		}

	def physical_size(
		self,
	) -> dict[
		str,
		float,
	]:
		"""
		Returns the physical size of the monitor.

		The physical size refers to the actual physical dimensions of the monitor.

		Returns
		-------
		dict[str, float]
		    A dictionary containing the width and height of the physical size.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		physical_size = monitor.physical_size()
		print(
		    'Physical Size:',
		    physical_size,
		)
		```
		"""
		monitor = self.screen
		return {
			'width': monitor.physicalSize().width(),
			'height': monitor.physicalSize().height(),
		}

	def depth(
		self,
	) -> int:
		"""
		Returns the depth of the monitor.

		The depth refers to the color depth of the monitor, which is the number of bits used to represent the color of a single pixel.

		Returns
		-------
		int
		    The color depth of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		depth = monitor.depth()
		print(
		    'Color Depth:',
		    depth,
		)
		```
		"""
		monitor = self.screen
		return monitor.depth()

	def device_pixel_ratio(
		self,
	) -> float:
		"""
		Returns the device pixel ratio of the monitor.

		The device pixel ratio is the ratio between physical pixels and device-independent pixels (DIPs).

		Returns
		-------
		float
		    The device pixel ratio of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		device_pixel_ratio = monitor.device_pixel_ratio()
		print(
		    'Device Pixel Ratio:',
		    device_pixel_ratio,
		)
		```
		"""
		monitor = self.screen
		return monitor.devicePixelRatio()

	def logical_dots_per_inch(
		self,
	) -> float:
		"""
		Returns the logical dots per inch (DPI) of the monitor.

		The logical DPI is the number of device-independent pixels (DIPs) per inch.

		Returns
		-------
		float
		    The logical DPI of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		logical_dpi = monitor.logical_dots_per_inch()
		print(
		    'Logical DPI:',
		    logical_dpi,
		)
		```
		"""
		monitor = self.screen
		return monitor.logicalDotsPerInch()

	def logical_dots_per_inch_x(
		self,
	) -> float:
		"""
		Returns the logical dots per inch (DPI) along the X axis of the monitor.

		The logical DPI along the X axis is the number of device-independent pixels (DIPs) per inch along the horizontal axis.

		Returns
		-------
		float
		    The logical DPI along the X axis of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		logical_dpi_x = monitor.logical_dots_per_inch_x()
		print(
		    'Logical DPI X:',
		    logical_dpi_x,
		)
		```
		"""
		monitor = self.screen
		return monitor.logicalDotsPerInchX()

	def logical_dots_per_inch_y(
		self,
	) -> float:
		"""
		Returns the logical dots per inch (DPI) along the Y axis of the monitor.

		The logical DPI along the Y axis is the number of device-independent pixels (DIPs) per inch along the vertical axis.

		Returns
		-------
		float
		    The logical DPI along the Y axis of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		logical_dpi_y = monitor.logical_dots_per_inch_y()
		print(
		    'Logical DPI Y:',
		    logical_dpi_y,
		)
		```
		"""
		monitor = self.screen
		return monitor.logicalDotsPerInchY()

	def orientation(
		self,
	) -> str:
		"""
		Returns the orientation of the monitor.

		The orientation refers to the current orientation of the monitor, such as landscape or portrait.

		Returns
		-------
		str
		    The orientation of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		orientation = monitor.orientation()
		print(
		    'Orientation:',
		    orientation,
		)
		```
		"""
		monitor = self.screen
		return monitor.orientation().name

	def physical_dots_per_inch(
		self,
	) -> float:
		"""
		Returns the physical dots per inch (DPI) of the monitor.

		The physical DPI is the number of physical pixels per inch.

		Returns
		-------
		float
		    The physical DPI of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		physical_dpi = monitor.physical_dots_per_inch()
		print(
		    'Physical DPI:',
		    physical_dpi,
		)
		```
		"""
		monitor = self.screen
		return monitor.physicalDotsPerInch()

	def physical_dots_per_inch_x(
		self,
	) -> float:
		"""
		Returns the physical dots per inch (DPI) along the X axis of the monitor.

		The physical DPI along the X axis is the number of physical pixels per inch along the horizontal axis.

		Returns
		-------
		float
		    The physical DPI along the X axis of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		physical_dpi_x = monitor.physical_dots_per_inch_x()
		print(
		    'Physical DPI X:',
		    physical_dpi_x,
		)
		```
		"""
		monitor = self.screen
		return monitor.physicalDotsPerInchX()

	def physical_dots_per_inch_y(
		self,
	) -> float:
		"""
		Returns the physical dots per inch (DPI) along the Y axis of the monitor.

		The physical DPI along the Y axis is the number of physical pixels per inch along the vertical axis.

		Returns
		-------
		float
		    The physical DPI along the Y axis of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		physical_dpi_y = monitor.physical_dots_per_inch_y()
		print(
		    'Physical DPI Y:',
		    physical_dpi_y,
		)
		```
		"""
		monitor = self.screen
		return monitor.physicalDotsPerInchY()

	def refresh_rate(
		self,
	) -> float:
		"""
		Returns the refresh rate of the monitor.

		The refresh rate is the number of times the monitor updates with new information per second, measured in Hertz (Hz).

		Returns
		-------
		float
		    The refresh rate of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		refresh_rate = monitor.refresh_rate()
		print(
		    'Refresh Rate:',
		    refresh_rate,
		)
		```
		"""
		monitor = self.screen
		return monitor.refreshRate()

	def manufacturer(
		self,
	) -> str:
		"""
		Returns the manufacturer of the monitor.

		The manufacturer refers to the company that produced the monitor.

		Returns
		-------
		str
		    The manufacturer of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		manufacturer = monitor.manufacturer()
		print(
		    'Manufacturer:',
		    manufacturer,
		)
		```
		"""
		monitor = self.screen
		return monitor.manufacturer()

	def model(
		self,
	) -> str:
		"""
		Returns the model of the monitor.

		The model refers to the specific model name or number assigned by the manufacturer.

		Returns
		-------
		str
		    The model of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		model = monitor.model()
		print(
		    'Model:',
		    model,
		)
		```
		"""
		monitor = self.screen
		return monitor.model()

	def name(
		self,
	) -> str:
		"""
		Returns the name of the monitor.

		The name refers to the user-friendly name assigned to the monitor.

		Returns
		-------
		str
		    The name of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		name = monitor.name()
		print(
		    'Name:',
		    name,
		)
		```
		"""
		monitor = self.screen
		return monitor.name()

	def serial_number(
		self,
	) -> str:
		"""
		Returns the serial number of the monitor.

		The serial number is a unique identifier assigned to the monitor by the manufacturer.

		Returns
		-------
		str
		    The serial number of the monitor.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')

		monitor = app.get_primary_monitor()
		serial_number = monitor.serial_number()
		print(
		    'Serial Number:',
		    serial_number,
		)
		```
		"""
		monitor = self.screen
		return monitor.serialNumber()

	def geometry_changed(
		self,
		callback: Callable,
	):
		"""
		Registers a callback for the event that occurs when the geometry of the monitor changes.

		Parameters
		----------
		callback : Callable
		    The function to be called when the geometry changes.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')


		def on_geometry_changed():
		    print('Geometry changed!')


		monitor = app.get_primary_monitor()
		monitor.geometry_changed(on_geometry_changed)
		```
		"""
		monitor = self.screen
		monitor.geometryChanged.connect(callback)

	def orientation_changed(
		self,
		callback: Callable,
	):
		"""
		Registers a callback for the event that occurs when the orientation of the monitor changes.

		Parameters
		----------
		callback : Callable
		    The function to be called when the orientation changes.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')


		def on_orientation_changed():
		    print('Orientation changed!')


		monitor = app.get_primary_monitor()
		monitor.orientation_changed(on_orientation_changed)
		```
		"""
		monitor = self.screen
		monitor.orientationChanged.connect(callback)

	def refresh_rate_changed(
		self,
		callback: Callable,
	):
		"""
		Registers a callback for the event that occurs when the refresh rate of the monitor changes.

		Parameters
		----------
		callback : Callable
		    The function to be called when the refresh rate changes.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')


		def on_refresh_rate_changed():
		    print('Refresh rate changed!')


		monitor = app.get_primary_monitor()
		monitor.refresh_rate_changed(on_refresh_rate_changed)
		```
		"""
		monitor = self.screen
		monitor.refreshRateChanged.connect(callback)

	def virtual_geometry_changed(
		self,
		callback: Callable,
	):
		"""
		Registers a callback for the event that occurs when the virtual geometry of the monitor changes.

		Parameters
		----------
		callback : Callable
		    The function to be called when the virtual geometry changes.

		Examples
		--------
		```python
		app = Pyloid('Pyloid-App')


		def on_virtual_geometry_changed():
		    print('Virtual geometry changed!')


		monitor = app.get_primary_monitor()
		monitor.virtual_geometry_changed(on_virtual_geometry_changed)
		```
		"""
		monitor = self.screen
		monitor.virtualGeometryChanged.connect(callback)
