//! Generate the test shell program.
/*
 * Copyright (c) 2022  Peter Pentchev <roam@ringlet.net>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

use anyhow::{Context as _, Result};
use config_diag::ConfigDiag;
use handlebars::{Context, Handlebars, Helper, HelperResult, Output, RenderContext, RenderError};

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

const TEMPLATE_NAME: &str = "run-tests";
const TEMPLATE_PATH: &str = "tests/test-data/run-test.sh.hbs";

fn helper_datafile(
    helper: &Helper<'_, '_>,
    _: &Handlebars<'_>,
    _: &Context,
    _: &mut RenderContext<'_, '_>,
    out: &mut (dyn Output),
) -> HelperResult {
    let value = helper
        .param(0)
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: datafile: no parameter in {:?}",
                helper
            ))
        })?
        .value()
        .as_str()
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: datafile: not a string parameter in {:?}",
                helper
            ))
        })?;
    let res = if value == "-" {
        "-".to_owned()
    } else if value.starts_with('/') {
        format!("{}", shell_words::quote(value))
    } else {
        format!("\"$datadir/{}\"", value)
    };
    out.write(&res)?;
    Ok(())
}

fn helper_shell_quote(
    helper: &Helper<'_, '_>,
    _: &Handlebars<'_>,
    _: &Context,
    _: &mut RenderContext<'_, '_>,
    out: &mut (dyn Output),
) -> HelperResult {
    let value = helper
        .param(0)
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: shell_quote: no parameter in {:?}",
                helper
            ))
        })?
        .value()
        .as_str()
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: shell_quote: not a string parameter in {:?}",
                helper
            ))
        })?;
    let res = shell_words::quote(value);
    out.write(&res)?;
    Ok(())
}

fn helper_shell_join(
    helper: &Helper<'_, '_>,
    _: &Handlebars<'_>,
    _: &Context,
    _: &mut RenderContext<'_, '_>,
    out: &mut (dyn Output),
) -> HelperResult {
    let arr = helper
        .param(0)
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: shell_join: no parameter in {:?}",
                helper
            ))
        })?
        .value()
        .as_array()
        .ok_or_else(|| {
            RenderError::new(format!(
                "Internal error: shell_join: not an array parameter in {:?}",
                helper
            ))
        })?
        .iter()
        .map(|item| {
            item.as_str().ok_or_else(|| {
                RenderError::new(format!(
                    "Internal error: shell_join: not a string: {:?}",
                    item
                ))
            })
        })
        .collect::<Result<Vec<_>, _>>()?;
    let res = shell_words::join(arr);
    out.write(&res)?;
    Ok(())
}

/// Build a handlebars renderer, load the template, etc.
pub fn generate(cfg: &Config, test_data: &TestData) -> Result<String> {
    let mut renderer = Handlebars::new();
    renderer.set_strict_mode(true);
    renderer.register_escape_fn(|value| value.to_owned());
    renderer.register_helper("datafile", Box::new(helper_datafile));
    renderer.register_helper("shell_quote", Box::new(helper_shell_quote));
    renderer.register_helper("shell_join", Box::new(helper_shell_join));
    renderer
        .register_template_file(TEMPLATE_NAME, TEMPLATE_PATH)
        .with_context(|| format!("Could not parse the {} template", TEMPLATE_PATH))?;

    cfg.diag(|| format!("Rendering {} tests", test_data.tests.len()));
    renderer
        .render(TEMPLATE_NAME, test_data)
        .with_context(|| format!("Could not render the {} template", TEMPLATE_NAME))
}
