"""Tobii Pro screen-based eye trackers (Spectrum / Fusion / Nano / X-series).

Uses the official `tobii_research` SDK. The SDK delivers gaze via a subscription
callback; we queue samples and yield them on the LSL clock. Lazy import so the
package only needs tobii_research when a Tobii is actually used.
"""

from __future__ import annotations

import queue

from ._base import DriverSource


class TobiiSource(DriverSource):
    def read(self):
        import tobii_research as tr  # lazy: only needed with a Tobii attached
        from pylsl import local_clock
        import time

        trackers = tr.find_all_eyetrackers()
        if not trackers:
            raise RuntimeError("no Tobii eye tracker found on the network/USB")
        tracker = trackers[0]
        if self.opts.get("serial_number"):
            tracker = next(t for t in trackers
                           if t.serial_number == self.opts["serial_number"])

        q: queue.Queue = queue.Queue(maxsize=2000)
        offset = local_clock() - time.time()

        def _cb(g):
            lx, ly = g["left_gaze_point_on_display_area"]
            rx, ry = g["right_gaze_point_on_display_area"]
            x = _avg(lx, rx); y = _avg(ly, ry)
            pl = g["left_pupil_diameter"]; pr = g["right_pupil_diameter"]
            ts = g["device_time_stamp"] / 1e6 + offset  # us -> s, onto LSL clock
            try:
                q.put_nowait(([float(x), float(y), float(pl), float(pr)], ts))
            except queue.Full:
                pass

        tracker.subscribe_to(tr.EYETRACKER_GAZE_DATA, _cb, as_dictionary=True)
        try:
            while not self.stopping:
                try:
                    yield q.get(timeout=0.5)
                except queue.Empty:
                    continue
        finally:
            tracker.unsubscribe_from(tr.EYETRACKER_GAZE_DATA, _cb)


def _avg(a, b):
    vals = [v for v in (a, b) if v == v]   # drop NaN
    return sum(vals) / len(vals) if vals else float("nan")
