"""BACnet Loop object per ASHRAE 135-2020 Clause 12.17."""

from __future__ import annotations

from typing import Any, ClassVar

from bac_py.objects.base import (
    BACnetObject,
    PropertyAccess,
    PropertyDefinition,
    register_object_type,
    standard_properties,
    status_properties,
)
from bac_py.types.constructed import BACnetObjectPropertyReference
from bac_py.types.enums import (
    Action,
    EngineeringUnits,
    EventType,
    ObjectType,
    PropertyIdentifier,
)


@register_object_type
class LoopObject(BACnetObject):
    """BACnet Loop object (Clause 12.17).

    Represents a PID control loop.  Present_Value is the manipulated
    variable output.  The control algorithm parameters (P, I, D
    constants) define the loop behavior.
    """

    OBJECT_TYPE: ClassVar[ObjectType] = ObjectType.LOOP
    INTRINSIC_EVENT_ALGORITHM: ClassVar[EventType | None] = EventType.FLOATING_LIMIT

    PROPERTY_DEFINITIONS: ClassVar[dict[PropertyIdentifier, PropertyDefinition]] = {
        **standard_properties(),
        PropertyIdentifier.PRESENT_VALUE: PropertyDefinition(
            PropertyIdentifier.PRESENT_VALUE,
            float,
            PropertyAccess.READ_WRITE,
            required=True,
            default=0.0,
        ),
        **status_properties(),
        PropertyIdentifier.CONTROLLED_VARIABLE_REFERENCE: PropertyDefinition(
            PropertyIdentifier.CONTROLLED_VARIABLE_REFERENCE,
            BACnetObjectPropertyReference,
            PropertyAccess.READ_WRITE,
            required=True,
        ),
        PropertyIdentifier.CONTROLLED_VARIABLE_VALUE: PropertyDefinition(
            PropertyIdentifier.CONTROLLED_VARIABLE_VALUE,
            float,
            PropertyAccess.READ_ONLY,
            required=True,
            default=0.0,
        ),
        PropertyIdentifier.CONTROLLED_VARIABLE_UNITS: PropertyDefinition(
            PropertyIdentifier.CONTROLLED_VARIABLE_UNITS,
            EngineeringUnits,
            PropertyAccess.READ_ONLY,
            required=True,
            default=EngineeringUnits.NO_UNITS,
        ),
        PropertyIdentifier.MANIPULATED_VARIABLE_REFERENCE: PropertyDefinition(
            PropertyIdentifier.MANIPULATED_VARIABLE_REFERENCE,
            BACnetObjectPropertyReference,
            PropertyAccess.READ_WRITE,
            required=True,
        ),
        PropertyIdentifier.SETPOINT_REFERENCE: PropertyDefinition(
            PropertyIdentifier.SETPOINT_REFERENCE,
            BACnetObjectPropertyReference,
            PropertyAccess.READ_WRITE,
            required=True,
        ),
        PropertyIdentifier.SETPOINT: PropertyDefinition(
            PropertyIdentifier.SETPOINT,
            float,
            PropertyAccess.READ_WRITE,
            required=True,
            default=0.0,
        ),
        PropertyIdentifier.ACTION: PropertyDefinition(
            PropertyIdentifier.ACTION,
            Action,
            PropertyAccess.READ_WRITE,
            required=True,
            default=Action.DIRECT,
        ),
        PropertyIdentifier.PROPORTIONAL_CONSTANT: PropertyDefinition(
            PropertyIdentifier.PROPORTIONAL_CONSTANT,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
            default=0.0,
        ),
        PropertyIdentifier.PROPORTIONAL_CONSTANT_UNITS: PropertyDefinition(
            PropertyIdentifier.PROPORTIONAL_CONSTANT_UNITS,
            EngineeringUnits,
            PropertyAccess.READ_ONLY,
            required=False,
            default=EngineeringUnits.NO_UNITS,
        ),
        PropertyIdentifier.INTEGRAL_CONSTANT: PropertyDefinition(
            PropertyIdentifier.INTEGRAL_CONSTANT,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
            default=0.0,
        ),
        PropertyIdentifier.INTEGRAL_CONSTANT_UNITS: PropertyDefinition(
            PropertyIdentifier.INTEGRAL_CONSTANT_UNITS,
            EngineeringUnits,
            PropertyAccess.READ_ONLY,
            required=False,
            default=EngineeringUnits.NO_UNITS,
        ),
        PropertyIdentifier.DERIVATIVE_CONSTANT: PropertyDefinition(
            PropertyIdentifier.DERIVATIVE_CONSTANT,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
            default=0.0,
        ),
        PropertyIdentifier.DERIVATIVE_CONSTANT_UNITS: PropertyDefinition(
            PropertyIdentifier.DERIVATIVE_CONSTANT_UNITS,
            EngineeringUnits,
            PropertyAccess.READ_ONLY,
            required=False,
            default=EngineeringUnits.NO_UNITS,
        ),
        PropertyIdentifier.MAXIMUM_OUTPUT: PropertyDefinition(
            PropertyIdentifier.MAXIMUM_OUTPUT,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
            default=100.0,
        ),
        PropertyIdentifier.MINIMUM_OUTPUT: PropertyDefinition(
            PropertyIdentifier.MINIMUM_OUTPUT,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
            default=0.0,
        ),
        PropertyIdentifier.PRIORITY_FOR_WRITING: PropertyDefinition(
            PropertyIdentifier.PRIORITY_FOR_WRITING,
            int,
            PropertyAccess.READ_WRITE,
            required=True,
            default=16,
        ),
        PropertyIdentifier.UPDATE_INTERVAL: PropertyDefinition(
            PropertyIdentifier.UPDATE_INTERVAL,
            int,
            PropertyAccess.READ_WRITE,
            required=False,
            default=100,
        ),
        PropertyIdentifier.OUTPUT_UNITS: PropertyDefinition(
            PropertyIdentifier.OUTPUT_UNITS,
            EngineeringUnits,
            PropertyAccess.READ_WRITE,
            required=True,
            default=EngineeringUnits.NO_UNITS,
        ),
        PropertyIdentifier.BIAS: PropertyDefinition(
            PropertyIdentifier.BIAS,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
        ),
        PropertyIdentifier.COV_INCREMENT: PropertyDefinition(
            PropertyIdentifier.COV_INCREMENT,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
        ),
        PropertyIdentifier.ERROR_LIMIT: PropertyDefinition(
            PropertyIdentifier.ERROR_LIMIT,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
        ),
        PropertyIdentifier.DEADBAND: PropertyDefinition(
            PropertyIdentifier.DEADBAND,
            float,
            PropertyAccess.READ_WRITE,
            required=False,
        ),
        PropertyIdentifier.NOTIFICATION_CLASS: PropertyDefinition(
            PropertyIdentifier.NOTIFICATION_CLASS,
            int,
            PropertyAccess.READ_WRITE,
            required=False,
        ),
        PropertyIdentifier.EVENT_ENABLE: PropertyDefinition(
            PropertyIdentifier.EVENT_ENABLE,
            list,
            PropertyAccess.READ_WRITE,
            required=False,
        ),
        PropertyIdentifier.TIME_DELAY: PropertyDefinition(
            PropertyIdentifier.TIME_DELAY,
            int,
            PropertyAccess.READ_WRITE,
            required=False,
        ),
    }

    def __init__(self, instance_number: int, **initial_properties: Any) -> None:
        super().__init__(instance_number, **initial_properties)
        self._init_status_flags()
        # Object property references default to empty if not provided
        self._set_default(PropertyIdentifier.CONTROLLED_VARIABLE_REFERENCE, None)
        self._set_default(PropertyIdentifier.MANIPULATED_VARIABLE_REFERENCE, None)
        self._set_default(PropertyIdentifier.SETPOINT_REFERENCE, None)
