import logging
from dataclasses import fields

from apischema import serialize
from typing_extensions import TypedDict

from tickit.core.device import Device, DeviceUpdate
from tickit.core.typedefs import SimTime
from tickit.devices.eiger.data.dummy_image import Image
from tickit.devices.eiger.eiger_schema import AccessMode, Value
from tickit.devices.eiger.eiger_settings import EigerSettings
from tickit.devices.eiger.filewriter.filewriter_config import FileWriterConfig
from tickit.devices.eiger.filewriter.filewriter_status import FileWriterStatus
from tickit.devices.eiger.monitor.monitor_config import MonitorConfig
from tickit.devices.eiger.monitor.monitor_status import MonitorStatus
from tickit.devices.eiger.stream.stream_config import StreamConfig
from tickit.devices.eiger.stream.stream_status import StreamStatus

from .eiger_status import EigerStatus, State

LOGGER = logging.getLogger(__name__)


class EigerDevice(Device):
    """A device class for the Eiger detector."""

    settings: EigerSettings
    status: EigerStatus

    #: An empty typed mapping of input values
    Inputs: TypedDict = TypedDict("Inputs", {"flux": float})
    #: A typed mapping containing the 'value' output value
    Outputs: TypedDict = TypedDict("Outputs", {})

    def __init__(
        self,
    ) -> None:
        """An Eiger device constructor.

        An Eiger device constructor which configures the default settings and various
        states of the device.
        """
        self.settings = EigerSettings()
        self.status = EigerStatus()

        self.stream_status = StreamStatus()
        self.stream_config = StreamConfig()
        self.stream_callback_period = SimTime(int(1e9))

        self.filewriter_status: FileWriterStatus = FileWriterStatus()
        self.filewriter_config: FileWriterConfig = FileWriterConfig()
        self.filewriter_callback_period = SimTime(int(1e9))

        self.monitor_status: MonitorStatus = MonitorStatus()
        self.monitor_config: MonitorConfig = MonitorConfig()
        self.monitor_callback_period = SimTime(int(1e9))

    async def initialize(self) -> None:
        """Function to initialise the Eiger."""
        self._set_state(State.IDLE)

    async def arm(self) -> None:
        """Function to arm the Eiger."""
        self._set_state(State.READY)

        header_detail = self.stream_config["header_detail"]["value"]

        json = {
            "htype": "dheader-1.0",
            "series": "<id>",
            "header_detail": header_detail,
        }
        if header_detail != "none":
            config_json = {}
            disallowed_configs = ["flatfield", "pixelmask" "countrate_correction_table"]
            for field_ in fields(self.settings):
                if field_.name not in disallowed_configs:
                    config_json[field_.name] = vars(self.settings)[field_.name]

        LOGGER.debug(json)
        LOGGER.debug(config_json)

    async def disarm(self) -> None:
        """Function to disarm the Eiger."""
        self._set_state(State.IDLE)

        json = {"htype": "dseries_end-1.0", "series": "<id>"}

        LOGGER.debug(json)

    async def trigger(self) -> str:
        """Function to trigger the Eiger.

        If the detector is in an external trigger mode, this is disabled as
        this software command interface only works for internal triggers.
        """
        trigger_mode = self.settings.trigger_mode
        state = self.status.state

        if state == State.READY and trigger_mode == "ints":
            self._set_state(State.ACQUIRE)

            for idx in range(0, self.settings.nimages):

                aquired = Image.create_dummy_image(idx)

                header_json = {
                    "htype": "dimage-1.0",
                    "series": "<series id>",
                    "frame": aquired.index,
                    "hash": aquired.hash,
                }

                json2 = {
                    "htype": "dimage_d-1.0",
                    "shape": "[x,y,(z)]",
                    "type": aquired.dtype,
                    "encoding": aquired.encoding,
                    "size": len(aquired.data),
                }

                json3 = {
                    "htype": "dconfig-1.0",
                    "start_time": "<start_time>",
                    "stop_time": "<stop_time>",
                    "real_time": "<real_time>",
                }

                LOGGER.debug(header_json)
                LOGGER.debug(json2)
                LOGGER.debug(json3)

            return "Aquiring Data from Eiger..."
        else:
            return (
                f"Ignoring trigger, state={self.status.state},"
                f"trigger_mode={trigger_mode}"
            )

    async def cancel(self) -> None:
        """Function to stop the data acquisition.

        Function to stop the data acquisition, but only after the next
        image is finished.
        """
        self._set_state(State.READY)

        header_json = {"htype": "dseries_end-1.0", "series": "<id>"}

        LOGGER.debug(header_json)

    async def abort(self) -> None:
        """Function to abort the current task on the Eiger."""
        self._set_state(State.IDLE)

        header_json = {"htype": "dseries_end-1.0", "series": "<id>"}

        LOGGER.debug(header_json)

    def update(self, time: SimTime, inputs: Inputs) -> DeviceUpdate[Outputs]:
        """Generic update function to update the values of the ExampleHTTPDevice.

        Args:
            time (SimTime): The simulation time in nanoseconds.
            inputs (Inputs): A TypedDict of the inputs to the ExampleHTTPDevice.

        Returns:
            DeviceUpdate[Outputs]:
                The produced update event which contains the value of the device
                variables.
        """
        current_flux = inputs["flux"]

        intensity_scale = (current_flux / 100) * 100
        LOGGER.debug(f"Relative beam intensity: {intensity_scale}")

        return DeviceUpdate(self.Outputs(), None)

    def get_state(self):  # TODO: Add return type hint
        """Returns the current state of the Eiger.

        Returns:
            State: The state of the Eiger.
        """
        val = self.status.state
        allowed = [s.value for s in State]
        return serialize(
            Value(
                val,
                AccessMode.STRING,  # type: ignore
                access_mode=AccessMode.READ_ONLY,
                allowed_values=allowed,
            )
        )

    def _set_state(self, state: State):
        self.status.state = state

import logging
from dataclasses import fields

from apischema import serialize
from typing_extensions import TypedDict

from tickit.core.device import Device, DeviceUpdate
from tickit.core.typedefs import SimTime
from tickit.devices.eiger.data.dummy_image import Image
from tickit.devices.eiger.eiger_schema import AccessMode, Value
from tickit.devices.eiger.eiger_settings import EigerSettings
from tickit.devices.eiger.filewriter.filewriter_config import FileWriterConfig
from tickit.devices.eiger.filewriter.filewriter_status import FileWriterStatus
from tickit.devices.eiger.monitor.monitor_config import MonitorConfig
from tickit.devices.eiger.monitor.monitor_status import MonitorStatus
from tickit.devices.eiger.stream.stream_config import StreamConfig
from tickit.devices.eiger.stream.stream_status import StreamStatus

from .eiger_status import EigerStatus, State

LOGGER = logging.getLogger(__name__)


class EigerDevice(Device):
    """A device class for the Eiger detector."""

    settings: EigerSettings
    status: EigerStatus

    #: An empty typed mapping of input values
    Inputs: TypedDict = TypedDict("Inputs", {"flux": float})
    #: A typed mapping containing the 'value' output value
    Outputs: TypedDict = TypedDict("Outputs", {})

    def __init__(
        self,
    ) -> None:
        """An Eiger device constructor.

        An Eiger device constructor which configures the default settings and various
        states of the device.
        """
        self.settings = EigerSettings()
        self.status = EigerStatus()

        self.stream_status = StreamStatus()
        self.stream_config = StreamConfig()
        self.stream_callback_period = SimTime(int(1e9))

        self.filewriter_status: FileWriterStatus = FileWriterStatus()
        self.filewriter_config: FileWriterConfig = FileWriterConfig()
        self.filewriter_callback_period = SimTime(int(1e9))

        self.monitor_status: MonitorStatus = MonitorStatus()
        self.monitor_config: MonitorConfig = MonitorConfig()
        self.monitor_callback_period = SimTime(int(1e9))

    async def initialize(self) -> None:
        """Function to initialise the Eiger."""
        self._set_state(State.IDLE)

    async def arm(self) -> None:
        """Function to arm the Eiger."""
        self._set_state(State.READY)

        header_detail = self.stream_config["header_detail"]["value"]

        json = {
            "htype": "dheader-1.0",
            "series": "<id>",
            "header_detail": header_detail,
        }
        if header_detail != "none":
            config_json = {}
            disallowed_configs = ["flatfield", "pixelmask" "countrate_correction_table"]
            for field_ in fields(self.settings):
                if field_.name not in disallowed_configs:
                    config_json[field_.name] = vars(self.settings)[field_.name]

        LOGGER.debug(json)
        LOGGER.debug(config_json)

    async def disarm(self) -> None:
        """Function to disarm the Eiger."""
        self._set_state(State.IDLE)

        json = {"htype": "dseries_end-1.0", "series": "<id>"}

        LOGGER.debug(json)

    async def trigger(self) -> str:
        """Function to trigger the Eiger.

        If the detector is in an external trigger mode, this is disabled as
        this software command interface only works for internal triggers.
        """
        trigger_mode = self.settings.trigger_mode
        state = self.status.state

        if state == State.READY and trigger_mode == "ints":
            self._set_state(State.ACQUIRE)

            for idx in range(0, self.settings.nimages):

                aquired = Image.create_dummy_image(idx)

                header_json = {
                    "htype": "dimage-1.0",
                    "series": "<series id>",
                    "frame": aquired.index,
                    "hash": aquired.hash,
                }

                json2 = {
                    "htype": "dimage_d-1.0",
                    "shape": "[x,y,(z)]",
                    "type": aquired.dtype,
                    "encoding": aquired.encoding,
                    "size": len(aquired.data),
                }

                json3 = {
                    "htype": "dconfig-1.0",
                    "start_time": "<start_time>",
                    "stop_time": "<stop_time>",
                    "real_time": "<real_time>",
                }

                LOGGER.debug(header_json)
                LOGGER.debug(json2)
                LOGGER.debug(json3)

            return "Aquiring Data from Eiger..."
        else:
            return (
                f"Ignoring trigger, state={self.status.state},"
                f"trigger_mode={trigger_mode}"
            )

    async def cancel(self) -> None:
        """Function to stop the data acquisition.

        Function to stop the data acquisition, but only after the next
        image is finished.
        """
        self._set_state(State.READY)

        header_json = {"htype": "dseries_end-1.0", "series": "<id>"}

        LOGGER.debug(header_json)

    async def abort(self) -> None:
        """Function to abort the current task on the Eiger."""
        self._set_state(State.IDLE)

        header_json = {"htype": "dseries_end-1.0", "series": "<id>"}

        LOGGER.debug(header_json)

    def update(self, time: SimTime, inputs: Inputs) -> DeviceUpdate[Outputs]:
        """Generic update function to update the values of the ExampleHTTPDevice.

        Args:
            time (SimTime): The simulation time in nanoseconds.
            inputs (Inputs): A TypedDict of the inputs to the ExampleHTTPDevice.

        Returns:
            DeviceUpdate[Outputs]:
                The produced update event which contains the value of the device
                variables.
        """
        current_flux = inputs["flux"]

        intensity_scale = (current_flux / 100) * 100
        LOGGER.debug(f"Relative beam intensity: {intensity_scale}")

        return DeviceUpdate(self.Outputs(), None)

    def get_state(self):  # TODO: Add return type hint
        """Returns the current state of the Eiger.

        Returns:
            State: The state of the Eiger.
        """
        val = self.status.state
        allowed = [s.value for s in State]
        return serialize(
            Value(
                val,
                AccessMode.STRING,  # type: ignore
                access_mode=AccessMode.READ_ONLY,
                allowed_values=allowed,
            )
        )

    def _set_state(self, state: State):
        self.status.state = state

A device class for the Eiger detector. #: An empty typed mapping of input values #: A typed mapping containing the 'value' output value An Eiger device constructor.          An Eiger device constructor which configures the default settings and various         states of the device. Function to initialise the Eiger. Function to arm the Eiger. Function to disarm the Eiger. Function to trigger the Eiger.          If the detector is in an external trigger mode, this is disabled as         this software command interface only works for internal triggers. Function to stop the data acquisition.          Function to stop the data acquisition, but only after the next         image is finished. Function to abort the current task on the Eiger. Generic update function to update the values of the ExampleHTTPDevice.          Args:             time (SimTime): The simulation time in nanoseconds.             inputs (Inputs): A TypedDict of the inputs to the ExampleHTTPDevice.          Returns:             DeviceUpdate[Outputs]:                 The produced update event which contains the value of the device                 variables. # TODO: Add return type hint Returns the current state of the Eiger.          Returns:             State: The state of the Eiger. # type: ignore