"""
Inscoper NBO Example Script: Detector Wavelength Ranges
======================================================
This script demonstrates how to describe the spectral response of a detector with
repeated WavelengthRange elements, and how physical quantities carry their unit.

Prerequisites:
-------------
1. Installation: The `inscoper_nbo` wheel must be installed in the active Python
   environment.
"""

import os
import tempfile

import inscoper_nbo

# --8<-- [start:main_logic]
# Step 1: Describe the detector
camera = inscoper_nbo.CMOS()

detector_id = inscoper_nbo.DetectorID_Type()
detector_id.set("Detector:1")
camera.setID(detector_id)
camera.setManufacturer("Hamamatsu")
camera.setModel("ORCA-Fusion BT")
camera.setCatalogNumber("C15440-20UP")

max_bit_depth = inscoper_nbo.DigitizerType_Type()
max_bit_depth.set("16bit")
camera.setMaxBitDepth(max_bit_depth)

noise_model = inscoper_nbo.DetectorNoiseModel_Type()
noise_model.set("Gaussian")
camera.setDetectorNoiseModel(noise_model)

camera.setReadOutNoise(0.7)
camera.setElectronicConversionFactor(0.24)
camera.setDarkCurrentRate(0.06)
camera.setQuantumEfficiency(0.8)

# Camera adds its own required attributes on top of those of Detector
camera.setPixelWidth(6.5)
camera.setPixelHeight(6.5)
camera.setPixelWellCapacity(15000)
camera.setMaximumFrameRate(89.1)
camera.setMaximumReadoutRate(100.0)

illumination = inscoper_nbo.CameraIllumination_Type()
illumination.set("Back")
camera.setIllumination(illumination)

# Step 2: Add one WavelengthRange per band the detector is characterized for
# WavelengthRange is declared inside Detector_Type, so the generated class is
# Detector_Type_InlineWavelengthRange. The same class is reused by every concrete
# detector, CMOS included.
for cut_on_value, cut_off_value, profile in [
    (400.0, 550.0, "qe_400_550.csv"),
    (550.0, 700.0, "qe_550_700.csv"),
]:
    wavelength_range = inscoper_nbo.Detector_Type_InlineWavelengthRange()

    cut_on = inscoper_nbo.PositiveFloat_Type()
    cut_on.set(cut_on_value)
    wavelength_range.setCutOn(cut_on)

    cut_off = inscoper_nbo.PositiveFloat_Type()
    cut_off.set(cut_off_value)
    wavelength_range.setCutOff(cut_off)

    # Wavelengths are lengths: the unit is an attribute of its own
    unit = inscoper_nbo.UnitsLength_Type()
    unit.set("nm")
    wavelength_range.setCutOnUnit(unit)
    wavelength_range.setCutOffUnit(unit)

    # PeakWavelength and WavelengthProfile are required on a wavelength range
    wavelength_range.setPeakWavelength((cut_on_value + cut_off_value) / 2)
    wavelength_range.setWavelengthProfile(profile)

    camera.addToWavelengthRange_List(wavelength_range)

# Step 3: Read the ranges back
print(f"Ranges: {len(camera.getWavelengthRange_List())}")
for wavelength_range in camera.getWavelengthRange_List():
    print(
        f"  {wavelength_range.getCutOn().get()} - {wavelength_range.getCutOff().get()} "
        f"{wavelength_range.getCutOnUnit().get()}"
    )

# Step 4: Export
output_path = os.path.join(tempfile.gettempdir(), "detector.xml")
camera.toXmlFile(output_path)
print(f"Detector description written to {output_path}")
# --8<-- [end:main_logic]
