use std::fmt::{Display, Error, Formatter, Write};

/// Represents a kind of lexical token.
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub enum Kind {
    /// A sequence of non-US-ASCII code points. (code points above 127)
    NonAscii,

    /// A single line-ending. (CR, LF, or CRLF)
    LineEnding,

    /// A sequence of US-ASCII whitespace chars. (spaces and tabs)
    Whitespace,

    /// A sequence of US-ASCII control chars. ([0-31] & 127, excluding CR, LF, and TAB)
    Controls,

    /// A sequence of US-ASCII letters, numbers, and underscores.
    Symbol,

    /// A single special US-ASCII char not listed above.
    Special(u8),
}

impl Kind {
    //! Formatting

    /// Formats the value of this kind with the formatter. Ensures the formatted value is readable.
    pub fn fmt_value(&self, f: &mut Formatter<'_>, value: &str) -> Result<(), Error> {
        for v in value.chars() {
            let c: char = match v {
                ' ' => 's',
                '\t' => 't',
                '\r' => 'r',
                '\n' => 'n',
                c if c > '~' || v < ' ' => '?',
                c => c,
            };
            f.write_char(c)?;
        }
        Ok(())
    }
}

impl Display for Kind {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        let s: &'static str = match self {
            Self::NonAscii => "non-ascii",
            Self::LineEnding => "line-ending",
            Self::Whitespace => "whitespace",
            Self::Controls => "controls",
            Self::Symbol => "symbol",
            Self::Special(_) => "special",
        };
        write!(f, "{}", s)
    }
}
