//! Parse a test template.
//!
//! Define the hierarchy of the test data JSON file and provide
//! a function to parse and deserialize it.

use std::error;
use std::fs;

use config_diag::ConfigDiag;
use expect_exit::ExpectedResult;
use serde::Deserialize;

use crate::defs;

quick_error! {
    /// An error that occurred while parsing the test data file.
    #[derive(Debug)]
    pub enum ParseError {
        /// The test data file has the wrong format/version attribute.
        UnknownVersion(major: u32, minor: u32) { display("Unsupported format version {}.{}", major, minor) }
    }
}

fn helper_perlbool(
    helper: &handlebars::Helper<'_, '_>,
    _: &handlebars::Handlebars<'_>,
    _: &handlebars::Context,
    _: &mut handlebars::RenderContext<'_, '_>,
    out: &mut (dyn handlebars::Output),
) -> handlebars::HelperResult {
    let value = helper
        .param(0)
        .and_then(|param| {
            param.value().as_bool().map(|value| match value {
                false => "0",
                true => "1",
            })
        })
        .unwrap();
    out.write(value)?;
    Ok(())
}

fn helper_pybool(
    helper: &handlebars::Helper<'_, '_>,
    _: &handlebars::Handlebars<'_>,
    _: &handlebars::Context,
    _: &mut handlebars::RenderContext<'_, '_>,
    out: &mut (dyn handlebars::Output),
) -> handlebars::HelperResult {
    let value = helper
        .param(0)
        .and_then(|param| {
            param.value().as_bool().map(|value| match value {
                false => "False",
                true => "True",
            })
        })
        .unwrap();
    out.write(value)?;
    Ok(())
}

#[derive(Debug, Deserialize)]
struct TopFormatOnly {
    format: defs::TestFormat,
}

/// Parse a Handlebars template file.
pub fn parse_template(cfg: &defs::Config) -> Result<handlebars::Handlebars, Box<dyn error::Error>> {
    cfg.diag(|| format!("Parsing the {} template", cfg.template_path));
    let mut renderer = handlebars::Handlebars::new();
    renderer.set_strict_mode(true);
    renderer.register_escape_fn(|value| value.to_string());
    renderer.register_helper("perlbool", Box::new(helper_perlbool));
    renderer.register_helper("pybool", Box::new(helper_pybool));
    renderer.register_template_file(defs::TEMPLATE_NAME, &cfg.template_path)?;
    Ok(renderer)
}

/// Parse and deserialize a test data file.
pub fn parse_test_data(cfg: &defs::Config) -> Result<defs::TestData, Box<dyn error::Error>> {
    cfg.diag(|| format!("Parsing the {} test data file", cfg.data_path));
    let contents =
        fs::read_to_string(&cfg.data_path).expect_result_("Could not read the input file")?;

    // Check the format version
    let fmt: TopFormatOnly =
        serde_json::from_str(&contents).expect_result_("Could not parse the format version")?;
    if fmt.format.version.major != 0 || fmt.format.version.minor != 1 {
        return Err(Box::new(ParseError::UnknownVersion(
            fmt.format.version.major,
            fmt.format.version.minor,
        )));
    }

    // Now deserialize the data
    let data: defs::TestData =
        serde_json::from_str(&contents).expect_result_("Could not parse the test data")?;
    Ok(data)
}
