# This file was generated by the "yardl" tool. DO NOT EDIT.

# pyright: reportUnusedImport=false
# pyright: reportUnknownArgumentType=false
# pyright: reportUnknownMemberType=false
# pyright: reportUnknownVariableType=false

import datetime
import enum
import types
import typing

import numpy as np
import numpy.typing as npt

from . import yardl_types as yardl
from . import _dtypes

class CoincidenceEvent:
    """All information about a coincidence event specified as identifiers or indices (i.e. discretized).
    TODO: this might take up too much space, so some/all of these could be combined in a single index if necessary.
    """

    detector_1_id: yardl.UInt32
    detector_2_id: yardl.UInt32
    tof_idx: yardl.UInt32
    energy_1_idx: yardl.UInt32
    energy_2_idx: yardl.UInt32

    def __init__(self, *,
        detector_1_id: yardl.UInt32 = 0,
        detector_2_id: yardl.UInt32 = 0,
        tof_idx: yardl.UInt32 = 0,
        energy_1_idx: yardl.UInt32 = 0,
        energy_2_idx: yardl.UInt32 = 0,
    ):
        self.detector_1_id = detector_1_id
        self.detector_2_id = detector_2_id
        self.tof_idx = tof_idx
        self.energy_1_idx = energy_1_idx
        self.energy_2_idx = energy_2_idx

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, CoincidenceEvent)
            and self.detector_1_id == other.detector_1_id
            and self.detector_2_id == other.detector_2_id
            and self.tof_idx == other.tof_idx
            and self.energy_1_idx == other.energy_1_idx
            and self.energy_2_idx == other.energy_2_idx
        )

    def __str__(self) -> str:
        return f"CoincidenceEvent(detector1Id={self.detector_1_id}, detector2Id={self.detector_2_id}, tofIdx={self.tof_idx}, energy1Idx={self.energy_1_idx}, energy2Idx={self.energy_2_idx})"

    def __repr__(self) -> str:
        return f"CoincidenceEvent(detector1Id={repr(self.detector_1_id)}, detector2Id={repr(self.detector_2_id)}, tofIdx={repr(self.tof_idx)}, energy1Idx={repr(self.energy_1_idx)}, energy2Idx={repr(self.energy_2_idx)})"


class Subject:
    name: typing.Optional[str]
    id: str

    def __init__(self, *,
        name: typing.Optional[str] = None,
        id: str = "",
    ):
        self.name = name
        self.id = id

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, Subject)
            and self.name == other.name
            and self.id == other.id
        )

    def __str__(self) -> str:
        return f"Subject(name={self.name}, id={self.id})"

    def __repr__(self) -> str:
        return f"Subject(name={repr(self.name)}, id={repr(self.id)})"


class Institution:
    name: str
    address: str

    def __init__(self, *,
        name: str = "",
        address: str = "",
    ):
        self.name = name
        self.address = address

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, Institution)
            and self.name == other.name
            and self.address == other.address
        )

    def __str__(self) -> str:
        return f"Institution(name={self.name}, address={self.address})"

    def __repr__(self) -> str:
        return f"Institution(name={repr(self.name)}, address={repr(self.address)})"


class ExamInformation:
    """Items describing the exam (incomplete)"""

    subject: Subject
    institution: Institution
    protocol: typing.Optional[str]
    start_of_acquisition: typing.Optional[yardl.DateTime]

    def __init__(self, *,
        subject: typing.Optional[Subject] = None,
        institution: typing.Optional[Institution] = None,
        protocol: typing.Optional[str] = None,
        start_of_acquisition: typing.Optional[yardl.DateTime] = None,
    ):
        self.subject = subject if subject is not None else Subject()
        self.institution = institution if institution is not None else Institution()
        self.protocol = protocol
        self.start_of_acquisition = start_of_acquisition

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, ExamInformation)
            and self.subject == other.subject
            and self.institution == other.institution
            and self.protocol == other.protocol
            and self.start_of_acquisition == other.start_of_acquisition
        )

    def __str__(self) -> str:
        return f"ExamInformation(subject={self.subject}, institution={self.institution}, protocol={self.protocol}, startOfAcquisition={self.start_of_acquisition})"

    def __repr__(self) -> str:
        return f"ExamInformation(subject={repr(self.subject)}, institution={repr(self.institution)}, protocol={repr(self.protocol)}, startOfAcquisition={repr(self.start_of_acquisition)})"


class Detector:
    """Detector ID and location. Units are in mm
    TODO: this is currently just a sample implementation with "point" detectors.
    We plan to have full shape information here.
    """

    id: yardl.UInt32
    x: yardl.Float32
    y: yardl.Float32
    z: yardl.Float32

    def __init__(self, *,
        id: yardl.UInt32 = 0,
        x: yardl.Float32 = 0.0,
        y: yardl.Float32 = 0.0,
        z: yardl.Float32 = 0.0,
    ):
        self.id = id
        self.x = x
        self.y = y
        self.z = z

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, Detector)
            and self.id == other.id
            and self.x == other.x
            and self.y == other.y
            and self.z == other.z
        )

    def __str__(self) -> str:
        return f"Detector(id={self.id}, x={self.x}, y={self.y}, z={self.z})"

    def __repr__(self) -> str:
        return f"Detector(id={repr(self.id)}, x={repr(self.x)}, y={repr(self.y)}, z={repr(self.z)})"


class ScannerInformation:
    model_name: typing.Optional[str]
    detectors: list[Detector]
    tof_bin_edges: npt.NDArray[np.float32]
    """edge information for TOF bins in mm (given as from first to last edge, so there is one more edge than the number of bins)
    TODO: this currently assumes equal size for each TOF bin, but some scanners "stretch" TOF bins depending on length of LOR
    """

    tof_resolution: yardl.Float32
    """TOF resolution in mm"""

    energy_bin_edges: npt.NDArray[np.float32]
    """edge information for energy windows in keV (given as from first to last edge, so there is one more edge than the number of bins)"""

    energy_resolution_at_511: yardl.Float32
    """FWHM of photopeak for incoming gamma of 511 keV, expressed as a ratio w.r.t. 511"""

    listmode_time_block_duration: yardl.UInt32
    """duration of each time block in ms"""


    def __init__(self, *,
        model_name: typing.Optional[str] = None,
        detectors: typing.Optional[list[Detector]] = None,
        tof_bin_edges: typing.Optional[npt.NDArray[np.float32]] = None,
        tof_resolution: yardl.Float32 = 0.0,
        energy_bin_edges: typing.Optional[npt.NDArray[np.float32]] = None,
        energy_resolution_at_511: yardl.Float32 = 0.0,
        listmode_time_block_duration: yardl.UInt32 = 0,
    ):
        self.model_name = model_name
        self.detectors = detectors if detectors is not None else []
        self.tof_bin_edges = tof_bin_edges if tof_bin_edges is not None else np.zeros((0), dtype=np.dtype(np.float32))
        self.tof_resolution = tof_resolution
        self.energy_bin_edges = energy_bin_edges if energy_bin_edges is not None else np.zeros((0), dtype=np.dtype(np.float32))
        self.energy_resolution_at_511 = energy_resolution_at_511
        self.listmode_time_block_duration = listmode_time_block_duration

    def number_of_detectors(self) -> yardl.Size:
        return len(self.detectors)

    def number_of_tof_bins(self) -> yardl.Size:
        return self.tof_bin_edges.size - 1

    def number_of_energy_bins(self) -> yardl.Size:
        return self.energy_bin_edges.size - 1

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, ScannerInformation)
            and self.model_name == other.model_name
            and self.detectors == other.detectors
            and yardl.structural_equal(self.tof_bin_edges, other.tof_bin_edges)
            and self.tof_resolution == other.tof_resolution
            and yardl.structural_equal(self.energy_bin_edges, other.energy_bin_edges)
            and self.energy_resolution_at_511 == other.energy_resolution_at_511
            and self.listmode_time_block_duration == other.listmode_time_block_duration
        )

    def __str__(self) -> str:
        return f"ScannerInformation(modelName={self.model_name}, detectors={self.detectors}, tofBinEdges={self.tof_bin_edges}, tofResolution={self.tof_resolution}, energyBinEdges={self.energy_bin_edges}, energyResolutionAt511={self.energy_resolution_at_511}, listmodeTimeBlockDuration={self.listmode_time_block_duration})"

    def __repr__(self) -> str:
        return f"ScannerInformation(modelName={repr(self.model_name)}, detectors={repr(self.detectors)}, tofBinEdges={repr(self.tof_bin_edges)}, tofResolution={repr(self.tof_resolution)}, energyBinEdges={repr(self.energy_bin_edges)}, energyResolutionAt511={repr(self.energy_resolution_at_511)}, listmodeTimeBlockDuration={repr(self.listmode_time_block_duration)})"


class Header:
    scanner: ScannerInformation
    exam: typing.Optional[ExamInformation]

    def __init__(self, *,
        scanner: typing.Optional[ScannerInformation] = None,
        exam: typing.Optional[ExamInformation] = None,
    ):
        self.scanner = scanner if scanner is not None else ScannerInformation()
        self.exam = exam

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, Header)
            and self.scanner == other.scanner
            and self.exam == other.exam
        )

    def __str__(self) -> str:
        return f"Header(scanner={self.scanner}, exam={self.exam})"

    def __repr__(self) -> str:
        return f"Header(scanner={repr(self.scanner)}, exam={repr(self.exam)})"


class TimeBlock:
    id: yardl.UInt32
    """number of the block. Multiply with listmodeTimeBlockDuration to get time since startOfAcquisition"""

    prompt_events: list[CoincidenceEvent]
    """list of prompts in this time block
    TODO might be better to use !array
    """

    delayed_events: typing.Optional[list[CoincidenceEvent]]
    """list of delayed coincidences in this time block"""


    def __init__(self, *,
        id: yardl.UInt32 = 0,
        prompt_events: typing.Optional[list[CoincidenceEvent]] = None,
        delayed_events: typing.Optional[list[CoincidenceEvent]] = None,
    ):
        self.id = id
        self.prompt_events = prompt_events if prompt_events is not None else []
        self.delayed_events = delayed_events

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, TimeBlock)
            and self.id == other.id
            and self.prompt_events == other.prompt_events
            and self.delayed_events == other.delayed_events
        )

    def __str__(self) -> str:
        return f"TimeBlock(id={self.id}, promptEvents={self.prompt_events}, delayedEvents={self.delayed_events})"

    def __repr__(self) -> str:
        return f"TimeBlock(id={repr(self.id)}, promptEvents={repr(self.prompt_events)}, delayedEvents={repr(self.delayed_events)})"


class TimeInterval:
    """Time interval in milliseconds since start of acquisition"""

    start: yardl.UInt32
    stop: yardl.UInt32

    def __init__(self, *,
        start: yardl.UInt32 = 0,
        stop: yardl.UInt32 = 0,
    ):
        self.start = start
        self.stop = stop

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, TimeInterval)
            and self.start == other.start
            and self.stop == other.stop
        )

    def __str__(self) -> str:
        return f"TimeInterval(start={self.start}, stop={self.stop})"

    def __repr__(self) -> str:
        return f"TimeInterval(start={repr(self.start)}, stop={repr(self.stop)})"


class TimeFrameInformation:
    """A sequence of time intervals (could be consecutive)"""

    time_frames: list[TimeInterval]

    def __init__(self, *,
        time_frames: typing.Optional[list[TimeInterval]] = None,
    ):
        self.time_frames = time_frames if time_frames is not None else []

    def number_of_time_frames(self) -> yardl.Size:
        return len(self.time_frames)

    def __eq__(self, other: object) -> bool:
        return (
            isinstance(other, TimeFrameInformation)
            and self.time_frames == other.time_frames
        )

    def __str__(self) -> str:
        return f"TimeFrameInformation(timeFrames={self.time_frames})"

    def __repr__(self) -> str:
        return f"TimeFrameInformation(timeFrames={repr(self.time_frames)})"


def _mk_get_dtype():
    dtype_map: dict[typing.Union[type, types.GenericAlias], typing.Union[np.dtype[typing.Any], typing.Callable[[tuple[type, ...]], np.dtype[typing.Any]]]] = {}
    get_dtype = _dtypes.make_get_dtype_func(dtype_map)

    dtype_map.setdefault(CoincidenceEvent, np.dtype([('detector_1_id', np.dtype(np.uint32)), ('detector_2_id', np.dtype(np.uint32)), ('tof_idx', np.dtype(np.uint32)), ('energy_1_idx', np.dtype(np.uint32)), ('energy_2_idx', np.dtype(np.uint32))], align=True))
    dtype_map.setdefault(Subject, np.dtype([('name', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.object_))], align=True)), ('id', np.dtype(np.object_))], align=True))
    dtype_map.setdefault(Institution, np.dtype([('name', np.dtype(np.object_)), ('address', np.dtype(np.object_))], align=True))
    dtype_map.setdefault(ExamInformation, np.dtype([('subject', get_dtype(Subject)), ('institution', get_dtype(Institution)), ('protocol', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.object_))], align=True)), ('start_of_acquisition', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.datetime64))], align=True))], align=True))
    dtype_map.setdefault(Detector, np.dtype([('id', np.dtype(np.uint32)), ('x', np.dtype(np.float32)), ('y', np.dtype(np.float32)), ('z', np.dtype(np.float32))], align=True))
    dtype_map.setdefault(ScannerInformation, np.dtype([('model_name', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.object_))], align=True)), ('detectors', np.dtype(np.object_)), ('tof_bin_edges', np.dtype(np.object_)), ('tof_resolution', np.dtype(np.float32)), ('energy_bin_edges', np.dtype(np.object_)), ('energy_resolution_at_511', np.dtype(np.float32)), ('listmode_time_block_duration', np.dtype(np.uint32))], align=True))
    dtype_map.setdefault(Header, np.dtype([('scanner', get_dtype(ScannerInformation)), ('exam', np.dtype([('has_value', np.dtype(np.bool_)), ('value', get_dtype(ExamInformation))], align=True))], align=True))
    dtype_map.setdefault(TimeBlock, np.dtype([('id', np.dtype(np.uint32)), ('prompt_events', np.dtype(np.object_)), ('delayed_events', np.dtype([('has_value', np.dtype(np.bool_)), ('value', np.dtype(np.object_))], align=True))], align=True))
    dtype_map.setdefault(TimeInterval, np.dtype([('start', np.dtype(np.uint32)), ('stop', np.dtype(np.uint32))], align=True))
    dtype_map.setdefault(TimeFrameInformation, np.dtype([('time_frames', np.dtype(np.object_))], align=True))

    return get_dtype

get_dtype = _mk_get_dtype()

