# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2015-2023 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.

"""CT2 (P201/C208) ESRF counter card

CT2 (P201/C208) ESRF counter card TANGO device
"""

__all__ = ["CT2", "main"]

import logging
import numpy

from PyTango import Util, GreenMode
from PyTango import DevState
from PyTango.server import Device
from PyTango.server import attribute, command
from PyTango.server import device_property

from bliss.config.static import get_config
from bliss.controllers.ct2.card import BaseCard
from bliss.controllers.ct2.device import AcqMode, AcqStatus, DataSignal
from bliss.common import event


def switch_state(tg_dev, state=None, status=None):
    """Helper to switch state and/or status and send event"""
    if state is not None:
        tg_dev.set_state(state)
        if state in (DevState.ALARM, DevState.UNKNOWN, DevState.FAULT):
            msg = "State changed to " + str(state)
            if status is not None:
                msg += ": " + status
    if status is not None:
        tg_dev.set_status(status)


def _to_enum(value, etype):
    if isinstance(value, str):
        return etype[value]
    return etype(value)


class CT2(Device):
    """
    CT2 (P201/C208) ESRF counter card TANGO device
    """

    card_name = device_property(dtype="str", default_value="p201")

    def __init__(self, *args, **kwargs):
        Device.__init__(self, *args, **kwargs)
        self.__buffer = []

    def init_device(self):
        Device.init_device(self)
        self.device = None

        try:
            config = get_config()
            util = Util.instance()
            if not util.is_svr_starting():
                config.reload()
            self.device = config.get(self.card_name)
            if isinstance(self.device, BaseCard):
                raise ValueError("ct2 card config is not supported anymore")

            switch_state(self, DevState.ON, "Ready!")
        except Exception as e:
            msg = "Exception initializing device: {0}".format(e)
            self.error_stream(msg)
            switch_state(self, DevState.FAULT, msg)

    def delete_device(self):
        if self.device:
            self.device.stop_acq()

    @attribute(dtype="str", label="Last error")
    def last_error(self):
        return self.device.last_error or ""

    @attribute(
        dtype="int32", label="Last point nb.", doc="Last acquisition point ready"
    )
    def last_point_nb(self):
        return self.device.last_point_nb

    @attribute(
        dtype="str",
        label="Acq. mode",
        memorized=True,
        hw_memorized=True,
        doc="Acquisition mode (supported: 'IntTrigReadout', "
        "'SoftTrigReadout', "
        "'IntTrigMulti')",
    )
    def acq_mode(self):
        return _to_enum(self.device.acq_mode, AcqMode).name

    @acq_mode.setter
    def acq_mode(self, acq_mode):
        self.device.acq_mode = AcqMode[acq_mode]

    @attribute(dtype="str", label="Acq. status", doc="Acquisition status")
    def acq_status(self):
        return _to_enum(self.device.acq_status, AcqStatus).name

    @attribute(
        dtype="float64",
        label="Acq. expo. time",
        unit="s",
        standard_unit="s",
        display_unit="s",
        format="%6.3f",
        memorized=True,
        hw_memorized=True,
        doc="Acquisition exposition time (s)",
    )
    def acq_expo_time(self):
        return self.device.acq_expo_time

    @acq_expo_time.setter
    def acq_expo_time(self, acq_expo_time):
        self.device.acq_expo_time = acq_expo_time

    @attribute(
        dtype="float64",
        label="Acq. expo. time",
        unit="s",
        standard_unit="s",
        display_unit="s",
        format="%6.3f",
        memorized=True,
        hw_memorized=True,
        doc="Acquisition point period (s)",
    )
    def acq_point_period(self):
        return self.device.acq_point_period

    @acq_point_period.setter
    def acq_point_period(self, acq_point_period):
        self.device.acq_point_period = acq_point_period

    @attribute(
        dtype="uint32",
        label="Acq. nb. points",
        memorized=True,
        hw_memorized=True,
        doc="Number of points per acquisition ",
    )
    def acq_nb_points(self):
        return self.device.acq_nb_points

    @acq_nb_points.setter
    def acq_nb_points(self, acq_nb_points):
        self.device.acq_nb_points = acq_nb_points

    @attribute(
        dtype=("int16",),
        max_dim_x=12,
        label="Active channels",
        doc="List of active channels (first is 1)",
    )
    def acq_channels(self):
        return tuple(self.device.acq_channels)

    @acq_channels.setter
    def acq_channels(self, acq_channels):
        self.device.acq_channels = acq_channels

    @attribute(
        dtype=("int16",),
        max_dim_x=12,
        label="Active channel indexes in data point",
        doc="List of indexes in data point (first is 0)",
    )
    def acq_channels_data_indexes(self):
        return tuple(self.device.acq_channels_data_indexes)

    @attribute(
        dtype=("int16",),
        max_dim_x=12,
        label="Integrator channels",
        doc="List indicating if corresponding acq_channel is integrator",
    )
    def integrator_channels(self):
        return tuple(self.device.integrator_channels)

    @integrator_channels.setter
    def integrator_channels(self, integrator_channels):
        self.device.integrator_channels = integrator_channels

    @attribute(
        dtype="float64",
        label="Timer clock freq.",
        unit="Hz",
        standard_unit="Hz",
        display_unit="Hz",
        format="%10.3g",
        memorized=True,
        hw_memorized=True,
        doc="Timer clock frequency (Hz)",
    )
    def timer_freq(self):
        return self.device.timer_freq

    @timer_freq.setter
    def timer_freq(self, timer_freq):
        self.device.timer_freq = timer_freq

    @attribute(dtype=("uint32",), max_dim_x=12)
    def counters(self):
        return self.device.counter_values

    @attribute(dtype=("uint32",), max_dim_x=12)
    def latches(self):
        return self.device.latches

    @attribute(dtype=(("uint32",),), max_dim_x=65535, max_dim_y=65535)
    def data(self):
        b = self.__buffer
        if b:
            self.__buffer = []
            data = numpy.vstack(b)
        else:
            data = numpy.array([[]], dtype=numpy.uint32)
        return data

    @attribute(dtype=(int,), max_dim_x=12)
    def counters_status(self):
        status = self.device.get_counters_status()
        return [
            int(status[i]["enable"]) | (int(status[i]["run"]) << 1)
            for i in sorted(status)
        ]

    @command
    def reset(self):
        self.device.reset()

    @command(
        dtype_in=("uint16",), doc_in="list of counters to latch (first counter == 1)"
    )
    def trigger_latch(self, counters):
        self.device.trigger_latch(counters)

    @command
    def prepare_acq(self):
        self.__buffer = []
        self.device.prepare_acq()

    @command
    def start_acq(self):
        event.connect(self.device.server, DataSignal, self._rx_data)
        self.device.start_acq()

    def _rx_data(self, data, signal):
        self.__buffer.extend(data)

    @command
    def stop_acq(self):
        self.device.stop_acq()
        event.disconnect(self.device.server, DataSignal, self._rx_data)

    @command
    def trigger_point(self):
        self.device.trigger_point()

    @command(dtype_out="DevVarCharArray")
    def dump_memory(self):
        data = self.device.dump_memory()
        return numpy.ndarray(shape=(len(data),), dtype=numpy.uint8, buffer=data)


def main(args=None, **kwargs):
    from PyTango.server import run

    logging.basicConfig(
        level=logging.INFO, format="%(levelname)s %(asctime)-15s %(name)s: %(message)s"
    )

    kwargs["green_mode"] = kwargs.get("green_mode", GreenMode.Gevent)
    return run((CT2,), args=args, **kwargs)


if __name__ == "__main__":
    main()
