//! Format the variable names and values as specified by the configuration.
//!
//! The [`filter_vars`] function is mainly used by the `confget`
//! command-line tool, but e.g. the "format safely for Bourne shell use"
//! handling may also be useful to other consumers.

/**
 * Copyright (c) 2021  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 std::collections::HashMap;
use std::error;
use std::iter::FromIterator;

use crate::defs;

/// The name and value of a variable, formatted according to the configuration.
///
/// This is the structure returned by the [`filter_vars`] function.
#[derive(Debug)]
pub struct FormatOutput {
    /// The name of the variable as found in the input.
    pub name: String,
    /// The value of the variable as found in the input.
    pub value: String,
    /// The name of the variable, formatted according to the configuration.
    pub output_name: String,
    /// The value of the variable, formatted according to the configuration.
    pub output_value: String,
    /// The combination of `output_name` and `output_value` as defined by
    /// the configuration (e.g.
    /// [show_var_name][`crate::defs::Config::show_var_name`]).
    pub output_full: String,
}

fn compile_fnmatch(glob_pattern: &str) -> Result<regex::Regex, Box<dyn error::Error>> {
    /* Convert the fnmatch pattern to a regular expression string. */
    let pattern_vec: Vec<String> = glob_pattern
        .chars()
        .map(|chr| match chr {
            '?' => ".".to_string(),
            '*' => ".*".to_string(),
            '.' | '+' | '(' | ')' | '[' | ']' => format!("\\{}", chr),
            _ => chr.to_string(),
        })
        .collect();
    let pattern = format!("^{}$", String::from_iter(pattern_vec.iter().cloned()));
    match regex::Regex::new(&pattern) {
        Ok(re) => Ok(re),
        Err(err) => Err(defs::ConfgetError::boxed(format!(
            "Could not parse the '{}' glob pattern (regex pattern: '{}'): {}",
            glob_pattern, pattern, err
        ))),
    }
}

fn compile_regex(pattern: &str) -> Result<regex::Regex, Box<dyn error::Error>> {
    match regex::Regex::new(pattern) {
        Ok(re) => Ok(re),
        Err(err) => Err(defs::ConfgetError::boxed(format!(
            "Could not parse the '{}' regular expression: {}",
            pattern, err
        ))),
    }
}

fn get_varnames_only<'a>(
    config: &'a defs::Config,
    sect_data: &HashMap<&'a String, &String>,
) -> Result<Vec<&'a String>, Box<dyn error::Error>> {
    let mut res: Vec<&String> = match config.list_all {
        true => sect_data.keys().copied().collect(),
        false => match config.match_var_names {
            true => {
                let regexes: Vec<regex::Regex> = config
                    .varnames
                    .iter()
                    .map(|pat| match config.match_regex {
                        true => compile_regex(pat),
                        false => compile_fnmatch(pat),
                    })
                    .collect::<Result<Vec<regex::Regex>, Box<dyn error::Error>>>()?;
                sect_data
                    .keys()
                    .filter(|value| regexes.iter().any(|re| re.is_match(value)))
                    .copied()
                    .collect()
            }
            false => config
                .varnames
                .iter()
                .filter(|name| sect_data.contains_key(name))
                .collect(),
        },
    };
    res.sort();
    Ok(res)
}

fn get_varnames<'a>(
    config: &'a defs::Config,
    sect_data: &HashMap<&'a String, &String>,
) -> Result<Vec<&'a String>, Box<dyn error::Error>> {
    let varnames = get_varnames_only(config, sect_data)?;
    match &config.match_var_values {
        None => Ok(varnames),
        Some(pattern) => {
            let re = match config.match_regex {
                true => compile_regex(pattern)?,
                false => compile_fnmatch(pattern)?,
            };
            Ok(varnames
                .iter()
                .copied()
                .filter(|name| match sect_data.get(name) {
                    Some(value) => re.is_match(value),
                    None => false,
                })
                .collect())
        }
    }
}

/// Select only the variables requested by the configuration.
///
/// This function is mainly used by the `confget` command-line tool.
/// It examines the [Config][`crate::defs::Config`] object and
/// returns the variables that have been selected by its settings
/// (`varname`, `list_all`, `match_var_names`, etc.) from
/// the appropriate sections (`section`, `section_override`, etc.).
pub fn filter_vars(
    config: &defs::Config,
    data: &HashMap<String, HashMap<String, String>>,
    section: &str,
) -> Result<Vec<FormatOutput>, Box<dyn error::Error>> {
    let empty = HashMap::new();
    let sect_iter_first = match config.section_override {
        true => match data.get("") {
            Some(sdata) => sdata.iter(),
            None => empty.iter(),
        },
        false => empty.iter(),
    };
    let sect_iter_second = match data.get(section) {
        Some(sdata) => sdata.iter(),
        None => empty.iter(),
    };
    let sect_data: HashMap<&String, &String> = sect_iter_first.chain(sect_iter_second).collect();
    Ok(get_varnames(config, &sect_data)?
        .iter()
        .map(|name| {
            let value = sect_data.get(name).unwrap();
            let output_name = format!("{}{}{}", config.name_prefix, name, config.name_suffix);
            let output_value = match config.shell_escape {
                true => format!(
                    "'{}'",
                    value.split('\'').collect::<Vec<_>>().join("'\"'\"'")
                ),
                false => value.to_string(),
            };
            let output_full = match config.show_var_name {
                false => output_value.to_string(),
                true => format!("{}={}", output_name, output_value),
            };
            FormatOutput {
                name: name.to_string(),
                value: value.to_string(),
                output_name,
                output_value,
                output_full,
            }
        })
        .collect())
}
