//! Case Insensitive String. Only printable ASCII allowed.

#[cfg(test)]
mod test;

#[cfg(test)]
mod test_reasonable_str;

use std::{fmt, ops::Deref};

use crate::{
    json,
    warning::{self, IntoCaveat as _, WithElement as _},
    Verdict,
};

/// The warnings that can happen when parsing a case-insensitive string.
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Warning {
    /// There should be no escape codes in a `CiString`.
    ContainsEscapeCodes,

    /// There should only be printable ASCII bytes in a `CiString`.
    ContainsNonPrintableASCII,

    /// The JSON value given is not a string.
    InvalidType { type_found: json::ValueKind },

    /// The length of the string exceeds the specs constraint.
    InvalidLengthMax { length: usize },

    /// The length of the string is not equal to the specs constraint.
    InvalidLengthExact { length: usize },

    /// The casing of the string is not common practice.
    ///
    /// Note: This is not enforced by the string types in this module, but can be used
    /// by linting code to signal that the casing of a given string is unorthodox.
    PreferUppercase,
}

impl Warning {
    /// Create a `Warning` for invalid type.
    fn invalid_type(elem: &json::Element<'_>) -> Self {
        Self::InvalidType {
            type_found: elem.value().kind(),
        }
    }
}

impl fmt::Display for Warning {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::ContainsEscapeCodes => f.write_str("The string contains escape codes."),
            Self::ContainsNonPrintableASCII => {
                f.write_str("The string contains non-printable bytes.")
            }
            Self::InvalidType { type_found } => {
                write!(f, "The value should be a string but is `{type_found}`")
            }
            Self::InvalidLengthMax { length } => {
                write!(
                    f,
                    "The string is longer than the max length `{length}` defined in the spec.",
                )
            }
            Self::InvalidLengthExact { length } => {
                write!(f, "The string should be length `{length}`.")
            }
            Self::PreferUppercase => {
                write!(f, "Upper case is preferred")
            }
        }
    }
}

impl crate::Warning for Warning {
    fn id(&self) -> warning::Id {
        match self {
            Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
            Self::ContainsNonPrintableASCII => {
                warning::Id::from_static("contains_non_printable_ascii")
            }
            Self::InvalidType { type_found } => {
                warning::Id::from_string(format!("invalid_type({type_found})"))
            }
            Self::InvalidLengthMax { .. } => warning::Id::from_static("invalid_length_max"),
            Self::InvalidLengthExact { .. } => warning::Id::from_static("invalid_length_exact"),
            Self::PreferUppercase => warning::Id::from_static("prefer_upper_case"),
        }
    }
}

/// String that can have `[0..=MAX_LEN]` bytes.
///
/// Only printable ASCII allowed. Non-printable characters like: Carriage returns, Tabs, Line breaks, etc. are not allowed.
/// Case insensitivity is not enforced.
///
/// See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#11-cistring-type>.
/// See: <https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/types.md#11-cistring-type>.
#[derive(Copy, Clone, Debug)]
pub(crate) struct CiMaxLen<'buf, const MAX_LEN: usize>(&'buf str);

impl<const MAX_LEN: usize> Deref for CiMaxLen<'_, MAX_LEN> {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<const MAX_LEN: usize> fmt::Display for CiMaxLen<'_, MAX_LEN> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<'buf, const MAX_LEN: usize> json::FromJson<'buf> for CiMaxLen<'buf, MAX_LEN> {
    type Warning = Warning;

    fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
        let (s, mut warnings) = Base::from_json(elem)?.into_parts();

        if s.len() > MAX_LEN {
            warnings.insert(elem, Warning::InvalidLengthMax { length: MAX_LEN });
        }

        Ok(Self(s.0).into_caveat(warnings))
    }
}

/// String that can have `LEN` bytes exactly.
///
/// Only printable ASCII allowed. Non-printable characters like: Carriage returns, Tabs, Line breaks, etc. are not allowed.
/// Case insensitivity is not enforced.
///
/// See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#11-cistring-type>.
/// See: <https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/types.md#11-cistring-type>.
#[derive(Copy, Clone, Debug)]
pub(crate) struct CiExactLen<'buf, const LEN: usize>(&'buf str);

impl<const LEN: usize> Deref for CiExactLen<'_, LEN> {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl<const LEN: usize> fmt::Display for CiExactLen<'_, LEN> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<'buf, const LEN: usize> json::FromJson<'buf> for CiExactLen<'buf, LEN> {
    type Warning = Warning;

    fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
        let (s, mut warnings) = Base::from_json(elem)?.into_parts();

        if s.len() != LEN {
            warnings.insert(elem, Warning::InvalidLengthExact { length: LEN });
        }

        Ok(Self(s.0).into_caveat(warnings))
    }
}

/// Case Insensitive String. Only printable ASCII allowed. (Non-printable characters like: Carriage returns, Tabs, Line breaks, etc. are not allowed).
///
/// See: <https://github.com/ocpi/ocpi/blob/release-2.2.1-bugfixes/types.asciidoc#11-cistring-type>.
/// See: <https://github.com/ocpi/ocpi/blob/release-2.1.1-bugfixes/types.md#11-cistring-type>.
#[derive(Copy, Clone, Debug)]
struct Base<'buf>(&'buf str);

impl Deref for Base<'_> {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl fmt::Display for Base<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl<'buf> json::FromJson<'buf> for Base<'buf> {
    type Warning = Warning;

    fn from_json(elem: &json::Element<'buf>) -> Verdict<Self, Self::Warning> {
        let mut warnings = warning::Set::new();
        let Some(id) = elem.to_raw_str() else {
            return warnings.bail(elem, Warning::invalid_type(elem));
        };

        // We don't care about the details of any warnings the escapes in the Id may have.
        // The Id should simply not have any escapes.
        let s = id.has_escapes(elem).ignore_warnings();
        let s = match s {
            json::PendingStr::NoEscapes(s) => {
                if check_printable(s) {
                    warnings.insert(elem, Warning::ContainsNonPrintableASCII);
                }
                s
            }
            json::PendingStr::HasEscapes(escape_str) => {
                warnings.insert(elem, Warning::ContainsEscapeCodes);
                // We decode the escapes to check if any of the escapes result in non-printable ASCII.
                let decoded = escape_str
                    .decode_escapes()
                    .with_element(elem)
                    .ignore_warnings();

                if check_printable(&decoded) {
                    warnings.insert(elem, Warning::ContainsNonPrintableASCII);
                }

                escape_str.into_raw()
            }
        };

        Ok(Self(s).into_caveat(warnings))
    }
}

/// Return true if the given `str` has any Non-printable characters like:
/// Carriage returns, Tabs, Line breaks, etc.
fn check_printable(s: &str) -> bool {
    s.chars()
        .any(|c| c.is_ascii_whitespace() || c.is_ascii_control())
}

/// The size of the input `str` exceeds the maximum deemed reasonable.
pub(crate) struct SizeExceedsMax(());

impl std::error::Error for SizeExceedsMax {}

impl fmt::Debug for SizeExceedsMax {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_tuple("SizeExceedsMax").finish()
    }
}

impl fmt::Display for SizeExceedsMax {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "The size of the input string exceeds the maximum length of {} megabytes",
            ReasonableLen::FACTOR
        )
    }
}

/// A `str` that is checked for having a reasonable size.
#[derive(Copy, Clone)]
pub(crate) struct ReasonableLen<'buf>(&'buf str);

impl<'buf> ReasonableLen<'buf> {
    /// One million bytes of information.
    const MEGA: usize = 1_000_000;

    /// Limit the string to this many megabyte.
    pub(crate) const FACTOR: usize = 5;

    /// The maximum allowed size for a `str` given to a parse function.
    ///
    /// If the input `str` exceeds this size, a [`SizeExceedsMax`] is returned.
    ///
    /// NOTE: Currently the largest tariff at `NLENE` is ~440 kilobyte and the largest CDR is ~1.1 megabytes.
    ///
    /// NOTE: The motivation for a limit is to avoid parsing unseasonably large JSON objects
    /// whether supplied through incompetence or maliciousness. Large JSON objects can be constructed
    /// to have many warnings. This could bog down the function processing the JSON object.
    pub(crate) const MAX_STR_INPUT_LEN: usize = Self::FACTOR * Self::MEGA;

    /// Create new `ReasonableLen` object.
    pub(crate) fn new(s: &'buf str) -> Result<ReasonableLen<'buf>, SizeExceedsMax> {
        if s.len() >= Self::MAX_STR_INPUT_LEN {
            return Err(SizeExceedsMax(()));
        }

        Ok(Self(s))
    }

    /// Unpack the contained `str`.
    pub(crate) fn into_inner(self) -> &'buf str {
        self.0
    }
}
