extern crate reqwest;

use indexmap::IndexMap;
use samplic::LoadableDevice;
use snafu::ResultExt;
use std::io::Read;
use std::str::FromStr;
use {error, samplic, serde, Result, Uuid};

#[derive(Clone)]
pub struct Device {
    url: String,
}

impl LoadableDevice for Device {
    fn load_unit_index(&self) -> Result<samplic::DirectoryListing> {
        Self::download_json(&format!("{}/unit", self.url))
    }

    fn load_unit(
        &self,
        unit_id: &str,
    ) -> Result<samplic::UnitDescription> {
        Self::download_json(&format!("{}/unit/{}", self.url, unit_id))
    }

    fn load_translation_index(&self) -> Result<samplic::DirectoryListing> {
        Self::download_json(&format!("{}/translation", self.url))
    }

    fn load_translation(
        &self,
        translation_id: &str,
    ) -> Result<samplic::Translation> {
        Self::download_json(&format!(
            "{}/translation/{}",
            self.url, translation_id
        ))
    }

    fn load_sample_metadata(
        &self,
        sample_id: &Uuid,
    ) -> Result<samplic::SampleMetadata> {
        Self::download_json(&format!(
            "{}/sample/{}/metadata",
            self.url,
            sample_id.to_hyphenated_ref()
        ))
    }

    fn load_measurement_metadata(
        &self,
        sample_id: &Uuid,
        measurement_index: usize,
    ) -> Result<samplic::MeasurementMetadata> {
        Self::download_json(&format!(
            "{}/sample/{}/measurement/{}/metadata",
            self.url,
            sample_id.to_hyphenated_ref(),
            measurement_index
        ))
    }

    fn load_curve(
        &self,
        sample_id: &Uuid,
        measurement_index: usize,
        curve_id: &str,
    ) -> Result<samplic::Curve> {
        Self::download_json(&format!(
            "{}/sample/{}/measurement/{}/curve/{}",
            self.url,
            sample_id.to_hyphenated_ref(),
            measurement_index,
            curve_id
        ))
    }

    fn load_curve_index(
        &self,
        sample_id: &Uuid,
        measurement_index: usize,
    ) -> Result<samplic::DirectoryListing> {
        Self::download_json(&format!(
            "{}/sample/{}/measurement/{}/curve",
            self.url,
            sample_id.to_hyphenated_ref(),
            measurement_index
        ))
    }

    fn load_results(
        &self,
        sample_id: &Uuid,
        measurement_index: usize,
    ) -> Result<samplic::MeasurementResults> {
        Self::download_json(&format!(
            "{}/sample/{}/measurement/{}/results",
            self.url,
            sample_id.to_hyphenated_ref(),
            measurement_index
        ))
    }

    fn load_sampling_index(&self) -> Result<samplic::DirectoryListing> {
        Self::download_json(&format!("{}/description/sampling", self.url))
    }

    fn load_measurement_index(
        &self,
        sample_id: &Uuid,
    ) -> Result<samplic::DirectoryListing> {
        Self::download_json(&format!(
            "{}/sample/{}/measurement",
            self.url,
            sample_id.to_hyphenated_ref()
        ))
    }

    fn load_sample_index(&self) -> Result<samplic::DirectoryListing> {
        Self::download_json(&format!("{}/sample", self.url))
    }

    fn load_device_description(
        &self,
    ) -> Result<samplic::DeviceDescription> {
        Self::download_json(&format!("{}/description/device", self.url))
    }

    fn load_profile(
        &self,
        profile_id: &Uuid,
    ) -> Result<samplic::ProfileDescription> {
        Self::download_json(&format!(
            "{}/description/profile/{}",
            self.url,
            profile_id.to_hyphenated_ref()
        ))
    }

    fn load_profile_index(&self) -> Result<samplic::DirectoryListing> {
        Self::download_json(&format!("{}/description/profile", self.url))
    }

    fn load_sampling(
        &self,
        sampling_id: &str,
    ) -> Result<samplic::SamplingDescription> {
        Self::download_json(&format!(
            "{}/description/sampling/{}",
            self.url, sampling_id
        ))
    }

    fn load_icon(&self, icon_id: &str) -> Result<samplic::Icon> {
        Self::download(&format!("{}/image/icon/{}/svg", self.url, icon_id))
    }

    fn load_icon_index(&self) -> Result<samplic::DirectoryListing> {
        Self::download_json(&format!("{}/image/icon", self.url))
    }
}

#[derive(Debug)]
pub struct Measurement {
    pub curves: IndexMap<String, samplic::Curve>,
    pub metadata: samplic::MeasurementMetadata,
    pub results: IndexMap<String, f64>,
}

#[derive(Debug)]
pub struct Sample {
    pub measurements: Vec<Measurement>,
    pub metadata: samplic::SampleMetadata,
}

#[derive(Debug)]
pub struct Description {
    pub device: samplic::DeviceDescription,
    pub profiles: IndexMap<Uuid, samplic::ProfileDescription>,
    pub samplings: IndexMap<String, samplic::SamplingDescription>,
    pub units: IndexMap<String, samplic::UnitDescription>,
    pub translations: IndexMap<String, IndexMap<String, String>>,
}

impl Device {
    pub fn new(baseurl: &str) -> Self {
        Device {
            url: baseurl.to_string(),
        }
    }

    fn download(url: &str) -> Result<String> {
        println!("Getting url {}", url);
        let mut response = reqwest::get(url).context(error::Reqwest {})?;
        let mut content = String::new();
        response.read_to_string(&mut content).context(
            error::ReadResponse {
                url: url.to_string(),
            },
        )?;
        Ok(content)
    }

    fn download_json<T>(url: &str) -> Result<T>
    where
        T: serde::de::DeserializeOwned,
    {
        println!("Getting url {}", url);
        reqwest::get(url)
            .context(error::Reqwest {})?
            .json()
            .context(error::Reqwest {})
    }

    pub fn load_units(
        &self,
    ) -> Result<IndexMap<String, samplic::UnitDescription>> {
        let directory = self.load_unit_index()?;

        let mut map = IndexMap::new();

        for (ref unit_id, _) in directory {
            map.insert(unit_id.to_string(), self.load_unit(unit_id)?);
        }

        Ok(map)
    }

    pub fn load_translations(
        &self,
    ) -> Result<IndexMap<String, IndexMap<String, String>>> {
        let directory = self.load_translation_index()?;

        let mut map = IndexMap::new();

        for (ref translation_id, _) in directory {
            map.insert(
                translation_id.to_string(),
                self.load_translation(translation_id)?,
            );
        }

        Ok(map)
    }

    pub fn load_curves(
        &self,
        sample_id: &Uuid,
        measurement_index: usize,
    ) -> Result<IndexMap<String, samplic::Curve>> {
        let directory =
            self.load_curve_index(sample_id, measurement_index)?;

        let mut map = IndexMap::new();

        for (ref curve_id, _) in directory {
            map.insert(
                curve_id.to_string(),
                self.load_curve(sample_id, measurement_index, curve_id)?,
            );
        }

        Ok(map)
    }

    pub fn load_measurement(
        &self,
        sample_id: &Uuid,
        index: usize,
    ) -> Result<Measurement> {
        Ok(Measurement {
            curves: self.load_curves(sample_id, index)?,
            metadata: self.load_measurement_metadata(sample_id, index)?,
            results: self.load_results(sample_id, index)?,
        })
    }

    pub fn load_measurements(
        &self,
        sample_id: &Uuid,
    ) -> Result<Vec<Measurement>> {
        let directory = self.load_measurement_index(sample_id)?;

        let mut vec = Vec::new();

        for (ref index, _) in directory {
            vec.push(self.load_measurement(
                sample_id,
                usize::from_str(index).context(error::ParseInt {
                    raw: index.to_string(),
                })?,
            )?);
        }

        Ok(vec)
    }

    pub fn load_sample(&self, sample_id: &Uuid) -> Result<Sample> {
        Ok(Sample {
            measurements: self.load_measurements(sample_id)?,
            metadata: self.load_sample_metadata(sample_id)?,
        })
    }

    pub fn load_samples(&self) -> Result<IndexMap<Uuid, Sample>> {
        let directory = self.load_sample_index()?;

        let mut map = IndexMap::new();

        for (ref sample_id, _) in directory {
            let uuid =
                Uuid::parse_str(sample_id).context(error::ParseUuid {
                    raw: sample_id.to_string(),
                })?;
            map.insert(uuid, self.load_sample(&uuid)?);
        }

        Ok(map)
    }

    pub fn load_profiles(
        &self,
    ) -> Result<IndexMap<Uuid, samplic::ProfileDescription>> {
        let directory = self.load_profile_index()?;

        let mut map = IndexMap::new();

        for (ref profile_id, _) in directory {
            let uuid =
                Uuid::parse_str(profile_id).context(error::ParseUuid {
                    raw: profile_id.to_string(),
                })?;
            map.insert(uuid, self.load_profile(&uuid)?);
        }

        Ok(map)
    }

    pub fn load_samplings(
        &self,
    ) -> Result<IndexMap<String, samplic::SamplingDescription>> {
        let directory = self.load_sampling_index()?;

        let mut map = IndexMap::new();

        for (ref sampling_id, _) in directory {
            map.insert(
                sampling_id.to_string(),
                self.load_sampling(sampling_id)?,
            );
        }

        Ok(map)
    }

    pub fn load_description(&self) -> Result<Description> {
        Ok(Description {
            device: self.load_device_description()?,
            profiles: self.load_profiles()?,
            samplings: self.load_samplings()?,
            units: self.load_units()?,
            translations: self.load_translations()?,
        })
    }

    pub fn load_icons(&self) -> Result<IndexMap<String, String>> {
        let directory = self.load_icon_index()?;

        let mut map = IndexMap::new();

        for (ref icon_id, _) in directory {
            map.insert(icon_id.to_string(), self.load_icon(icon_id)?);
        }

        Ok(map)
    }
}
