"""
dcv_pi_control.py - Implements Proportional-Integral control.
"""

from dataclasses import dataclass


@dataclass
class PiControlData:
    """
    kp: PI proportionality constant [L/s-ppm]
    tau: PI integration time constant [s]
    db: Dead band, above/below set-point [ppm]
    co_min: Minimum controller output [L/s]
    co_max: Maximum controller output [L/s]
    """

    kp: float
    tau: float
    db: float
    co_min: float
    co_max: float


# ====================================================== class PI_Control =====


class DcvPiControl:
    """PI controller class. Provides Proportional-Integral control with anti-
    windup. Based on [Pistochini 2023] Method of test for CO2-based demand
    control ventilation systems: Benchmarking the state-of-the-art and the
    undervalued potential of proportional-integral control. T. Pistochini,
    M. Ellis, F. Meyers, A. Frasier, C. Cappa and D. Bennett. Energy and
    Buildings, 2023 Vol. 301. DOI: 10.1016/j.enbuild.2023.113717.
    """

    def __init__(self, control_data):
        self.cd = control_data
        self.kp_inv = 1.0 / control_data.kp
        self.tau_inv = 1.0 / control_data.tau
        self.edb_t = 0.0
        self.ib_t = 0.0
        self.ib_t_prev = 0.0
        self.i_t = 0.0
        self.voa_star = 0.0

    def set_co(self, pv, sp, dt) -> float:
        """
        Set the Controller Output (co) for the PI control algorithm.

        Args:
            pv: `float`
                Current value of process variable, CO2 concentration [ppm]
            sp: `float`
                Current set point for `pv`, CO2 concentration [ppm]
            dt: `float`
                Time step [s]

        :returns: Control Output ventilation rate [sL/s]
        :rtype: float
        """
        self.ib_t_prev = self.ib_t
        e_t = sp - pv
        if e_t > self.cd.db:
            edb_t = e_t - self.cd.db
        elif e_t < -self.cd.db:
            edb_t = e_t + self.cd.db
        else:
            edb_t = 0.0

        self.i_t = self.ib_t_prev + dt * edb_t
        self.voa_star = -self.cd.kp * (edb_t + self.i_t * self.tau_inv)
        if self.voa_star < self.cd.co_min:
            self.ib_t = -self.cd.tau * (edb_t + self.cd.co_min * self.kp_inv)
        elif self.voa_star > self.cd.co_max:
            self.ib_t = -self.cd.tau * (edb_t + self.cd.co_max * self.kp_inv)
        else:
            self.ib_t = self.i_t

        voa_set = -self.cd.kp * (edb_t + self.ib_t * self.tau_inv)
        return voa_set
