use hence::assembler::ToCode;
use std::fmt;

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct StackResult {
    pub before: Vec<String>,
    pub after: Vec<String>,
}

impl fmt::Display for StackResult {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}--{}",
            if self.before.is_empty() {
                "".to_string()
            } else {
                format!("{} ", self.before.join(" "))
            },
            if self.after.is_empty() {
                "".to_string()
            } else {
                format!("{} ", self.after.join(" "))
            }
        )
    }
}

impl ToCode for StackResult {}

#[derive(Debug, PartialEq, Eq)]
pub enum Node {
    Comment(String),
    String {
        mode: String,
        string: String,
    },
    Number(i32),
    WordDefinition {
        name: String,
        stack: Option<StackResult>,
        body: Body,
    },
    Condition {
        if_body: Body,
        else_body: Body,
    },
    Word(String),
}

impl fmt::Display for Node {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Node::Comment(x) => write!(f, "\\ {}", x),
            Node::String { mode, string } => write!(f, "{}\" {}\"", mode, string),
            Node::Number(x) => write!(f, "{}", x),
            Node::WordDefinition { name, stack, body } => write!(
                f,
                ": {}{} {} ;",
                name,
                match stack {
                    Some(x) => format!(" {}", x),
                    None => "".to_string(),
                },
                // body.iter().map(|x| x.to_code()).join(" ")
                itertools::free::join(body, " ")
            ),
            Node::Condition { if_body, else_body } => {
                if else_body.is_empty() {
                    write!(
                        f,
                        "if {} then",
                        // if_body.iter().map(|x| x.to_code()).join(" ")
                        itertools::free::join(if_body, " ")
                    )
                } else {
                    write!(
                        f,
                        "if {} else {} then",
                        // if_body.iter().map(|x| x.to_code()).join(" "),
                        itertools::free::join(if_body, " "),
                        // else_body.iter().map(|x| x.to_code()).join(" ")
                        itertools::free::join(else_body, " "),
                    )
                }
            }
            Node::Word(x) => write!(f, "{}", x),
        }
    }
}

impl ToCode for Node {}

pub type Body = Vec<Node>;

#[derive(Debug)]
pub struct AST {
    pub body: Body,
}

impl fmt::Display for AST {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", itertools::free::join(&self.body, "\n"))
    }
}

impl ToCode for AST {}
