"""Receiver device class for VLC simulation."""

import numpy as np
import math as m
from typing import Optional, Any


class Receiver:
    """Represents a receiver device in the VLC scenario.

    The Receiver class models a photodetector device with all physical and
    electrical parameters required for VLC simulation, including optical and
    electrical properties, timing information, and connection state.

    Attributes:
        receiversCreated: Class variable tracking the total number of receivers
            created.

    Properties:
        - Position: x, y, z coordinates and position vector
        - Optical: aDet (detector area), ts (symbol period), index (refractive index),
          fov (field of view), gCon (concentrator gain)
        - Electrical: q (electronic charge), s (photodetector responsivity),
          b (bandwidth), ibg (background current), cb (Boltzmann constant),
          tk (temperature), gv (voltage gain), n (capacitance), fr (noise factor),
          gm (transconductance)
        - Experimental: i1, i2 (calibration constants)
        - Connection: capacityFromAP, timeFirstConnected, goalTime, timeActive,
          timeFinished

    Example:
        >>> receiver = Receiver(
        ...     x=2.5, y=3.0, z=0.85,
        ...     aDet=1e-4, ts=0.1, index=1.5, fov=60.0
        ... )
        >>> receiver.ID
        0
    """

    receiversCreated = 0
    """Class variable tracking the total number of receivers created."""

    def __init__(
        self,
        x: float,
        y: float,
        z: float,
        aDet: float,
        ts: float,
        index: float,
        fov: float,
        q: float = 1.6e-19,
        s: float = 0.54,
        b: float = 10e6,
        ibg: float = 5.1e-3,
        cb: float = 1.380649e-23,
        tk: float = 298.0,
        a: float = 1.0,
        gv: float = 10.0,
        n: float = 1.12e-6,
        fr: float = 1.5,
        gm: float = 3e-2,
        i1: float = 0.562,
        i2: float = 0.0868,
    ) -> None:
        """Initialize a receiver device with physical and electrical properties.

        Args:
            x: X coordinate of the receiver position in meters
            y: Y coordinate of the receiver position in meters
            z: Z coordinate of the receiver position in meters
            aDet: Photodetector active area in m²
            ts: Symbol period in seconds
            index: Refractive index of the optical concentrator
            fov: Field of view half-angle in degrees
            q: Electronic charge in coulombs (default: 1.6e-19 C)
            s: Photodetector responsivity in A/W (default: 0.54 A/W)
            b: Receiver bandwidth in Hz (default: 10 MHz)
            ibg: Background current in amperes (default: 5.1e-3 A)
            cb: Boltzmann constant in J/K (default: 1.380649e-23 J/K)
            tk: Absolute temperature in Kelvin (default: 298.0 K)
            a: Optical filter gain (default: 1.0)
            gv: Open-loop voltage gain (default: 10.0)
            n: Photodetector capacitance per unit area in F/m² (default: 1.12e-6 F/m²)
            fr: FET channel noise factor (default: 1.5)
            gm: FET transconductance in S (default: 3e-2 S)
            i1: Experimental calibration constant (default: 0.562)
            i2: Experimental calibration constant (default: 0.0868)

        Note:
            The receiver is automatically assigned a unique ID upon creation.
            The concentrator gain (gCon) is calculated from the index and FOV.
        """
        self.__x = x
        self.__y = y
        self.__z = z
        self.__aDet = aDet
        self.__ts = ts
        self.__index = index
        self.__fov = fov
        self.__q = q
        self.__s = s
        self.__b = b
        self.__ibg = ibg
        self.__cb = cb
        self.__tk = tk
        self.__a = a
        self.__gv = gv
        self.__n = n
        self.__fr = fr
        self.__gm = gm
        self.__i1 = i1
        self.__i2 = i2

        self.__timeFirstConnected: Optional[float] = None
        self.__goalTime: Optional[float] = None
        self.__timeActive: float = 0.0
        self.__timeFinished: Optional[float] = None
        self.__capacityFromAP: Optional[float] = None

        self.__gCon = (index**2) / (m.sin(m.radians(fov)) ** 2)
        self.__position = np.array([x, y, z])
        self.__ID = Receiver.receiversCreated
        Receiver.receiversCreated += 1

    @property
    def capacityFromAP(self) -> Optional[float]:
        """Get the capacity allocated from the access point.

        Returns:
            Optional[float]: Capacity from AP in bits/s, or None if not connected
        """
        return self.__capacityFromAP

    @capacityFromAP.setter
    def capacityFromAP(self, value: float) -> None:
        """Set the capacity allocated from the access point.

        Args:
            value: Capacity value in bits/s
        """
        self.__capacityFromAP = value

    @property
    def ID(self) -> int:
        """Get the unique identifier of this receiver.

        Returns:
            int: Unique receiver ID assigned at creation
        """
        return self.__ID

    @ID.setter
    def ID(self, value: int) -> None:
        """Set the unique identifier of this receiver.

        Args:
            value: The ID value to set
        """
        self.__ID = value

    @property
    def x(self) -> float:
        """Get the X coordinate of the receiver.

        Returns:
            float: X coordinate in meters
        """
        return self.__x

    @x.setter
    def x(self, value: float) -> None:
        """Set the X coordinate of the receiver.

        Args:
            value: X coordinate in meters
        """
        self.__x = value

    @property
    def y(self) -> float:
        """Get the Y coordinate of the receiver.

        Returns:
            float: Y coordinate in meters
        """
        return self.__y

    @y.setter
    def y(self, value: float) -> None:
        """Set the Y coordinate of the receiver.

        Args:
            value: Y coordinate in meters
        """
        self.__y = value

    @property
    def z(self) -> float:
        """Get the Z coordinate of the receiver.

        Returns:
            float: Z coordinate in meters
        """
        return self.__z

    @z.setter
    def z(self, value: float) -> None:
        """Set the Z coordinate of the receiver.

        Args:
            value: Z coordinate in meters
        """
        self.__z = value

    @property
    def aDet(self) -> float:
        """Get the photodetector active area.

        Returns:
            float: Photodetector active area in m²
        """
        return self.__aDet

    @aDet.setter
    def aDet(self, value: float) -> None:
        """Set the photodetector active area.

        Args:
            value: Photodetector active area in m²
        """
        self.__aDet = value

    @property
    def ts(self) -> float:
        """Get the symbol period.

        Returns:
            float: Symbol period in seconds
        """
        return self.__ts

    @ts.setter
    def ts(self, value: float) -> None:
        """Set the symbol period.

        Args:
            value: Symbol period in seconds
        """
        self.__ts = value

    @property
    def index(self) -> float:
        """Get the refractive index of the optical concentrator.

        Returns:
            float: Refractive index (dimensionless)
        """
        return self.__index

    @index.setter
    def index(self, value: float) -> None:
        """Set the refractive index of the optical concentrator.

        Args:
            value: Refractive index value
        """
        self.__index = value

    @property
    def fov(self) -> float:
        """Get the field of view half-angle.

        Returns:
            float: Field of view half-angle in degrees
        """
        return self.__fov

    @fov.setter
    def fov(self, value: float) -> None:
        """Set the field of view half-angle.

        Args:
            value: Field of view half-angle in degrees
        """
        self.__fov = value

    @property
    def gCon(self) -> float:
        """Get the concentrator gain.

        This is a read-only property calculated from the refractive index and FOV.

        Returns:
            float: Concentrator gain (dimensionless)
        """
        return self.__gCon

    @property
    def position(self) -> np.ndarray[Any, np.dtype[np.float64]]:
        """Get the position vector of the receiver.

        Returns:
            np.ndarray[Any, np.dtype[np.float64]]: Position as numpy array [x, y, z]
        """
        return self.__position

    @property
    def q(self) -> float:
        """Get the electronic charge.

        Returns:
            float: Electronic charge in coulombs (default: 1.6e-19 C)
        """
        return self.__q

    @q.setter
    def q(self, value: float) -> None:
        """Set the electronic charge.

        Args:
            value: Electronic charge in coulombs
        """
        self.__q = value

    @property
    def s(self) -> float:
        """Get the photodetector responsivity.

        Returns:
            float: Photodetector responsivity in A/W (default: 0.54 A/W)
        """
        return self.__s

    @s.setter
    def s(self, value: float) -> None:
        """Set the photodetector responsivity.

        Args:
            value: Photodetector responsivity in A/W
        """
        self.__s = value

    @property
    def b(self) -> float:
        """Get the receiver bandwidth.

        Returns:
            float: Receiver bandwidth in Hz (default: 10 MHz)
        """
        return self.__b

    @b.setter
    def b(self, value: float) -> None:
        """Set the receiver bandwidth.

        Args:
            value: Receiver bandwidth in Hz
        """
        self.__b = value

    @property
    def ibg(self) -> float:
        """Get the background current.

        Returns:
            float: Background current in amperes (default: 5.1e-3 A)
        """
        return self.__ibg

    @ibg.setter
    def ibg(self, value: float) -> None:
        """Set the background current.

        Args:
            value: Background current in amperes
        """
        self.__ibg = value

    @property
    def cb(self) -> float:
        """Get the Boltzmann constant.

        Returns:
            float: Boltzmann constant in J/K (default: 1.380649e-23 J/K)
        """
        return self.__cb

    @cb.setter
    def cb(self, value: float) -> None:
        """Set the Boltzmann constant.

        Args:
            value: Boltzmann constant in J/K
        """
        self.__cb = value

    @property
    def tk(self) -> float:
        """Get the absolute temperature.

        Returns:
            float: Temperature in Kelvin (default: 298.0 K)
        """
        return self.__tk

    @tk.setter
    def tk(self, value: float) -> None:
        """Set the absolute temperature.

        Args:
            value: Temperature in Kelvin
        """
        self.__tk = value

    @property
    def a(self) -> float:
        """Get the optical filter gain.

        Returns:
            float: Optical filter gain (dimensionless, default: 1.0)
        """
        return self.__a

    @a.setter
    def a(self, value: float) -> None:
        """Set the optical filter gain.

        Args:
            value: Optical filter gain value
        """
        self.__a = value

    @property
    def gv(self) -> float:
        """Get the open-loop voltage gain.

        Returns:
            float: Open-loop voltage gain (dimensionless, default: 10.0)
        """
        return self.__gv

    @gv.setter
    def gv(self, value: float) -> None:
        """Set the open-loop voltage gain.

        Args:
            value: Open-loop voltage gain value
        """
        self.__gv = value

    @property
    def n(self) -> float:
        """Get the photodetector capacitance per unit area.

        Returns:
            float: Capacitance in F/m² (default: 1.12e-6 F/m²)
        """
        return self.__n

    @n.setter
    def n(self, value: float) -> None:
        """Set the photodetector capacitance per unit area.

        Args:
            value: Capacitance in F/m²
        """
        self.__n = value

    @property
    def fr(self) -> float:
        """Get the FET channel noise factor.

        Returns:
            float: FET noise factor (dimensionless, default: 1.5)
        """
        return self.__fr

    @fr.setter
    def fr(self, value: float) -> None:
        """Set the FET channel noise factor.

        Args:
            value: FET noise factor value
        """
        self.__fr = value

    @property
    def gm(self) -> float:
        """Get the FET transconductance.

        Returns:
            float: FET transconductance in siemens (default: 3e-2 S)
        """
        return self.__gm

    @gm.setter
    def gm(self, value: float) -> None:
        """Set the FET transconductance.

        Args:
            value: FET transconductance in siemens
        """
        self.__gm = value

    @property
    def i1(self) -> float:
        """Get the first experimental calibration constant.

        Returns:
            float: Calibration constant (default: 0.562)
        """
        return self.__i1

    @i1.setter
    def i1(self, value: float) -> None:
        """Set the first experimental calibration constant.

        Args:
            value: Calibration constant value
        """
        self.__i1 = value

    @property
    def i2(self) -> float:
        """Get the second experimental calibration constant.

        Returns:
            float: Calibration constant (default: 0.0868)
        """
        return self.__i2

    @i2.setter
    def i2(self, value: float) -> None:
        """Set the second experimental calibration constant.

        Args:
            value: Calibration constant value
        """
        self.__i2 = value

    @property
    def timeFirstConnected(self) -> Optional[float]:
        """Get the time when the receiver was first connected.

        Returns:
            Optional[float]: Connection time in seconds, or None if not connected yet
        """
        return self.__timeFirstConnected

    @timeFirstConnected.setter
    def timeFirstConnected(self, value: float) -> None:
        """Set the time when the receiver was first connected.

        Args:
            value: Connection time in seconds
        """
        self.__timeFirstConnected = value

    @property
    def goalTime(self) -> Optional[float]:
        """Get the goal connection time for this receiver.

        The goal time represents the required connection duration based on SNR
        and capacity requirements.

        Returns:
            Optional[float]: Goal time in seconds, or None if not set
        """
        return self.__goalTime

    @goalTime.setter
    def goalTime(self, value: float) -> None:
        """Set the goal connection time for this receiver.

        Args:
            value: Goal time in seconds
        """
        self.__goalTime = value

    @property
    def timeActive(self) -> float:
        """Get the effective time the receiver has been connected.

        Returns:
            float: Active connection time in seconds
        """
        return self.__timeActive

    @timeActive.setter
    def timeActive(self, value: float) -> None:
        """Set the effective time the receiver has been connected.

        Args:
            value: Active time in seconds
        """
        self.__timeActive = value

    @property
    def timeFinished(self) -> Optional[float]:
        """Get the time when the receiver finished its connection.

        Returns:
            Optional[float]: Disconnection time in seconds, or None if still active
        """
        return self.__timeFinished

    @timeFinished.setter
    def timeFinished(self, value: float) -> None:
        """Set the time when the receiver finished its connection.

        Args:
            value: Disconnection time in seconds
        """
        self.__timeFinished = value
