import asyncio
import logging
from ctypes import c_int8, c_int16, c_int32, c_float, byref
from datetime import datetime, timezone, timedelta

from datalogd import DataSource

try:
    from picosdk.usbtc08 import usbtc08 as tc08
except ModuleNotFoundError:
    log = logging.getLogger(__name__.rpartition(".")[2])
    log.warning("picosdk module not found. Install it with \"pip install picosdk\" or similar.")
else:
    class PicoTC08DataSource(DataSource):
        """
        Obtain readings from a Pico Technologies TC-08 USB data logging device.

        The drivers and libraries (such as libusbtc08.so on Linux, usbtc08.dll on Windows) from PicoTech
        must be installed into a system library directory, and the ``picosdk`` python wrappers package
        must be on the system (with ``pip install picosdk`` or similar).

        The ``interval`` parameter determines how often data will be obtained from the sensors, in
        seconds. The minimum interval time is about 0.2 s for a single probe and 0.9 s for all eight.

        The ``mains_rejection`` parameter filters out either 50 Hz or 60 Hz interference from mains
        power. The frequency is selected as either ``"50Hz"`` or ``"60Hz"``.

        The ``probes`` parameter is a list of probe to initialise. Each element is itself a list of
        the form ``[number, label, type, units]``, where probe numbers are unique integers from 1 to
        8 corresponding to an input channel on the device. Probe labels can be any valid string.
        Valid probe thermocouple types are ``"B"``, ``"E"``, ``"J"``, ``"K"``, ``"N"``, ``"R"``,
        ``"S"``, ``"T"``, or ``"X"``, where ``"X""`` indicates a raw voltage reading. Units are one
        of Celsius, Fahrenheit, Kelvin, Rankine specified as ``"C"``, ``"F"``, ``"K"`` or ``"R"``.
        For the ``"X"`` probe type, readings will always be returned in millivolts.
        """
        def __init__(self, sinks=[], interval=1.0, mains_rejection="50Hz", probes=[
                [1, "Channel_1", "K", "C"],
                [2, "Channel_2", "K", "C"],
                [3, "Channel_3", "K", "C"],
                [4, "Channel_4", "K", "C"],
                [5, "Channel_5", "K", "C"],
                [6, "Channel_6", "K", "C"],
                [7, "Channel_7", "K", "C"],
                [8, "Channel_8", "K", "C"],
            ]):
            super().__init__(sinks=sinks)
            self._log = logging.getLogger("PicoTC08")
            self._log.info("Initialising Pico TC-08 device.")
            self._handle = tc08.usb_tc08_open_unit()
            if self._handle <= 0:
                raise RuntimeError("Unable to initialise Pico TC-08 USB device.")
            # Set mains power frequency rejection
            tc08.usb_tc08_set_mains(self._handle, 1 if mains_rejection == "60Hz" else 0)
            # Configure each specified channel, keep configured probes in a dictionary by channel
            self._probes = {}
            for i, probe in enumerate(probes):
                try:
                    probe_n = int(probe[0])
                    probe_label = str(probe[1])
                    probe_type = str(probe[2])[0]
                    probe_units = str(probe[3])[0]
                    status = tc08.usb_tc08_set_channel(self._handle, probe_n, ord(probe_type))
                    if status == 1:
                        self._probes[probe_n] = {"type": probe_type, "label": probe_label, "units": probe_units}
                        self._log.info(f"Configured TC-08 channel {probe_n} as type {probe_type}.")
                    else:
                        self._log.error(f"Error configuring TC-08 channel {probe_n} as type {probe_type}.")
                except Exception as ex:
                    self._log.exception("Error configuring TC-08 channel using probe configuration \"{probe}\"")
            # Should check that at least one probe was configured...
            if len(self._probes) <= 0:
                tc08.usb_tc08_close_unit(self._handle)
                self._handle = 0
                raise RuntimeError("No TC-08 channels were successfully configured.")
            # Get minimum possible sampling interval, in ms
            self._interval = max(int(1000*interval), tc08.usb_tc08_get_minimum_interval_ms(self._handle))
            # Start the device
            interval = tc08.usb_tc08_run(self._handle, self._interval)
            if interval == 0:
                tc08.usb_tc08_close_unit(self._handle)
                self._handle = 0
                raise RuntimeError("Error starting Pico TC-08 device.")
            else:
                self._interval = interval/1000
            # Queue first call of update routine
            asyncio.get_event_loop().call_soon(self.acquire_data)
            # Record local time of acquisition start
            self._time_start = datetime.now(timezone.utc).astimezone()


        def acquire_data(self):
            """
            Acquire data from the TC-08 streaming buffer.
            """
            loop = asyncio.get_running_loop()
            data = []
            for probe_n, probe in self._probes.items():
                temps = (c_float * 128)()
                times = (c_int32 * 128)()
                overflow = c_int16()
                count = tc08.usb_tc08_get_temp(
                    self._handle,
                    byref(temps),
                    byref(times),
                    128,
                    byref(overflow),
                    probe_n,
                    "CFKR".index(probe["units"]) if probe["units"] in "CFKR" else 0,
                    1)
                if count < 0:
                    self._log.error("Error acquiring data from Pico TC-08.")
                    tc08.usb_tc08_close_unit(self._handle)
                    self._handle = 0
                    return
                # Loop through each record
                for i in range(count):
                    data.append({
                        "timestamp" : (self._time_start + timedelta(milliseconds=times[i])).isoformat(),
                        "type" : "analog" if probe["type"] == "X" else "temperature",
                        "source" : "TC-08",
                        "id" : probe_n,
                        "label" : probe["label"],
                        "probe" : probe["type"],
                        "units" : "mV" if probe["type"] == "X" else probe["units"],
                        "value": temps[i]})
            self.send(data)
            # Reschedule next update
            loop.call_later(self._interval, self.acquire_data)


        def close(self):
            """
            Close the connection to the Pico TC-08 device.
            """
            if self._handle:
                tc08.usb_tc08_close_unit(self._handle)
                self._handle = 0

