from typing import TypedDict

from softioc import builder
from tickit.adapters.epics import EpicsAdapter
from tickit.core.device import Device, DeviceUpdate
from tickit.core.typedefs import SimTime


class PneumaticDevice(Device):
    """Pneumatic Device with movement controls."""

    class Inputs(TypedDict): ...

    #: A typed mapping containing the current output value
    class Outputs(TypedDict):
        output: float

    def __init__(
        self,
        initial_speed: float,
        initial_state: bool,
    ) -> None:
        """Initialise a Pneumatic object."""
        self.speed: float = initial_speed
        self.state: bool = initial_state
        self.moving: bool = False
        self.time_at_last_update: float = 0.0

    def set_speed(self, speed: float) -> None:
        """Set the speed of movement for the device.

        Args:
            speed (float): The desired movement speed of the device.
        """
        self.speed = speed

    def get_speed(self) -> float:
        """Get the speed of movement of the device."""
        return self.speed

    def set_state(self) -> None:
        """Toggles the target state of the device."""
        self.moving = True
        self.target_state = not self.state

    def get_state(self) -> bool:
        """Gets the current state of the device."""
        return self.state

    def update(self, time: SimTime, inputs) -> DeviceUpdate[Outputs]:
        """Run the update logic for the device.

        If the device is moving then the state of the device is updated.
        Otherwise nothing changes. In either case the current state of the
        device is returned.

        Args:
            time (SimTime): The time of the simulation in nanoseconds.
            inputs (dict): The dict containing the input values of state of the device.

        Returns:
            DeviceUpdate: A container for the Device's outputs and a callback time.
        """
        if self.moving:
            callback_period = SimTime(int(1e9 / self.speed))
            self.state = self.target_state
            self.moving = False
            return DeviceUpdate(
                self.Outputs(output=self.state),
                callback_period,
            )
        else:
            return DeviceUpdate(self.Outputs(output=self.state), None)


class PneumaticAdapter(EpicsAdapter):
    """An adapter for the Pneumatic class.

    Connects the device to an external messaging protocol.
    """

    device: PneumaticDevice

    def __init__(self, device: PneumaticDevice) -> None:
        super().__init__()
        self.device = device

    async def callback(self, value) -> None:
        """Set the state of the device and await a response.

        Args:
            value (bool): The value to set the state to.
        """
        self.device.set_state()
        await self.interrupt()

    def on_db_load(self):
        """Adds a record of the current state to the mapping of interrupting records."""
        builder.boolOut("FILTER", initial_value=False, on_update=self.callback)
        self.link_input_on_interrupt(
            builder.boolIn("FILTER_RBV"), self.device.get_state
        )
