"""
Inscoper NBO Example Script: Describe an Instrument
==================================================
This script demonstrates how to describe a complete microscope in the NBO model,
from the stand to the light sources, and export the result as NBO-XML.

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: Create the instrument and identify it
instrument = inscoper_nbo.Instrument()

instrument_id = inscoper_nbo.InstrumentID_Type()
instrument_id.set("Instrument:1")
instrument.setID(instrument_id)

instrument_name = inscoper_nbo.Denomination_Type()
instrument_name.set("Nikon Ti2 - Room 214")
instrument.setName(instrument_name)

# Step 2: Add the microscope stand
# MicroscopeStand is abstract: the concrete class states the geometry of the
# stand, here an inverted one.
stand = inscoper_nbo.InvertedMicroscopeStand()

stand_id = inscoper_nbo.LSID_Type()
stand_id.set("MicroscopeStand:1")
stand.setID(stand_id)

stand.setManufacturer("Nikon")
stand.setModel("Eclipse Ti2-E")
stand.setCatalogNumber("Ti2-E")

stand_type = inscoper_nbo.MicroscopeType_Type()
stand_type.set("Compound")
stand.setType(stand_type)

instrument.addToMicroscopeStandGroup_List(stand)

# Step 3: Add the objectives
# Objective declares twelve required attributes, so a compliant description of a
# lens states its optical characteristics in full.
for index, (model, catalog_number, magnification, na, immersion, working_distance) in enumerate(
    [
        ("CFI Plan Apo Lambda 20x", "MRD00205", 20.0, 0.75, "Air", 1.0),
        ("CFI Plan Apo Lambda 60x", "MRD01605", 60.0, 1.4, "Mineral Oil", 0.13),
    ],
    start=1,
):
    objective = inscoper_nbo.Objective()

    objective_id = inscoper_nbo.ObjectiveID_Type()
    objective_id.set(f"Objective:{index}")
    objective.setID(objective_id)

    objective.setManufacturer("Nikon")
    objective.setModel(model)
    objective.setCatalogNumber(catalog_number)

    objective.setMagnification(magnification)
    objective.setLensNA(na)
    objective.setInfinityCorrected(True)
    objective.setDIC(False)
    objective.setCorrectionCollar(False)
    objective.setWorkingDistance(working_distance)
    objective.setObjectiveViewField(25.0)
    objective.setImageDistance(200.0)

    correction = inscoper_nbo.ObjectiveCorrection_Type()
    correction.set("Apochromat")
    objective.setCorrection(correction)

    immersion_type = inscoper_nbo.ImmersionTypeList_Type()
    immersion_type.set(immersion)
    objective.setImmersionType(immersion_type)

    contrast_modulation = inscoper_nbo.ContrastModulationPlate_Type()
    contrast_modulation.set("None")
    objective.setContrastModulation(contrast_modulation)

    # Lengths carry their unit as a separate attribute
    millimeter = inscoper_nbo.UnitsLength_Type()
    millimeter.set("mm")
    objective.setWorkingDistanceUnit(millimeter)

    instrument.addToObjective_List(objective)

# Step 4: Add the detector
# CMOS derives from Camera, which derives from the abstract Detector, so a CMOS
# instance is accepted by the detector group of the instrument.
camera = inscoper_nbo.CMOS()

camera_id = inscoper_nbo.DetectorID_Type()
camera_id.set("Detector:1")
camera.setID(camera_id)

camera.setManufacturer("Hamamatsu")
camera.setModel("ORCA-Fusion BT")
camera.setCatalogNumber("C15440-20UP")

camera.setPixelWidth(6.5)
camera.setPixelHeight(6.5)
camera.setArrayWidth(2304)
camera.setArrayHeight(2304)
camera.setPixelWellCapacity(15000)
camera.setMaximumFrameRate(89.1)
camera.setMaximumReadoutRate(100.0)
camera.setQuantumEfficiency(0.8)
camera.setElectronicConversionFactor(0.24)
camera.setReadOutNoise(0.7)
camera.setDarkCurrentRate(0.06)

noise_model = inscoper_nbo.DetectorNoiseModel_Type()
noise_model.set("Gaussian")
camera.setDetectorNoiseModel(noise_model)

illumination = inscoper_nbo.CameraIllumination_Type()
illumination.set("Back")
camera.setIllumination(illumination)

max_bit_depth = inscoper_nbo.DigitizerType_Type()
max_bit_depth.set("16bit")
camera.setMaxBitDepth(max_bit_depth)

# Detectors are characterized over one or more spectral bands
wavelength_range = inscoper_nbo.Detector_Type_InlineWavelengthRange()
cut_on = inscoper_nbo.PositiveFloat_Type()
cut_on.set(400.0)
cut_off = inscoper_nbo.PositiveFloat_Type()
cut_off.set(700.0)
wavelength_range.setCutOn(cut_on)
wavelength_range.setCutOff(cut_off)
wavelength_range.setWavelengthProfile("orca_fusion_qe.csv")
wavelength_range.setPeakWavelength(560.0)
camera.addToWavelengthRange_List(wavelength_range)

instrument.addToDetectorGroup_List(camera)

# Step 5: Add the light source
# LightSource is abstract as well; a Laser is one of its concrete forms.
laser = inscoper_nbo.Laser()

laser_id = inscoper_nbo.LightSourceID_Type()
laser_id.set("LightSource:1")
laser.setID(laser_id)

laser.setManufacturer("Coherent")
laser.setModel("OBIS 488")
laser.setCatalogNumber("1185053")

laser.setTuneable(False)
laser.setPulse(False)
laser.setIsPump(False)
laser.setIsPumped(False)

laser_type = inscoper_nbo.LaserType_Type()
laser_type.set("SemiconductorLaserDiode")
laser.setType(laser_type)

modulation = inscoper_nbo.LaserModulation_Type()
modulation.set("Direct")
laser.setModulationMechanism(modulation)

illumination_range = inscoper_nbo.IlluminationWavelengthRange()
laser_cut_on = inscoper_nbo.PositiveFloat_Type()
laser_cut_on.set(486.0)
laser_cut_off = inscoper_nbo.PositiveFloat_Type()
laser_cut_off.set(490.0)
illumination_range.setCutOn(laser_cut_on)
illumination_range.setCutOff(laser_cut_off)
illumination_range.setPeakWavelength(488.0)
illumination_range.setIlluminationPower(60.0)
laser.addToIlluminationWavelengthRange_List(illumination_range)

instrument.addToLightSourceGroup_List(laser)

# Step 6: Export the description
output_path = os.path.join(tempfile.gettempdir(), "instrument.xml")
instrument.toXmlFile(output_path)
print(f"Instrument description written to {output_path}")

# Step 7: Read the description back through the model
print(f"Objectives: {len(instrument.getObjective_List())}")
for objective in instrument.getObjective_List():
    print(f"  {objective.getModel()} - {objective.getMagnification()}x NA {objective.getLensNA()}")

for detector in instrument.getDetectorGroup_List():
    print(f"Detector: {detector.getManufacturer()} {detector.getModel()}")

for light_source in instrument.getLightSourceGroup_List():
    print(f"Light source: {light_source.getManufacturer()} {light_source.getModel()}")
# --8<-- [end:main_logic]
