use crate::step::Step;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::fs::File;
use std::path::{Path, PathBuf};
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Specification {
    benchmarks: Vec<Benchmark>,
}
#[derive(Debug, thiserror::Error)]
pub enum SpecificationError {
        #[error("Duplicate benchmark name {0}")]
    DuplicateBenchmark(
                String,
    ),
        #[error("Couldn't open {0}: {1}")]
    Open(
                PathBuf,
                std::io::Error,
    ),
        #[error("Couldn't read YAML specification from {0}:\n  {1}")]
    Yaml(
                PathBuf,
                serde_yaml::Error,
    ),
}
#[derive(Debug, Serialize, Deserialize)]
struct Benchmark {
    benchmark: String,
    backups: Vec<Backup>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Backup {
    pub changes: Vec<Change>,
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) enum Change {
    #[serde(rename = "create")]
    Create(Create),
    #[serde(rename = "delete")]
    Delete(FileCount),
    #[serde(rename = "rename")]
    Rename(FileCount),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Create {
        pub files: u64,
        pub file_size: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileCount {
        pub files: u64,
}
impl Specification {
        pub fn from_file(filename: &Path) -> Result<Self, SpecificationError> {
        let f = File::open(filename)
            .map_err(|err| SpecificationError::Open(filename.to_path_buf(), err))?;
        let spec: Specification = serde_yaml::from_reader(f)
            .map_err(|err| SpecificationError::Yaml(filename.to_path_buf(), err))?;
        spec.check()?;
        Ok(spec)
    }
    fn check(&self) -> Result<(), SpecificationError> {
        let mut names = HashSet::new();
        for name in self.benchmarks.iter().map(|b| b.benchmark.to_string()) {
            if names.contains(&name) {
                return Err(SpecificationError::DuplicateBenchmark(name));
            }
            names.insert(name);
        }
        Ok(())
    }
        pub fn steps(&self) -> Vec<Step> {
        let mut steps = vec![];
        for b in self.benchmarks.iter() {
            let n = b.backups.len();
            let after_base = n;
            let restore_base = 2 * n;
            steps.push(Step::Start(b.benchmark.to_string()));
            for (i, backup) in b.backups.iter().enumerate() {
                for change in backup.changes.iter() {
                    steps.push(Step::from(change));
                }
                steps.push(Step::ManifestLive(i));
                steps.push(Step::Backup(i));
                let after = after_base + i;
                steps.push(Step::ManifestLive(after));
                steps.push(Step::CompareManifests(i, after));
            }
            for (i, _) in b.backups.iter().enumerate() {
                steps.push(Step::Restore(i));
                let restored = restore_base + i;
                steps.push(Step::ManifestRestored(restored));
                steps.push(Step::CompareManifests(i, restored));
            }
            steps.push(Step::Stop(b.benchmark.to_string()));
        }
        steps
    }
}