"""
Lakeshore temperature controllers
+++++++++++++++++++++++++++++++++

.. autosummary::

   ~LakeShore336Device
   ~LakeShore340Device

.. not yet implemented
   ~LakeShore330Device
   ~LakeShore331Device
   ~LakeShore335Device
   ~LakeShoreDR93CADevice
"""

# TODO: refactor
#    temperature = Component(PVPositionerSoftDoneWithStop, ...)

from ophyd import Component
from ophyd import Device
from ophyd import EpicsSignal
from ophyd import EpicsSignalRO
from ophyd import EpicsSignalWithRBV
from ophyd import FormattedComponent
from ophyd import Signal

from . import PVPositionerSoftDoneWithStop
from ..synApps import AsynRecord

SECOND = 1
MINUTE = 60 * SECOND
HOUR = 60 * MINUTE


class LakeShore336_LoopControl(PVPositionerSoftDoneWithStop):
    """
    LakeShore 336 temperature controller -- with heater control.

    The LakeShore 336 accepts up to two heaters.
    """

    # position
    # readback is provided by PVPositionerSoftDoneWithStop

    setpoint = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}OUT{loop_number}:SP", put_complete=True
    )

    # configuration
    units = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}IN{loop_number}:Units", kind="config"
    )

    heater = FormattedComponent(
        EpicsSignalRO,
        "{prefix}HTR{loop_number}",
        auto_monitor=True,
        kind="normal"
    )
    heater_range = FormattedComponent(
        EpicsSignalWithRBV,
        "{prefix}HTR{loop_number}:Range",
        kind="config",
        auto_monitor=True,
        string=True,
    )

    pid_P = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}P{loop_number}", kind="config"
    )
    pid_I = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}I{loop_number}", kind="config"
    )
    pid_D = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}D{loop_number}", kind="config"
    )

    ramp_rate = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}RampR{loop_number}", kind="config"
    )
    ramp_on = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}OnRamp{loop_number}", kind="config"
    )

    loop_name = FormattedComponent(
        EpicsSignalRO, "{prefix}IN{loop_number}:Name_RBV", kind="config"
    )
    control = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}OUT{loop_number}:Cntrl", kind="config"
    )
    manual = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}OUT{loop_number}:MOUT", kind="config"
    )
    mode = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}OUT{loop_number}:Mode", kind="config"
    )

    def __init__(self, *args, loop_number=None, timeout=10 * HOUR, **kwargs):
        self.loop_number = loop_number
        super().__init__(
            *args,
            timeout=timeout,
            tolerance=0.1,
            readback_pv=f"IN{loop_number}",
            **kwargs
        )
        self._settle_time = 0

    @property
    def settle_time(self):
        return self._settle_time

    @settle_time.setter
    def settle_time(self, value):
        if value < 0:
            raise ValueError("Settle value needs to be >= 0.")
        else:
            self._settle_time = value

    def pause(self):
        """Change setpoint to current position."""
        self.setpoint.put(self._position)


class LakeShore336_LoopRO(Device):
    """
    LakeShore 336 temperature controller -- Read-only loop (no heaters).
    """

    # Set this to normal because we don't use it.
    readback = FormattedComponent(
        EpicsSignalRO, "{prefix}IN{loop_number}", kind="normal"
    )
    units = FormattedComponent(
        EpicsSignalWithRBV, "{prefix}IN{loop_number}:Units", kind="omitted"
    )
    loop_name = FormattedComponent(
        EpicsSignalRO, "{prefix}IN{loop_number}:Name_RBV", kind="omitted"
    )

    def __init__(self, *args, loop_number=None, **kwargs):
        self.loop_number = loop_number
        super().__init__(*args, **kwargs)


class LakeShore336Device(Device):
    """
    LakeShore 336 temperature controller.

    - loop 1: temperature positioner AND heater, PID, & ramp controls
    - loop 2: temperature positioner AND heater, PID, & ramp controls
    - loop 3: temperature positioner
    - loop 4: temperature positioner
    """

    loop1 = FormattedComponent(
        LakeShore336_LoopControl, "{self.prefix}", loop_number=1
    )
    loop2 = FormattedComponent(
        LakeShore336_LoopControl, "{self.prefix}", loop_number=2
    )
    loop3 = FormattedComponent(
        LakeShore336_LoopRO, "{self.prefix}", loop_number=3
    )
    loop4 = FormattedComponent(
        LakeShore336_LoopRO, "{self.prefix}", loop_number=4
    )

    # same names as apstools.synApps._common.EpicsRecordDeviceCommonAll
    scanning_rate = Component(EpicsSignal, "read.SCAN", kind="omitted")
    process_record = Component(EpicsSignal, "read.PROC", kind="omitted")

    read_all = Component(EpicsSignal, "readAll.PROC", kind="omitted")
    serial = Component(AsynRecord, "serial", kind="omitted")


class LS340_LoopBase(PVPositionerSoftDoneWithStop):
    """ Base settings for both sample and control loops. """

    # position
    # readback is provided by PVPositionerSoftDoneWithStop
    # TODO: Polar reports: sometimes the setpoint PV is not updated
    setpoint = FormattedComponent(
        EpicsSignal,
        "{prefix}SP{loop_number}",
        write_pv="{prefix}wr_SP{loop_number}",
        kind="normal",
        put_complete=True,
    )

    # configuration
    units = Component(Signal, value="K", kind="config")

    pid_P = FormattedComponent(
        EpicsSignal,
        "{prefix}P{loop_number}",
        write_pv="{prefix}setPID{loop_number}.AA",
        kind="config",
    )
    pid_I = FormattedComponent(
        EpicsSignal,
        "{prefix}I{loop_number}",
        write_pv="{prefix}setPID{loop_number}.BB",
        kind="config",
    )
    pid_D = FormattedComponent(
        EpicsSignal,
        "{prefix}D{loop_number}",
        write_pv="{prefix}setPID{loop_number}.CC",
        kind="config",
    )

    ramp_rate = FormattedComponent(
        EpicsSignal,
        "{prefix}Ramp{loop_number}",
        write_pv="{prefix}setRamp{loop_number}.BB",
        kind="config",
    )
    ramp_on = FormattedComponent(
        EpicsSignal, "{prefix}Ramp{loop_number}_on", kind="config"
    )

    def __init__(self, *args, loop_number=None, timeout=10 * HOUR, **kwargs):
        self.loop_number = loop_number
        super().__init__(
            *args,
            readback_pv="ignore",
            timeout=timeout,
            tolerance=0.1,
            **kwargs
        )
        self._settle_time = 0

    @property
    def settle_time(self):
        return self._settle_time

    @settle_time.setter
    def settle_time(self, value):
        if value < 0:
            raise ValueError("Settle value needs to be >= 0.")
        else:
            self._settle_time = value

    @property
    def egu(self):
        return self.units.get(as_string=True)

    def pause(self):
        """Change setpoint to current position."""
        self.setpoint.put(self._position, wait=True)


class LS340_LoopControl(LS340_LoopBase):
    """ Control specific """

    readback = Component(EpicsSignalRO, "Control", kind="normal")
    sensor = Component(EpicsSignal, "Ctl_sel", kind="config")


class LS340_LoopSample(LS340_LoopBase):
    """ Sample specific """

    readback = Component(EpicsSignalRO, "Sample", kind="hinted")
    sensor = Component(EpicsSignal, "Spl_sel", kind="config")


class LakeShore340Device(Device):
    """
    LakeShore 340 temperature controller
    """

    control = FormattedComponent(LS340_LoopControl, "{prefix}", loop_number=1)
    sample = FormattedComponent(LS340_LoopSample, "{prefix}", loop_number=2)

    heater = Component(EpicsSignalRO, "Heater")
    heater_range = Component(
        EpicsSignal,
        "Rg_rdbk",
        write_pv="HeatRg",
        kind="normal",
        put_complete=True
    )

    read_pid = Component(EpicsSignal, "readPID.PROC", kind="omitted")

    # same names as apstools.synApps._common.EpicsRecordDeviceCommonAll
    scanning_rate = Component(EpicsSignal, "read.SCAN", kind="omitted")
    process_record = Component(EpicsSignal, "read.PROC", kind="omitted")

    serial = Component(AsynRecord, "serial", kind="omitted")

# -----------------------------------------------------------------------------
# :author:    Pete R. Jemian
# :email:     jemian@anl.gov
# :copyright: (c) 2017-2022, UChicago Argonne, LLC
#
# Distributed under the terms of the Argonne National Laboratory Open Source License.
#
# The full license is in the file LICENSE.txt, distributed with this software.
# -----------------------------------------------------------------------------
