// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
// SPDX-License-Identifier: BSD-2-Clause
//! Parse a test template.
//!
//! Define the hierarchy of the test data JSON file and provide
//! a function to parse and deserialize it.

use std::fs;

use anyhow::Context as _;
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError};
use tracing::debug;

use crate::defs::{Config, Error, TestData, TEMPLATE_NAME};

/// Output a boolean value as a Perl 0 or 1 constant.
fn helper_perlbool(
    helper: &Helper<'_, '_>,
    _: &Handlebars<'_>,
    _: &Context,
    _: &mut RenderContext<'_, '_>,
    out: &mut (dyn Output),
) -> HelperResult {
    let value = if helper
        .param(0)
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: perlbool: no parameter in {helper:?}"
            ))
        })?
        .value()
        .as_bool()
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: perlbool: not a boolean parameter in {helper:?}"
            ))
        })? {
        "1"
    } else {
        "0"
    };
    out.write(value)?;
    Ok(())
}

/// Output a boolean value as a Python "True" or "False" constant.
fn helper_pybool(
    helper: &Helper<'_, '_>,
    _: &Handlebars<'_>,
    _: &Context,
    _: &mut RenderContext<'_, '_>,
    out: &mut (dyn Output),
) -> HelperResult {
    let value = if helper
        .param(0)
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: pybool: no parameter in {helper:?}"
            ))
        })?
        .value()
        .as_bool()
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: perlbool: not a boolean parameter in {helper:?}"
            ))
        })? {
        "True"
    } else {
        "False"
    };
    out.write(value)?;
    Ok(())
}

/// Parse a Handlebars template file.
///
/// # Errors
///
/// Fails on template parsing errors from
/// [`handlebars::Handlebars::register_template_file`].
#[allow(clippy::module_name_repetitions)]
#[inline]
pub fn parse_template(cfg: &Config) -> Result<Handlebars<'_>, Error> {
    debug!(
        "Parsing the {template} template",
        template = cfg.template_path
    );
    let mut renderer = Handlebars::new();
    renderer.set_strict_mode(true);
    renderer.register_escape_fn(ToOwned::to_owned);
    renderer.register_helper("perlbool", Box::new(helper_perlbool));
    renderer.register_helper("pybool", Box::new(helper_pybool));
    renderer
        .register_template_file(TEMPLATE_NAME, &cfg.template_path)
        .map_err(Error::ParseTemplate)?;
    Ok(renderer)
}

/// Parse and deserialize a test data file.
///
/// # Errors
///
/// Fails on filesystem errors and parse errors, including an invalid version
/// specified for the test data format.
#[allow(clippy::module_name_repetitions)]
#[inline]
pub fn parse_test_data(cfg: &Config) -> Result<TestData, Error> {
    debug!("Parsing the {data} test data file", data = cfg.data_path);
    let contents = fs::read_to_string(&cfg.data_path)
        .with_context(|| format!("Could not read the {data} input file", data = cfg.data_path))
        .map_err(Error::IO)?;

    let fver = typed_format_version::get_version_from_str(&contents, serde_json::from_str)
        .map_err(Error::VersionLoad)?;
    if fver.as_version_tuple() != (0, 1) {
        return Err(Error::VersionUnknown(fver.major(), fver.minor()));
    }

    // Now deserialize the data
    serde_json::from_str(&contents).map_err(Error::ParseJson)
}
