use std::fmt::Display;

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum FormatType {
    Whitespace,
    Emphasized,
    Dimmed,
    Text,
    String,
    Keyword,
    Value,
    Unit,
    Identifier,
    TypeIdentifier,
    Operator,
    Decorator,
}

#[derive(Debug, Clone, PartialEq)]
pub enum OutputType {
    Normal,
    Optional,
}

#[derive(Debug, Clone, PartialEq)]
pub struct FormattedString(pub OutputType, pub FormatType, pub String);

#[derive(Debug, Clone, Default, PartialEq)]
pub struct Markup(pub Vec<FormattedString>);

impl Markup {
    pub fn from(f: FormattedString) -> Self {
        Self(vec![f])
    }
}

impl Display for Markup {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", PlainTextFormatter {}.format(self, false))
    }
}

impl std::ops::Add for Markup {
    type Output = Markup;

    fn add(self, rhs: Self) -> Self::Output {
        let mut res = self.0;
        res.extend_from_slice(&rhs.0);
        Markup(res)
    }
}

impl std::ops::AddAssign for Markup {
    fn add_assign(&mut self, rhs: Self) {
        self.0.extend(rhs.0)
    }
}

impl std::iter::Sum for Markup {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(empty(), |acc, n| acc + n)
    }
}

pub fn space() -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Whitespace,
        " ".to_string(),
    ))
}

pub fn empty() -> Markup {
    Markup::default()
}

pub fn whitespace(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Whitespace,
        text.as_ref().to_string(),
    ))
}

pub fn emphasized(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Emphasized,
        text.as_ref().to_string(),
    ))
}

pub fn dimmed(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Dimmed,
        text.as_ref().to_string(),
    ))
}

pub fn text(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Text,
        text.as_ref().to_string(),
    ))
}

pub fn string(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::String,
        text.as_ref().to_string(),
    ))
}

pub fn keyword(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Keyword,
        text.as_ref().to_string(),
    ))
}

pub fn value(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Value,
        text.as_ref().to_string(),
    ))
}

pub fn unit(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Unit,
        text.as_ref().to_string(),
    ))
}

pub fn identifier(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Identifier,
        text.as_ref().to_string(),
    ))
}

pub fn type_identifier(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::TypeIdentifier,
        text.as_ref().to_string(),
    ))
}

pub fn operator(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Operator,
        text.as_ref().to_string(),
    ))
}

pub fn decorator(text: impl AsRef<str>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Decorator,
        text.as_ref().to_string(),
    ))
}

pub fn nl() -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Whitespace,
        "\n".into(),
    ))
}

pub trait Formatter {
    fn format_part(&self, part: &FormattedString) -> String;

    fn format(&self, markup: &Markup, indent: bool) -> String {
        let spaces = self.format_part(&FormattedString(
            OutputType::Normal,
            FormatType::Whitespace,
            "  ".into(),
        ));

        let mut output: String = String::new();
        if indent {
            output.push_str(&spaces);
        }
        for part in &markup.0 {
            output.push_str(&self.format_part(part));
            if indent && part.2.contains('\n') {
                output.push_str(&spaces);
            }
        }
        output
    }
}

pub struct PlainTextFormatter;

impl Formatter for PlainTextFormatter {
    fn format_part(&self, FormattedString(_, _, text): &FormattedString) -> String {
        text.clone()
    }
}
