/*
 * Inscoper NBO Example: Detector Wavelength Ranges
 * ================================================
 * This example 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 include directory of the release must be on the include
 *    paths, and the application must link against the NBO library.
 */

#include <NBO/NBO/CMOS.h>
#include <NBO/NBO/CameraIllumination_Type.h>
#include <NBO/NBO/DetectorID_Type.h>
#include <NBO/NBO/DetectorNoiseModel_Type.h>
#include <NBO/NBO/DigitizerType_Type.h>
#include <NBO/NBO/PositiveFloat_Type.h>
#include <NBO/NBO/UnitsLength_Type.h>

#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <tuple>
#include <vector>

using namespace Inscoper;

// --8<-- [start:main_logic]
int main() {
    // Step 1: Describe the detector
    NBO::CMOS camera;

    NBO::DetectorID_TypePtr detectorId = std::make_shared<NBO::DetectorID_Type>();
    detectorId->set("Detector:1");
    camera.setID(detectorId);

    camera.setManufacturer("Hamamatsu");
    camera.setModel("ORCA-Fusion BT");
    camera.setCatalogNumber("C15440-20UP");

    NBO::DigitizerType_TypePtr maxBitDepth = std::make_shared<NBO::DigitizerType_Type>();
    maxBitDepth->set("16bit");
    camera.setMaxBitDepth(maxBitDepth);

    NBO::DetectorNoiseModel_TypePtr noiseModel = std::make_shared<NBO::DetectorNoiseModel_Type>();
    noiseModel->set("Gaussian");
    camera.setDetectorNoiseModel(noiseModel);

    camera.setReadOutNoise(0.7f);
    camera.setElectronicConversionFactor(0.24f);
    camera.setDarkCurrentRate(0.06f);
    camera.setQuantumEfficiency(0.8f);

    // Camera adds its own required attributes on top of those of Detector
    camera.setPixelWidth(6.5f);
    camera.setPixelHeight(6.5f);
    camera.setPixelWellCapacity(15000);
    camera.setMaximumFrameRate(89.1f);
    camera.setMaximumReadoutRate(100.0f);

    NBO::CameraIllumination_TypePtr illumination =
        std::make_shared<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 in C++ it is the nested
    // class Detector_Type::InlineWavelengthRange. The same class is reused by every
    // concrete detector, CMOS included.
    const std::vector<std::tuple<float, float, std::string>> bands = {
        {400.0f, 550.0f, "qe_400_550.csv"},
        {550.0f, 700.0f, "qe_550_700.csv"},
    };

    for (const auto &[cutOnValue, cutOffValue, profile] : bands) {
        NBO::Detector_Type::InlineWavelengthRangePtr wavelengthRange =
            std::make_shared<NBO::Detector_Type::InlineWavelengthRange>();

        NBO::PositiveFloat_TypePtr cutOn = std::make_shared<NBO::PositiveFloat_Type>();
        cutOn->set(cutOnValue);
        wavelengthRange->setCutOn(cutOn);

        NBO::PositiveFloat_TypePtr cutOff = std::make_shared<NBO::PositiveFloat_Type>();
        cutOff->set(cutOffValue);
        wavelengthRange->setCutOff(cutOff);

        // Wavelengths are lengths: the unit is an attribute of its own
        NBO::UnitsLength_TypePtr unit = std::make_shared<NBO::UnitsLength_Type>();
        unit->set("nm");
        wavelengthRange->setCutOnUnit(unit);
        wavelengthRange->setCutOffUnit(unit);

        // PeakWavelength and WavelengthProfile are required on a wavelength range
        wavelengthRange->setPeakWavelength((cutOnValue + cutOffValue) / 2.0f);
        wavelengthRange->setWavelengthProfile(profile);

        camera.addToWavelengthRange_List(wavelengthRange);
    }

    // Step 3: Read the ranges back
    std::cout << "Ranges: " << camera.getWavelengthRange_List().size() << std::endl;
    for (const auto &wavelengthRange : camera.getWavelengthRange_List()) {
        std::cout << "  " << wavelengthRange->getCutOn()->get() << " - "
                  << wavelengthRange->getCutOff()->get() << " "
                  << wavelengthRange->getCutOnUnit()->get() << std::endl;
    }

    // Step 4: Export
    std::filesystem::path outputPath = std::filesystem::temp_directory_path() / "detector.xml";
    camera.toXmlFile(outputPath.string());
    std::cout << "Detector description written to " << outputPath.string() << std::endl;

    return 0;
}
// --8<-- [end:main_logic]
