//! Common definitions used by the various `confget` modules.
//!
//! This module mainly defines the [`Config`] struct that provides
//! configuration settings for pretty much all of the functions in
//! the `confget` library. The easiest way to use it is to call
//! the [`Config::default`] method and override some settings as
//! needed:
//!
//! ```
//! let config = confget::Config {
//!     filename: Some("/etc/config.ini".to_string()),
//!     section: "client".to_string(),
//!     ..confget::Config::default()
//! };
//! ```

/*
 * Copyright (c) 2021, 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 std::error::Error;
use std::fmt::{Display, Formatter, Result as FormatResult};
use std::str::FromStr;

/// A list of features supported by the confget library.
pub const FEATURES: [(&str, &str); 3] = [
    ("BASE", env!("CARGO_PKG_VERSION")),
    ("REGEX", "1.0"),
    ("REGEX_IMPL_RUST_CRATE_REGEX", "1"),
];

/// An error that occurred during parsing the input data.
#[derive(Debug)]
pub struct ConfgetError {
    /// A human-readable error message.
    msg: String,
}

impl Display for ConfgetError {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult {
        write!(f, "{}", self.msg)
    }
}

impl Error for ConfgetError {}

impl ConfgetError {
    /// Return a `Box` containing an error object.
    #[inline]
    #[must_use]
    pub fn boxed(msg: String) -> Box<dyn Error> {
        Box::new(Self { msg })
    }
}

/// The configuration backend to use for parsing the input data.
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BackendKind {
    /// Parse INI-style files and their sections.
    Ini,
}

impl BackendKind {
    /// The name of the INI-style file backend.
    pub const INI: &'static str = "ini";
}

impl FromStr for BackendKind {
    type Err = ConfgetError;

    #[inline]
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            Self::INI => Ok(Self::Ini),
            other => Err(ConfgetError {
                msg: format!("unrecognized backend type '{}'", other),
            }),
        }
    }
}

impl AsRef<str> for BackendKind {
    #[inline]
    fn as_ref(&self) -> &str {
        match self {
            &Self::Ini => Self::INI,
        }
    }
}

/// Configuration settings for the `confget` functions.
///
/// This is the main way to control the behavior of a backend's
/// [`read_file`][`crate::backend::Backend::read_file`] method,
/// the [`read_ini_file`][`crate::read_ini_file`] function, and
/// the [`format::filter_vars`][`crate::format::filter_vars`] function:
/// specify what file to read, what variables to extract from it, and
/// how to format them.
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
#[allow(clippy::struct_excessive_bools)]
pub struct Config {
    /// The configuration backend to use.
    pub backend: BackendKind,
    /// The (backend-specific) filename to read data from.
    pub filename: Option<String>,
    /// Formatting: select all the variables in the specified section.
    pub list_all: bool,
    /// Formatting: treat the variable match patterns as regular expressions
    /// instead of glob ones.
    pub match_regex: bool,
    /// Formatting: treat `varnames` as a list of patterns, not exact
    /// variable names.
    pub match_var_names: bool,
    /// Formatting: only select variables with values that match a pattern.
    pub match_var_values: Option<String>,
    /// Formatting: specify a string to prepend to the variable name.
    pub name_prefix: String,
    /// Formatting: specify a string to append to the variable name.
    pub name_suffix: String,
    /// Formatting: select variables from the specified section.
    pub section: String,
    /// Formatting: read variables from the initial section (""), then
    /// override their values with variables from the one specified by
    /// the `section` field.
    pub section_override: bool,
    /// Formatting: if `section` is an empty string and there are no
    /// variables in the initial section, do not select the first section
    /// defined in the file.
    pub section_specified: bool,
    /// Formatting: make the output values suitable for parsing by
    /// Bourne-like shells.
    pub shell_escape: bool,
    /// Formatting: always display the variable name.
    pub show_var_name: bool,
    /// Formatting: select variable names or patterns to display.
    pub varnames: Vec<String>,
}

impl Default for Config {
    /// Initialize a [`Config`] object with default values.
    ///
    /// This is the recommended way to use the [`Config`] struct:
    /// only override the settings that must be overridden.
    #[inline]
    fn default() -> Self {
        Self {
            backend: BackendKind::Ini,
            filename: None,
            list_all: false,
            match_regex: false,
            match_var_names: false,
            match_var_values: None,
            name_prefix: String::new(),
            name_suffix: String::new(),
            section: String::new(),
            section_override: false,
            section_specified: false,
            shell_escape: false,
            show_var_name: false,
            varnames: Vec::new(),
        }
    }
}

impl Config {
    /// A deprecated alias for [`Config::default()`].
    #[inline]
    #[must_use]
    pub fn get_default() -> Self {
        Self::default()
    }
}
