import asyncio
import struct
from collections.abc import AsyncIterable
from typing import TypedDict

from tickit.adapters.specifications import RegexCommand
from tickit.adapters.tcp import CommandAdapter
from tickit.core.device import Device, DeviceUpdate
from tickit.core.typedefs import SimTime

from tickit_devices.cryostream.base import CryostreamBase
from tickit_devices.cryostream.states import PhaseIds

_EXTENDED_STATUS = ">BBHHHBBHHHHHBBBBBBHHBBBBBBBBHH"


class CryostreamDevice(Device, CryostreamBase):
    """A Cryostream device, used for cooling of samples using cold gas."""

    #: An empty typed mapping of device inputs
    class Inputs(TypedDict): ...

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

    def __init__(self) -> None:
        """A Cryostream constructor sets up initial internal values."""
        super().__init__()
        self.phase_id: int = PhaseIds.HOLD.value
        self.callback_period: SimTime = SimTime(int(1e9))

    def update(self, time: SimTime, inputs: Inputs) -> DeviceUpdate[Outputs]:
        """The update method which changes the output temperature according to set
        modes.

        Returns:
            DeviceUpdate[Outputs]:
                The produced update event which contains the value of the output
                temperature, and requests callback if temperature should continue to
                change.
        """
        if self.phase_id in (PhaseIds.RAMP.value, PhaseIds.COOL.value):
            self.gas_temp = self.update_temperature(time)
            return DeviceUpdate(
                self.Outputs(temperature=self.gas_temp),
                SimTime(time + self.callback_period),
            )
        if self.phase_id == PhaseIds.PLAT.value:
            self.phase_id = PhaseIds.HOLD.value
            return DeviceUpdate(
                self.Outputs(temperature=self.gas_temp),
                SimTime(time + int(self.plat_duration * 1e10)),
            )
        return DeviceUpdate(self.Outputs(temperature=self.gas_temp), None)


class CryostreamAdapter(CommandAdapter):
    """A Cryostream TCP adapter which sends regular status packets and can set modes."""

    device: CryostreamDevice

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

    async def on_connect(self) -> AsyncIterable[bytes]:
        """A method which continiously yields status packets.

        Returns:
            AsyncIterable[bytes]: An asyncronous iterable of packed Cryostream status
                packets.
        """
        while True:
            await asyncio.sleep(2.0)
            await self.device.set_status_format(1)
            status = await self.device.get_status(1)
            yield status.pack()

    @RegexCommand(b"\\x02\\x0a", interrupt=True)
    async def restart(self) -> None:
        """A regex bytes command which restarts the Cryostream."""
        await self.device.restart()

    @RegexCommand(b"\\x02\\x0d", interrupt=True)
    async def hold(self) -> None:
        """A regex bytes command which holds the current temperature."""
        await self.device.hold()

    @RegexCommand(b"\\x02\\x10", interrupt=True)
    async def purge(self) -> None:
        """A regex bytes command which purges (immediately raise to 300K)."""
        await self.device.purge()

    @RegexCommand(b"\\x02\\x11", interrupt=True)
    async def pause(self) -> None:
        """A regex bytes command which pauses."""
        await self.device.pause()

    @RegexCommand(b"\\x02\\x12", interrupt=True)
    async def resume(self) -> None:
        """A regex bytes command which resumes the last command."""
        await self.device.resume()

    @RegexCommand(b"\\x02\\x13", interrupt=True)
    async def stop(self) -> None:
        """A regex bytes command which stops gas flow."""
        await self.device.stop()

    @RegexCommand(b"\\x03\\x14([\\x00\\x01])", interrupt=True)
    async def turbo(self, turbo_on: bytes) -> None:
        """A regex bytes command which enables / disables turbo mode.

        Args:
            turbo_on (bytes): The desired turbo mode, where 0 denotes off and 1 denotes
                on.
        """
        turbo_on = struct.unpack(">B", turbo_on)[0]
        await self.device.turbo(turbo_on)  # type: ignore

    # Todo set status format not interrupt
    @RegexCommand(b"\\x03\\x28([\\x00\\x01])", interrupt=False)
    async def set_status_format(self, status_format: bytes) -> None:
        """A regex bytes command which sets the status packet format.

        Args:
            status_format (bytes): The status packet format, where 0 denotes a standard
                status packet and 1 denotes an extended status packet.
        """
        status_format = struct.unpack(">B", status_format)[0]
        await self.device.set_status_format(status_format)  # type: ignore

    @RegexCommand(b"\\x04\\x0c(.{2})", interrupt=True)
    async def plat(self, duration: bytes) -> None:
        """A regex bytes command which maintains temperature for a set amount of time.

        Args:
            duration (bytes): The duration for which the temperature should be held.
        """
        duration = struct.unpack(">H", duration)[0]
        await self.device.plat(duration)  # type: ignore

    @RegexCommand(b"\\x04\\x0f(.{2})", interrupt=True)
    async def end(self, ramp_rate: bytes) -> None:
        """A regex bytes command which brings the gas temperature to 300 K at ramp rate.

        Args:
            ramp_rate (bytes): The rate at which the temperature should change.
        """
        ramp_rate = struct.unpack(">H", ramp_rate)[0]
        await self.device.end(ramp_rate)  # type: ignore

    @RegexCommand(b"\\x04\\x0e(.{2})", interrupt=True)
    async def cool(self, target_temp: bytes) -> None:
        """A regex bytes command which makes decreases temperature to a set value.

        Args:
            target_temp (bytes): The target temperature.
        """
        target_temp = struct.unpack(">H", target_temp)[0]
        await self.device.cool(target_temp)  # type: ignore

    @RegexCommand(b"\\x06\\x0b(.{2,4})", interrupt=True)
    async def ramp(self, values: bytes) -> None:
        """Change gas temperature to a set value at a controlled rate.

        Args:
            values (bytes): The rate at which the temperature should change and the
                target temperature.
        """
        ramp_rate, target_temp = struct.unpack(">HH", values)
        await self.device.ramp(ramp_rate, target_temp)  # type: ignore
