use anyhow::{anyhow, Result};
use rust_database::*;
use serde::Serialize;
use serde_json::from_str;

use std::{collections::HashMap, fs::read_to_string};

use crate::guarded_variable::{CombineRights, GuardedVariable};

#[derive(Serialize)]
pub struct CepiMeta<'a> {
    pub probenvorbereitung_name: &'a GuardedVariable<String>,
    pub bezeichnung: &'a GuardedVariable<String>,
    pub probeneingang_datum: &'a GuardedVariable<String>,
    pub probenuntersuchung_datum: &'a GuardedVariable<String>,
    pub probenvorbereitung_datum: &'a GuardedVariable<String>,
    pub probenuntersuchung_name: &'a GuardedVariable<String>,
    pub auftrag: &'a GuardedVariable<String>,
    pub probeneingang_name: &'a GuardedVariable<String>,
    pub firma: &'a GuardedVariable<String>,
    pub labornummer: &'a GuardedVariable<String>,

    pub nasslabor_gerätetyp: &'a GuardedVariable<String>,
    pub nasslabor_labor: &'a GuardedVariable<String>,

    pub nasslabor_fein_labor: &'a GuardedVariable<String>,

    pub nasslabor_grob_labor: &'a GuardedVariable<String>,
}

impl<'a> From<&'a Cepi22_8> for CepiMeta<'a> {
    fn from(value: &'a Cepi22_8) -> Self {
        Self {
            probenvorbereitung_name: &value.uebersicht.probenvorbereitung_name,
            bezeichnung: &value.uebersicht.bezeichnung,
            probeneingang_datum: &value.uebersicht.probeneingang_datum,
            probenuntersuchung_datum: &value.uebersicht.probenuntersuchung_datum,
            probenvorbereitung_datum: &value.uebersicht.probenvorbereitung_datum,
            probenuntersuchung_name: &value.uebersicht.probenuntersuchung_name,
            auftrag: &value.uebersicht.auftrag,
            probeneingang_name: &value.uebersicht.probeneingang_name,
            firma: &value.uebersicht.firma,
            labornummer: &value.uebersicht.labornummer,

            nasslabor_gerätetyp: &value.nasslabor.gerätetyp,
            nasslabor_labor: &value.nasslabor.labor,

            nasslabor_fein_labor: &value.nasslabor_fein.labor,

            nasslabor_grob_labor: &value.nasslabor_grob.labor,
        }
    }
}

#[derive(Serialize)]
pub struct CepiInfo<'a> {
    nasslabor_bemerkung: &'a GuardedVariable<Option<String>>,
    nasslabor_bestandteile: Vec<GuardedVariable<String>>,
    nasslabor_trocken_entfernt: Vec<GuardedVariable<bool>>,

    nasslabor_fein_faser_stippen_rückstand_art_größe: &'a GuardedVariable<String>,
    nasslabor_fein_faser_stippen_rückstand: &'a GuardedVariable<bool>,

    nasslabor_fein_papierfremde_bestandteile: &'a GuardedVariable<String>,
    nasslabor_fein_papierfremde_art_größe: &'a GuardedVariable<String>,
    nasslabor_fein_papierfremde_material: &'a GuardedVariable<String>,
    nasslabor_fein_papierfremde_zerkleinerung: &'a GuardedVariable<String>,

    nasslabor_grob_faser_stippen_rückstand_art_größe: &'a GuardedVariable<String>,
    nasslabor_grob_faser_stippen_rückstand: &'a GuardedVariable<bool>,

    nasslabor_grob_papierfremde_bestandteile: &'a GuardedVariable<String>,
    nasslabor_grob_papierfremde_art_größe: &'a GuardedVariable<String>,
    nasslabor_grob_papierfremde_material: &'a GuardedVariable<String>,
    nasslabor_grob_papierfremde_zerkleinerung: &'a GuardedVariable<String>,

    blattklebetest: HashMap<String, &'a GuardedVariable<Option<String>>>,
    visuelle_beurteilung: HashMap<String, &'a GuardedVariable<Option<String>>>,
}

impl<'a> From<&'a Cepi22_8> for CepiInfo<'a> {
    fn from(value: &'a Cepi22_8) -> Self {
        Self {
            nasslabor_bemerkung: &value.nasslabor.bemerkung,
            nasslabor_bestandteile: value
                .nasslabor
                .zusammensetzung
                .iter()
                .map(|z| z.bestandteil.clone())
                .collect(),
            nasslabor_trocken_entfernt: value
                .nasslabor
                .zusammensetzung
                .iter()
                .map(|z| z.trocken_entfernt.clone())
                .collect(),

            nasslabor_fein_faser_stippen_rückstand_art_größe: &value
                .nasslabor_fein
                .faser_stippen_rückstand_art_größe,
            nasslabor_fein_faser_stippen_rückstand: &value.nasslabor_fein.faser_stippen_rückstand,

            nasslabor_fein_papierfremde_bestandteile: &value
                .nasslabor_fein
                .papierfremde_bestandteile,
            nasslabor_fein_papierfremde_art_größe: &value.nasslabor_fein.papierfremde_art_größe,
            nasslabor_fein_papierfremde_material: &value.nasslabor_fein.papierfremde_material,
            nasslabor_fein_papierfremde_zerkleinerung: &value
                .nasslabor_fein
                .papierfremde_zerkleinerung,

            blattklebetest: value.blattklebetest.to_map(),
            visuelle_beurteilung: value.visuelle_beurteilung.to_map(),

            nasslabor_grob_faser_stippen_rückstand_art_größe: &value
                .nasslabor_grob
                .faser_stippen_rückstand_art_größe,
            nasslabor_grob_faser_stippen_rückstand: &value.nasslabor_grob.faser_stippen_rückstand,

            nasslabor_grob_papierfremde_bestandteile: &value
                .nasslabor_grob
                .papierfremde_bestandteile,
            nasslabor_grob_papierfremde_art_größe: &value.nasslabor_grob.papierfremde_art_größe,
            nasslabor_grob_papierfremde_material: &value.nasslabor_grob.papierfremde_material,
            nasslabor_grob_papierfremde_zerkleinerung: &value
                .nasslabor_grob
                .papierfremde_zerkleinerung,
        }
    }
}

#[derive(Debug, Serialize)]
pub struct Cepi22_8 {
    pub version: String,
    pub uebersicht: Uebersicht,
    pub nasslabor: NassLabor,
    pub nasslabor_fein: NassLaborFein,
    pub nasslabor_grob: NassLaborGrob,
    pub blattklebetest: BlattklebeTest,
    pub visuelle_beurteilung: VisuelleBeurteilung,
}

impl Cepi22_8 {
    pub fn new(
        mut data: HashMap<String, HashMap<String, String>>,
        rights_file: &str,
    ) -> Result<Self> {
        let mut rights: HashMap<String, HashMap<String, Rights>> =
            from_str(&read_to_string(rights_file)?)?;

        Ok(Self {
            version: data["version"]["version"].clone(),
            uebersicht: Uebersicht::new(
                data.remove("übersicht")
                    .ok_or(anyhow!("übersicht not present in data"))?,
                rights
                    .remove("uebersicht")
                    .ok_or(anyhow!("uebersicht not present in rights"))?,
            )?,
            nasslabor: NassLabor::new(
                data.remove("nasslaborteil")
                    .ok_or(anyhow!("nasslaborteil not present in data"))?,
                rights
                    .remove("nasslabor")
                    .ok_or(anyhow!("nasslabor not present in rights"))?,
            )?,
            nasslabor_fein: NassLaborFein::new(
                data.remove("nasslaborteil_fein")
                    .ok_or(anyhow!("nasslaborteil_fein not present in data"))?,
                rights
                    .remove("nasslabor_fein")
                    .ok_or(anyhow!("nasslabor_fein not present in rights"))?,
            )?,
            nasslabor_grob: NassLaborGrob::new(
                data.remove("nasslaborteil_grob")
                    .ok_or(anyhow!("nasslaborteil_grob not present in data"))?,
                rights
                    .remove("nasslabor_grob")
                    .ok_or(anyhow!("nasslabor_grob not present in rights"))?,
            )?,
            blattklebetest: BlattklebeTest::new(
                data.remove("blattklebetest")
                    .ok_or(anyhow!("blattklebetest not present in data"))?,
                rights
                    .remove("blattklebetest")
                    .ok_or(anyhow!("blattklebetest not present in rights"))?,
            )?,
            visuelle_beurteilung: VisuelleBeurteilung::new(
                data.remove("visuelle beurteilung")
                    .ok_or(anyhow!("visuelle beurteilung not present in data"))?,
                rights
                    .remove("visuelle_beurteilung")
                    .ok_or(anyhow!("visuelle_beurteilung not present in rights"))?,
            )?,
        })
    }

    pub fn create_measurements(&self) -> Vec<Measurement> {
        let nasslabor_zsm: Vec<Measurement> = self
            .nasslabor
            .zusammensetzung
            .iter()
            .map(|zsm| {
                vec![
                    zsm.masse.as_measurement(),
                    zsm.tg_1.as_measurement(),
                    zsm.tg_2.as_measurement(),
                ]
            })
            .flatten()
            .collect();

        let nasslabor_fein_grammatur: Vec<Measurement> = self
            .nasslabor_fein
            .grammatur_af
            .iter()
            .map(|grammatur| grammatur.as_measurement())
            .collect();

        let nasslabor_grob_grammatur: Vec<Measurement> = self
            .nasslabor_grob
            .grammatur_ac
            .iter()
            .map(|grammatur| grammatur.as_measurement())
            .collect();

        let nasslabor_filtrat_auswaage: Vec<Measurement> = self
            .nasslabor
            .ars_filtrat_auswaage
            .iter()
            .map(|auswaage| auswaage.as_measurement())
            .collect();

        let nasslabor_filtrat_einwaage: Vec<Measurement> = self
            .nasslabor
            .ars_filtrat_einwaage
            .iter()
            .map(|einwaage| einwaage.as_measurement())
            .collect();

        let nasslabor_ars_filtrat_masse_schale: Vec<Measurement> = self
            .nasslabor
            .ars_filtrat_masse_schale
            .iter()
            .map(|masse_schale| masse_schale.as_measurement())
            .collect();

        let nasslabor_ars_wasser_einwaage: Vec<Measurement> = self
            .nasslabor
            .ars_wasser_einwaage
            .iter()
            .map(|einwwage| einwwage.as_measurement())
            .collect();

        let nasslabor_ars_wasser_auswaage: Vec<Measurement> = self
            .nasslabor
            .ars_wasser_auswaage
            .iter()
            .map(|auswaage| auswaage.as_measurement())
            .collect();

        let nasslabor_ars_wasser_masse_schale: Vec<Measurement> = self
            .nasslabor
            .ars_wasser_masse_schale
            .iter()
            .map(|masse_schale| masse_schale.as_measurement())
            .collect();

        let mut values = vec![
            // nasslabor
            // self.nasslabor.csb_messbereich.as_measurement(),
            self.nasslabor.csb_verdünnung.as_measurement(),
            self.nasslabor.csb_messwert.as_measurement(),
            self.nasslabor.lf_wasser.as_measurement(),
            self.nasslabor.ph_wasser.as_measurement(),
            self.nasslabor.ph_filtrat.as_measurement(),
            self.nasslabor.zerfaserungsdauer.as_measurement(),
            self.nasslabor.einwaage.as_measurement(),
            self.nasslabor.umdrehungen.as_measurement(),
            // nasslabor fein
            self.nasslabor_fein.reject_filtergewicht.as_measurement(),
            self.nasslabor_fein.reject_auswaage.as_measurement(),
            self.nasslabor_fein.volumen_af.as_measurement(),
            // nasslabor grob
            self.nasslabor_grob.sd_einwaage.as_measurement(),
            self.nasslabor_grob.sd_auswaage_ts.as_measurement(),
            self.nasslabor_grob.sd_auswaage.as_measurement(),
            self.nasslabor_grob.sd_filterblatt.as_measurement(),
            self.nasslabor_grob.reject_filtergewicht.as_measurement(),
            self.nasslabor_grob.reject_auswaage.as_measurement(),
            self.nasslabor_grob.volumen_ac.as_measurement(),
        ];

        values.extend(nasslabor_zsm);
        values.extend(nasslabor_fein_grammatur);
        values.extend(nasslabor_grob_grammatur);

        values.extend(nasslabor_filtrat_auswaage);
        values.extend(nasslabor_filtrat_einwaage);
        values.extend(nasslabor_ars_filtrat_masse_schale);

        values.extend(nasslabor_ars_wasser_auswaage);
        values.extend(nasslabor_ars_wasser_einwaage);
        values.extend(nasslabor_ars_wasser_masse_schale);

        values
    }
}

#[derive(Debug, Serialize)]
pub struct Uebersicht {
    pub probenvorbereitung_name: GuardedVariable<String>,
    pub bezeichnung: GuardedVariable<String>,
    pub probeneingang_datum: GuardedVariable<String>,
    pub probenuntersuchung_datum: GuardedVariable<String>,
    pub probenvorbereitung_datum: GuardedVariable<String>,
    pub probenuntersuchung_name: GuardedVariable<String>,
    pub auftrag: GuardedVariable<String>,
    pub probeneingang_name: GuardedVariable<String>,
    pub firma: GuardedVariable<String>,
    pub labornummer: GuardedVariable<String>,
}

impl Uebersicht {
    fn new(data: HashMap<String, String>, rights: HashMap<String, Rights>) -> Result<Self> {
        Ok(Self {
            probenvorbereitung_name: GuardedVariable::combine(
                &data,
                &rights,
                "probenvorbereitung_name",
            )?,
            bezeichnung: GuardedVariable::combine(&data, &rights, "bezeichnung")?,
            probeneingang_datum: GuardedVariable::combine(&data, &rights, "probeneingang_datum")?,
            probenuntersuchung_datum: GuardedVariable::combine(
                &data,
                &rights,
                "probenuntersuchung_datum",
            )?,
            probenvorbereitung_datum: GuardedVariable::combine(
                &data,
                &rights,
                "probenvorbereitung_datum",
            )?,
            probenuntersuchung_name: GuardedVariable::combine(
                &data,
                &rights,
                "probenuntersuchung_name",
            )?,
            auftrag: GuardedVariable::combine(&data, &rights, "auftrag")?,
            probeneingang_name: GuardedVariable::combine(&data, &rights, "probeneingang_name")?,
            firma: GuardedVariable::combine(&data, &rights, "firma")?,
            labornummer: GuardedVariable::combine(&data, &rights, "labornummer")?,
        })
    }
}

#[derive(Debug, Serialize)]
pub struct ZusammenSetzungNassLabor {
    pub bestandteil: GuardedVariable<String>,
    pub masse: GuardedVariable<f32>,
    pub trocken_entfernt: GuardedVariable<bool>,
    pub tg_1: GuardedVariable<f32>,
    pub tg_2: GuardedVariable<f32>,
}

#[derive(Debug, Serialize)]
pub struct NassLabor {
    pub gerätetyp: GuardedVariable<String>,
    pub labor: GuardedVariable<String>,

    pub ars_filtrat_einwaage: Vec<GuardedVariable<f32>>,
    pub ars_filtrat_auswaage: Vec<GuardedVariable<f32>>,
    pub ars_filtrat_masse_schale: Vec<GuardedVariable<f32>>,

    pub ars_wasser_einwaage: Vec<GuardedVariable<f32>>,
    pub ars_wasser_auswaage: Vec<GuardedVariable<f32>>,
    pub ars_wasser_masse_schale: Vec<GuardedVariable<f32>>,

    pub csb_messbereich: GuardedVariable<String>,
    pub csb_verdünnung: GuardedVariable<f32>,
    pub csb_messwert: GuardedVariable<f32>,

    pub lf_wasser: GuardedVariable<f32>,
    pub ph_wasser: GuardedVariable<f32>,
    pub ph_filtrat: GuardedVariable<f32>,

    pub zerfaserungsdauer: GuardedVariable<f32>,
    pub einwaage: GuardedVariable<f32>,
    pub umdrehungen: GuardedVariable<f32>,

    pub bemerkung: GuardedVariable<Option<String>>,

    pub zusammensetzung: Vec<ZusammenSetzungNassLabor>,
}

impl NassLabor {
    pub fn new(data: HashMap<String, String>, rights: HashMap<String, Rights>) -> Result<Self> {
        Ok(Self {
            gerätetyp: GuardedVariable::combine(&data, &rights, "gerätetyp")?,
            labor: GuardedVariable::combine(&data, &rights, "labor")?,

            ars_filtrat_einwaage: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(
                        match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("ars_filtrat{}_einwaage", i).as_str(),
                                "ars_filtrat_einwaage",
                            ),
                        ) {
                            Ok(g) => g,
                            Err(_) => break,
                        },
                    );

                    i += 1;
                }

                v
            },
            ars_filtrat_auswaage: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(
                        match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("ars_filtrat{}_auswaage", i).as_str(),
                                "ars_filtrat_auswaage",
                            ),
                        ) {
                            Ok(g) => g,
                            Err(_) => break,
                        },
                    );

                    i += 1;
                }

                v
            },
            ars_filtrat_masse_schale: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(
                        match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("ars_filtrat{}_masse_schale", i).as_str(),
                                "ars_filtrat_masse_schale",
                            ),
                        ) {
                            Ok(g) => g,
                            Err(_) => break,
                        },
                    );

                    i += 1;
                }

                v
            },

            ars_wasser_einwaage: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(
                        match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("ars_wasser{}_einwaage", i).as_str(),
                                "ars_wasser_einwaage",
                            ),
                        ) {
                            Ok(g) => g,
                            Err(_) => break,
                        },
                    );

                    i += 1;
                }

                v
            },
            ars_wasser_auswaage: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(
                        match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("ars_wasser{}_auswaage", i).as_str(),
                                "ars_wasser_auswaage",
                            ),
                        ) {
                            Ok(g) => g,
                            Err(_) => break,
                        },
                    );

                    i += 1;
                }

                v
            },
            ars_wasser_masse_schale: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(
                        match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("ars_wasser{}_masse_schale", i).as_str(),
                                "ars_wasser_masse_schale",
                            ),
                        ) {
                            Ok(g) => g,
                            Err(_) => break,
                        },
                    );

                    i += 1;
                }

                v
            },

            csb_messbereich: GuardedVariable::combine(&data, &rights, "csb_messbereich")?,
            csb_verdünnung: GuardedVariable::combine(&data, &rights, "csb_verdünnung")?,
            csb_messwert: GuardedVariable::combine(&data, &rights, "csb_messwert")?,

            lf_wasser: GuardedVariable::combine(&data, &rights, "lf_wasser")?,
            ph_wasser: GuardedVariable::combine(&data, &rights, "ph_wasser")?,
            ph_filtrat: GuardedVariable::combine(&data, &rights, "ph_filtrat")?,

            zerfaserungsdauer: GuardedVariable::combine(&data, &rights, "zerfaserungsdauer")?,
            einwaage: GuardedVariable::combine(&data, &rights, "einwaage")?,
            umdrehungen: GuardedVariable::combine(&data, &rights, "umdrehungen")?,

            bemerkung: GuardedVariable::combine(&data, &rights, "bemerkung")?,

            zusammensetzung: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(ZusammenSetzungNassLabor {
                        bestandteil: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (format!("bestandteil_{}", i).as_str(), "bestandteil"),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                        masse: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (format!("masse_{}", i).as_str(), "masse"),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                        trocken_entfernt: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("trocken_entfernt_{}", i).as_str(),
                                "trocken_entfernt",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                        tg_1: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (format!("tg_1_{}", i).as_str(), "tg_1"),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                        tg_2: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (format!("tg_2_{}", i).as_str(), "tg_2"),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                    });

                    i += 1;
                }

                v
            },
        })
    }
}

#[derive(Debug, Serialize)]
pub struct NassLaborFein {
    pub labor: GuardedVariable<String>,

    pub faser_stippen_rückstand: GuardedVariable<bool>,
    pub faser_stippen_rückstand_art_größe: GuardedVariable<String>,

    pub reject_filtergewicht: GuardedVariable<f32>,
    pub reject_auswaage: GuardedVariable<f32>,

    pub papierfremde_bestandteile: GuardedVariable<String>,
    pub papierfremde_art_größe: GuardedVariable<String>,
    pub papierfremde_material: GuardedVariable<String>,
    pub papierfremde_zerkleinerung: GuardedVariable<String>,

    pub volumen_af: GuardedVariable<f32>,
    pub grammatur_af: Vec<GuardedVariable<f32>>,
}

impl NassLaborFein {
    pub fn new(data: HashMap<String, String>, rights: HashMap<String, Rights>) -> Result<Self> {
        Ok(Self {
            labor: GuardedVariable::combine(&data, &rights, "labor")?,

            faser_stippen_rückstand: GuardedVariable::combine(
                &data,
                &rights,
                ("faser-stippen-rückstand", "faser_stippen_rückstand"),
            )?,
            faser_stippen_rückstand_art_größe: GuardedVariable::combine(
                &data,
                &rights,
                (
                    "faser-stippen-rückstand_art-größe",
                    "faser_stippen_rückstand_art_größe",
                ),
            )?,

            reject_filtergewicht: GuardedVariable::combine(
                &data,
                &rights,
                ("reject_filtergewicht_fein", "reject_filtergewicht"),
            )?,
            reject_auswaage: GuardedVariable::combine(
                &data,
                &rights,
                ("reject_auswaage_fein", "reject_auswaage"),
            )?,

            papierfremde_bestandteile: GuardedVariable::combine(
                &data,
                &rights,
                ("papierfremde-bestandteile", "papierfremde_bestandteile"),
            )?,
            papierfremde_art_größe: GuardedVariable::combine(
                &data,
                &rights,
                ("papierfremde-art-größe", "papierfremde_art_größe"),
            )?,
            papierfremde_material: GuardedVariable::combine(
                &data,
                &rights,
                ("papierfremde-material", "papierfremde_material"),
            )?,
            papierfremde_zerkleinerung: GuardedVariable::combine(
                &data,
                &rights,
                ("papierfremde-zerkleinerung", "papierfremde_zerkleinerung"),
            )?,

            volumen_af: GuardedVariable::combine(&data, &rights, ("volumen_af1", "volumen_af"))?,

            grammatur_af: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(
                        match GuardedVariable::combine(
                            &data,
                            &rights,
                            (format!("grammatur_af{}", i).as_str(), "grammatur_af"),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                    );

                    i += 1;
                }

                v
            },
        })
    }
}

#[derive(Debug, Serialize)]
pub struct Bewertung {
    pub bewertung: GuardedVariable<Option<String>>,
    pub bemerkung: GuardedVariable<Option<String>>,
}

#[derive(Debug, Serialize)]
pub struct BlattklebeTest {
    pub bewertung_fein: Vec<Bewertung>,
    pub bewertung_fein_gesamt: Bewertung,

    pub bewertung_grob: Vec<Bewertung>,
    pub bewertung_grob_gesamt: Bewertung,
}

impl BlattklebeTest {
    pub fn new(data: HashMap<String, String>, rights: HashMap<String, Rights>) -> Result<Self> {
        Ok(Self {
            bewertung_fein: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(Bewertung {
                        bewertung: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("bewertung_fein_{}_bewertung", i).as_str(),
                                "bewertung_fein_bewertung",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                        bemerkung: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("bewertung_fein_{}_bemerkung", i).as_str(),
                                "bewertung_fein_bemerkung",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                    });

                    i += 1;
                }

                v
            },
            bewertung_fein_gesamt: Bewertung {
                bewertung: GuardedVariable::combine(
                    &data,
                    &rights,
                    (
                        "bewertung_fein_ges_bewertung",
                        "bewertung_fein_gesamt_bewertung",
                    ),
                )?,
                bemerkung: GuardedVariable::combine(
                    &data,
                    &rights,
                    (
                        "bewertung_fein_ges_bemerkung",
                        "bewertung_fein_gesamt_bemerkung",
                    ),
                )?,
            },

            bewertung_grob: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(Bewertung {
                        bewertung: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("bewertung_grob_{}_bewertung", i).as_str(),
                                "bewertung_grob_bewertung",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                        bemerkung: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("bewertung_grob_{}_bemerkung", i).as_str(),
                                "bewertung_grob_bemerkung",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                    });

                    i += 1;
                }

                v
            },
            bewertung_grob_gesamt: Bewertung {
                bewertung: GuardedVariable::combine(
                    &data,
                    &rights,
                    (
                        "bewertung_grob_ges_bewertung",
                        "bewertung_grob_gesamt_bewertung",
                    ),
                )?,
                bemerkung: GuardedVariable::combine(
                    &data,
                    &rights,
                    (
                        "bewertung_grob_ges_bemerkung",
                        "bewertung_grob_gesamt_bemerkung",
                    ),
                )?,
            },
        })
    }

    fn to_map(&self) -> HashMap<String, &GuardedVariable<Option<String>>> {
        let mut map = HashMap::new();

        map.insert(
            "bkt_bewertung_fein_gesamt".to_string(),
            &self.bewertung_fein_gesamt.bewertung,
        );
        map.insert(
            "bkt_bemerkung_fein_gesamt".to_string(),
            &self.bewertung_fein_gesamt.bemerkung,
        );

        for (index, bewertung_fein) in self.bewertung_fein.iter().enumerate() {
            map.insert(
                format!("bkt_bewertung_fein{}", index),
                &bewertung_fein.bewertung,
            );
            map.insert(
                format!("bkt_bemerkung_fein{}", index),
                &bewertung_fein.bemerkung,
            );
        }

        map.insert(
            "bkt_bewertung_grob_gesamt".to_string(),
            &self.bewertung_grob_gesamt.bewertung,
        );
        map.insert(
            "bkt_bemerkung_grob_gesamt".to_string(),
            &self.bewertung_grob_gesamt.bemerkung,
        );

        for (index, bewertung_grob) in self.bewertung_grob.iter().enumerate() {
            map.insert(
                format!("bkt_bewertung_grob{}", index),
                &bewertung_grob.bewertung,
            );
            map.insert(
                format!("bkt_bemerkung_grob{}", index),
                &bewertung_grob.bemerkung,
            );
        }

        map
    }
}

#[derive(Debug, Serialize)]
pub struct VisuelleBeurteilung {
    pub bewertung_fein: Vec<Bewertung>,
    pub bewertung_fein_gesamt: Bewertung,

    pub bewertung_grob: Vec<Bewertung>,
    pub bewertung_grob_gesamt: Bewertung,
}

impl VisuelleBeurteilung {
    pub fn new(data: HashMap<String, String>, rights: HashMap<String, Rights>) -> Result<Self> {
        Ok(Self {
            bewertung_fein: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(Bewertung {
                        bewertung: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("bewertung_fein_{}_bewertung", i).as_str(),
                                "bewertung_fein_bewertung",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },

                        bemerkung: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("bewertung_fein_{}_prüfer", i).as_str(),
                                "bewertung_fein_bemerkung",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                    });

                    i += 1;
                }

                v
            },
            bewertung_fein_gesamt: Bewertung {
                bewertung: GuardedVariable::combine(
                    &data,
                    &rights,
                    (
                        "bewertung_fein_ges_bewertung",
                        "bewertung_fein_gesamt_bewertung",
                    ),
                )?,
                bemerkung: GuardedVariable::combine(
                    &data,
                    &rights,
                    (
                        "bewertung_fein_ges_prüfer",
                        "bewertung_fein_gesamt_bemerkung",
                    ),
                )?,
            },

            bewertung_grob: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(Bewertung {
                        bewertung: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("bewertung_grob_{}_bewertung", i).as_str(),
                                "bewertung_grob_bewertung",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                        bemerkung: match GuardedVariable::combine(
                            &data,
                            &rights,
                            (
                                format!("bewertung_grob_{}_prüfer", i).as_str(),
                                "bewertung_grob_bemerkung",
                            ),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                    });

                    i += 1;
                }

                v
            },
            bewertung_grob_gesamt: Bewertung {
                bewertung: GuardedVariable::combine(
                    &data,
                    &rights,
                    (
                        "bewertung_grob_ges_bewertung",
                        "bewertung_grob_gesamt_bewertung",
                    ),
                )?,
                bemerkung: GuardedVariable::combine(
                    &data,
                    &rights,
                    (
                        "bewertung_grob_ges_prüfer",
                        "bewertung_grob_gesamt_bemerkung",
                    ),
                )?,
            },
        })
    }

    fn to_map(&self) -> HashMap<String, &GuardedVariable<Option<String>>> {
        let mut map = HashMap::new();

        map.insert(
            "vb_bewertung_fein_gesamt".to_string(),
            &self.bewertung_fein_gesamt.bewertung,
        );
        map.insert(
            "vb_bemerkung_fein_gesamt".to_string(),
            &self.bewertung_fein_gesamt.bemerkung,
        );

        for (index, bewertung_fein) in self.bewertung_fein.iter().enumerate() {
            map.insert(
                format!("vb_bewertung_fein{}", index),
                &bewertung_fein.bewertung,
            );
            map.insert(
                format!("vb_bemerkung_fein{}", index),
                &bewertung_fein.bemerkung,
            );
        }

        map.insert(
            "vb_bewertung_grob_gesamt".to_string(),
            &self.bewertung_grob_gesamt.bewertung,
        );
        map.insert(
            "vb_bemerkung_grob_gesamt".to_string(),
            &self.bewertung_grob_gesamt.bemerkung,
        );

        for (index, bewertung_grob) in self.bewertung_grob.iter().enumerate() {
            map.insert(
                format!("vb_bewertung_grob{}", index),
                &bewertung_grob.bewertung,
            );
            map.insert(
                format!("vb_bemerkung_grob{}", index),
                &bewertung_grob.bemerkung,
            );
        }

        map
    }
}

#[derive(Debug, Serialize)]
pub struct NassLaborGrob {
    pub labor: GuardedVariable<String>,

    pub faser_stippen_rückstand_art_größe: GuardedVariable<String>,
    pub faser_stippen_rückstand: GuardedVariable<bool>,

    pub papierfremde_zerkleinerung: GuardedVariable<String>,
    pub papierfremde_material: GuardedVariable<String>,
    pub papierfremde_art_größe: GuardedVariable<String>,
    pub papierfremde_bestandteile: GuardedVariable<String>,

    pub sd_einwaage: GuardedVariable<f32>,
    pub sd_auswaage_ts: GuardedVariable<f32>,
    pub sd_auswaage: GuardedVariable<f32>,
    pub sd_filterblatt: GuardedVariable<f32>,

    pub reject_filtergewicht: GuardedVariable<f32>,
    pub reject_auswaage: GuardedVariable<f32>,

    pub volumen_ac: GuardedVariable<f32>,
    pub grammatur_ac: Vec<GuardedVariable<f32>>,
}

impl NassLaborGrob {
    pub fn new(data: HashMap<String, String>, rights: HashMap<String, Rights>) -> Result<Self> {
        Ok(Self {
            labor: GuardedVariable::combine(&data, &rights, "labor")?,

            faser_stippen_rückstand_art_größe: GuardedVariable::combine(
                &data,
                &rights,
                (
                    "faser-stippen-rückstand_art-größe",
                    "faser_stippen_rückstand_art_größe",
                ),
            )?,
            faser_stippen_rückstand: GuardedVariable::combine(
                &data,
                &rights,
                ("faser-stippen-rückstand", "faser_stippen_rückstand"),
            )?,

            papierfremde_zerkleinerung: GuardedVariable::combine(
                &data,
                &rights,
                ("papierfremde-zerkleinerung", "papierfremde_zerkleinerung"),
            )?,
            papierfremde_material: GuardedVariable::combine(
                &data,
                &rights,
                ("papierfremde-material", "papierfremde_material"),
            )?,
            papierfremde_art_größe: GuardedVariable::combine(
                &data,
                &rights,
                ("papierfremde-art-größe", "papierfremde_art_größe"),
            )?,
            papierfremde_bestandteile: GuardedVariable::combine(
                &data,
                &rights,
                ("papierfremde-bestandteile", "papierfremde_bestandteile"),
            )?,

            sd_einwaage: GuardedVariable::combine(&data, &rights, "sd_einwaage")?,
            sd_auswaage: GuardedVariable::combine(&data, &rights, "sd_auswaage")?,
            sd_auswaage_ts: GuardedVariable::combine(&data, &rights, "sd_auswaage_ts")?,
            sd_filterblatt: GuardedVariable::combine(&data, &rights, "sd_filterblatt")?,

            reject_filtergewicht: GuardedVariable::combine(
                &data,
                &rights,
                ("reject_filtergewicht_grob", "reject_filtergewicht"),
            )?,
            reject_auswaage: GuardedVariable::combine(
                &data,
                &rights,
                ("reject_auswaage_grob", "reject_auswaage"),
            )?,

            volumen_ac: GuardedVariable::combine(&data, &rights, ("volumen_ac1", "volumen_ac"))?,

            grammatur_ac: {
                let mut v = Vec::new();
                let mut i = 1;

                loop {
                    v.push(
                        match GuardedVariable::combine(
                            &data,
                            &rights,
                            (format!("grammatur_ac{}", i).as_str(), "grammatur_ac"),
                        ) {
                            Ok(v) => v,
                            Err(_) => break,
                        },
                    );

                    i += 1;
                }

                v
            },
        })
    }
}
