use super::{
    error, DirectoryListing, LoadableDevice, MeasurementMetadata,
    MeasurementResults, ProfileDescription, Result, ResultDescription,
    SampleMetadata, SamplingDescription, UnitDescription, Uuid,
};
use snafu::OptionExt;

pub struct Sample<'a> {
    pub device: &'a LoadableDevice,
    pub sample_id: Uuid,
}

pub trait GetSampleRef<'a> {
    fn get_sample_ref(&'a self, sample_id: Uuid) -> Sample<'a>;
}

impl<'a> GetSampleRef<'a> for LoadableDevice {
    fn get_sample_ref(&'a self, sample_id: Uuid) -> Sample<'a> {
        Sample {
            device: self,
            sample_id,
        }
    }
}

impl<'a> Sample<'a> {
    pub fn get_measurement_ref(
        &'a self,
        measurement_index: usize,
    ) -> Measurement<'a> {
        Measurement {
            device: self.device,
            sample_id: self.sample_id,
            measurement_index,
        }
    }

    pub fn load_metadata(&self) -> Result<SampleMetadata> {
        self.device.load_sample_metadata(&self.sample_id)
    }

    pub fn load_measurement_index(&self) -> Result<DirectoryListing> {
        self.device.load_measurement_index(&self.sample_id)
    }

    pub fn load_profile(&'a self) -> Result<ProfileDescription> {
        self.device.load_profile(&self.load_metadata()?.profile)
    }

    pub fn load_sampling(&'a self) -> Result<SamplingDescription> {
        self.device.load_sampling(&self.load_profile()?.sampling_id)
    }
}

pub struct Measurement<'a> {
    pub device: &'a LoadableDevice,
    pub sample_id: Uuid,
    pub measurement_index: usize,
}

impl<'a> Measurement<'a> {
    pub fn get_sample_ref(&'a self) -> Sample<'a> {
        Sample {
            device: self.device,
            sample_id: self.sample_id,
        }
    }

    pub fn load_metadata(&self) -> Result<MeasurementMetadata> {
        self.device.load_measurement_metadata(
            &self.sample_id,
            self.measurement_index,
        )
    }

    pub fn load_results(&self) -> Result<MeasurementResults> {
        self.device
            .load_results(&self.sample_id, self.measurement_index)
    }
}

pub struct MeasurementResult<'a> {
    pub device: &'a LoadableDevice,
    pub sample_id: Uuid,
    pub measurement_index: usize,
    pub result_id: String,
}

impl<'a> MeasurementResult<'a> {
    pub fn load(&'a self) -> Result<f64> {
        match self
            .device
            .load_results(&self.sample_id, self.measurement_index)?
            .get(&self.result_id)
        {
            Some(value) => Ok(*value),
            None => error::NotFound {
                kind: "result",
                id: self.result_id.to_string(),
            }
            .fail()?,
        }
    }

    pub fn get_measurement_ref(&'a self) -> Measurement<'a> {
        Measurement {
            device: self.device,
            sample_id: self.sample_id,
            measurement_index: self.measurement_index,
        }
    }

    pub fn get_sample_ref(&'a self) -> Sample<'a> {
        Sample {
            device: self.device,
            sample_id: self.sample_id,
        }
    }

    pub fn load_result_description(&self) -> Result<ResultDescription> {
        Ok(self
            .get_sample_ref()
            .load_sampling()?
            .results
            .get(&self.result_id)
            .context(error::NotFound {
                kind: "result",
                id: self.result_id.to_string(),
            })?
            .clone())
    }

    pub fn load_unit(&self) -> Result<UnitDescription> {
        let unit_id = self.load_result_description()?.unit.unit_id;
        self.device.load_unit(&unit_id)
    }
}
