use hence::assembler::ToCode;
use itertools::Itertools;

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

impl ToCode for StackResult {
    fn to_code(&self) -> String {
        format!(
            "{}--{}",
            if self.before.is_empty() {
                "".to_string()
            } else {
                format!("{} ", self.before.join(" "))
            },
            if self.after.is_empty() {
                "".to_string()
            } else {
                format!("{} ", self.after.join(" "))
            }
        )
    }
}

#[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 ToCode for Node {
    fn to_code(&self) -> String {
        match self {
            Node::Comment(x) => format!("\\ {}", x),
            Node::String { mode, string } => format!("{}\" {}\"", mode, string),
            Node::Number(x) => x.to_string(),
            Node::WordDefinition { name, stack, body } => format!(
                ": {}{} {} ;",
                name,
                match stack {
                    Some(x) => format!(" {}", x.to_code()),
                    None => "".to_string(),
                },
                body.iter().map(|x| x.to_code()).join(" ")
            ),
            Node::Condition { if_body, else_body } => {
                if else_body.is_empty() {
                    format!("if {} then", if_body.iter().map(|x| x.to_code()).join(" "))
                } else {
                    format!(
                        "if {} else {} then",
                        if_body.iter().map(|x| x.to_code()).join(" "),
                        else_body.iter().map(|x| x.to_code()).join(" ")
                    )
                }
            }
            Node::Word(x) => x.to_owned(),
        }
    }
}

pub type Body = Vec<Node>;

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

impl ToCode for AST {
    fn to_code(&self) -> String {
        self.body.iter().map(|x| x.to_code()).join(" ")
    }
}
