"""AccessPoint base class for VLC access points."""

import numpy as np
from enum import Enum
from typing import Optional, Any


class AccessPoint:
    """Base class for all communication access points in VLC simulation.

    AccessPoint provides the foundational structure for both VLC (VLed) and RF communication
    access points. It manages spatial positioning, operational state, and time-division
    multiplexing (TDM) parameters.

    The class implements a state machine where access points can be either IDLE or BUSY,
    and supports TDM frame slicing for resource allocation.

    Attributes:
        stateap (Enum): Enumeration defining AP states (IDLE, BUSY)

    Properties:
        x (float): X-coordinate position in meters
        y (float): Y-coordinate position in meters
        z (float): Z-coordinate position in meters (typically height)
        position (ndarray): 3D position vector [x, y, z]
        state (stateap): Current operational state
        ID (int): Unique identifier for the access point
        B (float): Bandwidth in Hz
        sliceTime (float): Duration of each time slice in seconds
        slicesInFrame (int): Number of slices per TDM frame

    Example:
        AccessPoint is typically not instantiated directly, but through subclasses ::

            # Create VLed access point
            vled = VLed(x=5.0, y=5.0, z=3.0, nLedsX=2, nLedsY=2,
                       ledPower=20, theta=60)
            vled.sliceTime = 0.1  # 100ms slices
            vled.slicesInFrame = 10  # 10 slices per frame

            # Create RF access point
            rf = RF(x=5.0, y=5.0, z=1.0)
            rf.B = 5e6  # 5 MHz bandwidth

    Note:
        - Position coordinates use a standard 3D Cartesian coordinate system
        - Z-coordinate typically represents height (e.g., ceiling height for LEDs)
        - Bandwidth B is used in capacity calculations
        - TDM parameters control resource allocation granularity
    """

    stateap = Enum("state", "IDLE BUSY")

    def __init__(
        self,
        x: float,
        y: float,
        z: float,
    ) -> None:
        """Initialize an AccessPoint at a given 3D position.

        Creates a new access point with specified coordinates and default configuration.
        The access point is initialized in IDLE state with default TDM parameters.

        Args:
            x: X-coordinate position in meters
            y: Y-coordinate position in meters
            z: Z-coordinate position in meters (typically height from ground)

        Example:
            Typically called from subclass constructors::

                class MyAccessPoint(AccessPoint):
                    def __init__(self, x, y, z):
                        super().__init__(x, y, z)
                        # Add subclass-specific initialization

        Note:
            - Default bandwidth is set to 1 MHz
            - Default slice time is 0.1 seconds (100ms)
            - Default frame has 10 slices
            - Initial state is IDLE
        """
        self._x: float = x
        self._y: float = y
        self._z: float = z
        self._state = AccessPoint.stateap.IDLE
        self._sliceTime: float = 0.1
        self._slicesInFrame: int = 10

        self._ID: Optional[int] = None
        self._position: np.ndarray[Any, np.dtype[np.float64]] = np.array([x, y, z])
        self._B: float = 1e6

    @property
    def B(self) -> float:
        """Get the bandwidth of this access point.

        Returns:
            float: Bandwidth in Hz (default: 1 MHz)
        """
        return self._B

    @B.setter
    def B(self, value: float) -> None:
        """Set the bandwidth of this access point.

        Args:
            value: Bandwidth in Hz
        """
        self._B = value

    @property
    def ID(self) -> Optional[int]:
        """Get the unique identifier of this access point.

        Returns:
            int or None: Access point ID, None if not yet assigned
        """
        return self._ID

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

        Args:
            value: ID number to assign
        """
        self._ID = value

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

        Returns:
            float: X-coordinate in meters
        """
        return self._x

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

        Updates both the individual coordinate and the position vector.

        Args:
            value: X-coordinate in meters
        """
        self._x = value
        self._position = np.array([self._x, self.y, self.z])

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

        Returns:
            float: Y-coordinate in meters
        """
        return self._y

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

        Updates both the individual coordinate and the position vector.

        Args:
            value: Y-coordinate in meters
        """
        self._y = value
        self._position = np.array([self.x, self._y, self.z])

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

        Returns:
            float: Z-coordinate in meters (typically height)
        """
        return self._z

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

        Updates both the individual coordinate and the position vector.

        Args:
            value: Z-coordinate in meters
        """
        self._z = value
        self._position = np.array([self.x, self.y, self._z])

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

        Returns:
            ndarray: Position vector [x, y, z] in meters
        """
        return self._position

    @property
    def state(self) -> stateap:
        """Get the current operational state of this access point.

        Returns:
            stateap: Current state, either IDLE or BUSY
        """
        return self._state

    def setIDLE(self) -> None:
        """Set this access point to IDLE state.

        Transitions the access point to IDLE, indicating it is ready to accept
        new connections.
        """
        self._state = AccessPoint.stateap.IDLE

    def setBUSY(self) -> None:
        """Set this access point to BUSY state.

        Transitions the access point to BUSY, indicating it is actively
        serving connections.
        """
        self._state = AccessPoint.stateap.BUSY

    @property
    def sliceTime(self) -> float:
        """Get the time duration of each TDM slice.

        The slice time determines how long each connection can use the access point
        in a single time slice.

        Returns:
            float: Slice time duration in seconds (default: 0.1s)
        """
        return self._sliceTime

    @sliceTime.setter
    def sliceTime(self, value: float) -> None:
        """Set the time duration of each TDM slice.

        Args:
            value: Slice time duration in seconds
        """
        self._sliceTime = value

    @property
    def slicesInFrame(self) -> int:
        """Get the number of time slices in each TDM frame.

        This determines the frame structure for time-division multiplexing.
        Total frame duration equals sliceTime * slicesInFrame.

        Returns:
            int: Number of slices per frame (default: 10)
        """
        return self._slicesInFrame

    @slicesInFrame.setter
    def slicesInFrame(self, value: int) -> None:
        """Set the number of time slices in each TDM frame.

        Args:
            value: Number of slices per frame
        """
        self._slicesInFrame = value
