/*
 * Inscoper NBO Example: Describe an Instrument
 * ============================================
 * This example 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: inscoper-nbo-<version>.jar must be on the classpath.
 */

import com.inscoper.nbo.CameraIllumination_Type;
import com.inscoper.nbo.CMOS;
import com.inscoper.nbo.ContrastModulationPlate_Type;
import com.inscoper.nbo.Denomination_Type;
import com.inscoper.nbo.Detector_Type_InlineWavelengthRange;
import com.inscoper.nbo.DetectorID_Type;
import com.inscoper.nbo.DetectorNoiseModel_Type;
import com.inscoper.nbo.DigitizerType_Type;
import com.inscoper.nbo.IlluminationWavelengthRange;
import com.inscoper.nbo.ImmersionTypeList_Type;
import com.inscoper.nbo.Instrument;
import com.inscoper.nbo.InstrumentID_Type;
import com.inscoper.nbo.InvertedMicroscopeStand;
import com.inscoper.nbo.Laser;
import com.inscoper.nbo.LaserModulation_Type;
import com.inscoper.nbo.LaserType_Type;
import com.inscoper.nbo.LightSourceID_Type;
import com.inscoper.nbo.LSID_Type;
import com.inscoper.nbo.MicroscopeType_Type;
import com.inscoper.nbo.Objective;
import com.inscoper.nbo.ObjectiveCorrection_Type;
import com.inscoper.nbo.ObjectiveID_Type;
import com.inscoper.nbo.PositiveFloat_Type;
import com.inscoper.nbo.UnitsLength_Type;

import java.nio.file.Path;
import java.nio.file.Paths;

public class DescribeInstrument {

    // --8<-- [start:main_logic]
    record ObjectiveSpec(String model, String catalogNumber, float magnification, float lensNA,
            String immersion, float workingDistance) {
    }

    public static void main(String[] args) {
        // Step 1: Create the instrument and identify it
        Instrument instrument = new Instrument();

        InstrumentID_Type instrumentId = new InstrumentID_Type();
        instrumentId.set("Instrument:1");
        instrument.setID(instrumentId);

        Denomination_Type instrumentName = new Denomination_Type();
        instrumentName.set("Nikon Ti2 - Room 214");
        instrument.setName(instrumentName);

        // Step 2: Add the microscope stand
        // MicroscopeStand is abstract: the concrete class states the geometry of the
        // stand, here an inverted one.
        InvertedMicroscopeStand stand = new InvertedMicroscopeStand();

        LSID_Type standId = new LSID_Type();
        standId.set("MicroscopeStand:1");
        stand.setID(standId);

        stand.setManufacturer("Nikon");
        stand.setModel("Eclipse Ti2-E");
        stand.setCatalogNumber("Ti2-E");

        MicroscopeType_Type standType = new MicroscopeType_Type();
        standType.set("Compound");
        stand.setType(standType);

        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.
        ObjectiveSpec[] objectiveSpecs = {
            new ObjectiveSpec("CFI Plan Apo Lambda 20x", "MRD00205", 20.0f, 0.75f, "Air", 1.0f),
            new ObjectiveSpec("CFI Plan Apo Lambda 60x", "MRD01605", 60.0f, 1.4f, "Mineral Oil",
                    0.13f),
        };

        int index = 1;
        for (ObjectiveSpec spec : objectiveSpecs) {
            Objective objective = new Objective();

            ObjectiveID_Type objectiveId = new ObjectiveID_Type();
            objectiveId.set("Objective:" + index);
            objective.setID(objectiveId);

            objective.setManufacturer("Nikon");
            objective.setModel(spec.model());
            objective.setCatalogNumber(spec.catalogNumber());

            objective.setMagnification(spec.magnification());
            objective.setLensNA(spec.lensNA());
            objective.setInfinityCorrected(true);
            objective.setDIC(false);
            objective.setCorrectionCollar(false);
            objective.setWorkingDistance(spec.workingDistance());
            objective.setObjectiveViewField(25.0f);
            objective.setImageDistance(200.0f);

            ObjectiveCorrection_Type correction = new ObjectiveCorrection_Type();
            correction.set("Apochromat");
            objective.setCorrection(correction);

            ImmersionTypeList_Type immersionType = new ImmersionTypeList_Type();
            immersionType.set(spec.immersion());
            objective.setImmersionType(immersionType);

            ContrastModulationPlate_Type contrastModulation = new ContrastModulationPlate_Type();
            contrastModulation.set("None");
            objective.setContrastModulation(contrastModulation);

            // Lengths carry their unit as a separate attribute
            UnitsLength_Type millimeter = new UnitsLength_Type();
            millimeter.set("mm");
            objective.setWorkingDistanceUnit(millimeter);

            instrument.addToObjective_List(objective);
            index++;
        }

        // 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.
        CMOS camera = new CMOS();

        DetectorID_Type cameraId = new DetectorID_Type();
        cameraId.set("Detector:1");
        camera.setID(cameraId);

        camera.setManufacturer("Hamamatsu");
        camera.setModel("ORCA-Fusion BT");
        camera.setCatalogNumber("C15440-20UP");

        camera.setPixelWidth(6.5f);
        camera.setPixelHeight(6.5f);
        camera.setArrayWidth(2304L);
        camera.setArrayHeight(2304L);
        camera.setPixelWellCapacity(15000L);
        camera.setMaximumFrameRate(89.1f);
        camera.setMaximumReadoutRate(100.0f);
        camera.setQuantumEfficiency(0.8f);
        camera.setElectronicConversionFactor(0.24f);
        camera.setReadOutNoise(0.7f);
        camera.setDarkCurrentRate(0.06f);

        DetectorNoiseModel_Type noiseModel = new DetectorNoiseModel_Type();
        noiseModel.set("Gaussian");
        camera.setDetectorNoiseModel(noiseModel);

        CameraIllumination_Type illumination = new CameraIllumination_Type();
        illumination.set("Back");
        camera.setIllumination(illumination);

        DigitizerType_Type maxBitDepth = new DigitizerType_Type();
        maxBitDepth.set("16bit");
        camera.setMaxBitDepth(maxBitDepth);

        // Detectors are characterized over one or more spectral bands
        Detector_Type_InlineWavelengthRange wavelengthRange =
                new Detector_Type_InlineWavelengthRange();
        PositiveFloat_Type cutOn = new PositiveFloat_Type();
        cutOn.set(400.0f);
        PositiveFloat_Type cutOff = new PositiveFloat_Type();
        cutOff.set(700.0f);
        wavelengthRange.setCutOn(cutOn);
        wavelengthRange.setCutOff(cutOff);
        wavelengthRange.setPeakWavelength(560.0f);
        wavelengthRange.setWavelengthProfile("orca_fusion_qe.csv");
        camera.addToWavelengthRange_List(wavelengthRange);

        instrument.addToDetectorGroup_List(camera);

        // Step 5: Add the light source
        // LightSource is abstract as well; a Laser is one of its concrete forms.
        Laser laser = new Laser();

        LightSourceID_Type laserId = new LightSourceID_Type();
        laserId.set("LightSource:1");
        laser.setID(laserId);

        laser.setManufacturer("Coherent");
        laser.setModel("OBIS 488");
        laser.setCatalogNumber("1185053");

        laser.setTuneable(false);
        laser.setPulse(false);
        laser.setIsPump(false);
        laser.setIsPumped(false);

        LaserType_Type laserType = new LaserType_Type();
        laserType.set("SemiconductorLaserDiode");
        laser.setType(laserType);

        LaserModulation_Type modulation = new LaserModulation_Type();
        modulation.set("Direct");
        laser.setModulationMechanism(modulation);

        IlluminationWavelengthRange illuminationRange = new IlluminationWavelengthRange();
        PositiveFloat_Type laserCutOn = new PositiveFloat_Type();
        laserCutOn.set(486.0f);
        PositiveFloat_Type laserCutOff = new PositiveFloat_Type();
        laserCutOff.set(490.0f);
        illuminationRange.setCutOn(laserCutOn);
        illuminationRange.setCutOff(laserCutOff);
        illuminationRange.setPeakWavelength(488.0f);
        illuminationRange.setIlluminationPower(60.0f);
        laser.addToIlluminationWavelengthRange_List(illuminationRange);

        instrument.addToLightSourceGroup_List(laser);

        // Step 6: Export the description
        Path outputPath = Paths.get(System.getProperty("java.io.tmpdir"), "instrument.xml");
        instrument.toXmlFile(outputPath.toString());
        System.out.println("Instrument description written to " + outputPath);

        // Step 7: Read the description back through the model
        System.out.println("Objectives: " + instrument.getObjective_List().size());
        for (Objective objective : instrument.getObjective_List()) {
            System.out.println("  " + objective.getModel() + " - " + objective.getMagnification()
                    + "x NA " + objective.getLensNA());
        }

        for (var detector : instrument.getDetectorGroup_List()) {
            System.out.println(
                    "Detector: " + detector.getManufacturer() + " " + detector.getModel());
        }

        for (var lightSource : instrument.getLightSourceGroup_List()) {
            System.out.println(
                    "Light source: " + lightSource.getManufacturer() + " " + lightSource.getModel());
        }
    }
    // --8<-- [end:main_logic]
}
