//! Deserialization structs for the traces TOML config format.

use std::{collections::HashMap, path::Path};

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TracesConfig {
    pub traces_db: TracesDb,
    pub traces: Vec<TraceEntry>,
}

impl TracesConfig {
    pub fn load(path: &Path) -> Result<TracesConfig> {
        let contents = std::fs::read_to_string(path)
            .with_context(|| format!("reading config file {}", path.display()))?;
        toml::from_str(&contents).with_context(|| format!("parsing TOML config {}", path.display()))
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TracesDb {
    pub download_url: String,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct TraceEntry {
    pub path: String,
    /// When true, we shouldn't use apitrace's internal frame looping support
    /// due to instability (not all sequences of API calls can just be
    /// replayed).  Instead, do the replay as many times as we are capturing
    /// frames.
    #[serde(default)]
    pub nonloopable: bool,
    /// Extra arguments appended to the replay command for this trace,
    /// regardless of device.  Concatenated with any per-device replay_args.
    #[serde(default)]
    pub replay_args: Vec<String>,
    #[serde(default)]
    pub devices: HashMap<String, DeviceEntry>,
}

impl TraceEntry {
    pub fn device(&self, name: &str) -> Option<&DeviceEntry> {
        self.devices.get(name)
    }
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DeviceEntry {
    pub checksum: String,
    /// Don't run any other traces in parallel with this one.
    #[serde(default)]
    pub singlethread: bool,
    /// Skip this trace on this device entirely.
    #[serde(default)]
    pub skip: bool,
    /// Extra arguments appended to the replay command for this trace on this
    /// device.  Concatenated after any per-trace replay_args.
    #[serde(default)]
    pub replay_args: Vec<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    const MINIMAL_TOML: &str = r#"
[traces_db]
download_url = "https://s3.freedesktop.org/mesa-tracie-public/"

[[traces]]
path = "valve/half-life-2-v2.trace"
devices = {
    freedreno-a306 = {
        checksum = "8f5929c82e7d990e8c3d2bea14688224aabbccdd8f5929c82e7d990e8c3d2bea",
        skip = true,
    },
    freedreno-a530 = {
        checksum = "c7b816feafeae42eef3ccd5357db4cd7c7b816feafeae42eef3ccd5357db4cd7",
    },
}

[[traces]]
path = "valve/portal-2-v2.trace"
devices = {
    freedreno-a530 = {
        checksum = "102a09ce76092436173fd09a6a2bd941102a09ce76092436173fd09a6a2bd941"
    },
}
"#;

    #[test]
    fn parse_minimal_toml() {
        let config: TracesConfig = toml::from_str(MINIMAL_TOML).expect("parsing TOML");

        assert_eq!(
            config.traces_db.download_url,
            "https://s3.freedesktop.org/mesa-tracie-public/"
        );
        assert_eq!(config.traces.len(), 2);

        let hl2 = &config.traces[0];
        assert_eq!(hl2.path, "valve/half-life-2-v2.trace");
        assert_eq!(hl2.devices.len(), 2);

        let a306 = &hl2.device("freedreno-a306").unwrap();
        assert_eq!(
            a306.checksum,
            "8f5929c82e7d990e8c3d2bea14688224aabbccdd8f5929c82e7d990e8c3d2bea"
        );
        assert!(a306.skip);

        let a530 = &hl2.device("freedreno-a530").unwrap();
        assert!(!a530.skip);
    }

    #[test]
    fn device_lookup() {
        let config: TracesConfig = toml::from_str(MINIMAL_TOML).expect("parsing TOML");
        let hl2 = &config.traces[0];

        assert!(hl2.device("freedreno-a306").is_some());
        assert!(hl2.device("freedreno-a530").is_some());
        assert!(hl2.device("unknown-device").is_none());
    }

    #[test]
    fn skip_detection() {
        let config: TracesConfig = toml::from_str(MINIMAL_TOML).expect("parsing TOML");
        let hl2 = &config.traces[0];

        let a306 = hl2.device("freedreno-a306").unwrap();
        assert!(a306.skip);

        let a530 = hl2.device("freedreno-a530").unwrap();
        assert!(!a530.skip);
    }

    #[test]
    fn trace_with_no_device_entry() {
        let config: TracesConfig = toml::from_str(MINIMAL_TOML).expect("parsing TOML");
        let portal2 = &config.traces[1];

        assert_eq!(portal2.path, "valve/portal-2-v2.trace");
        assert!(portal2.device("freedreno-a306").is_none());
        assert!(portal2.device("freedreno-a530").is_some());
    }
}
