#[derive(Clone, Debug)]
pub enum CommandType {
    String(String),
    Number(f64),
    FunctionDefinition(String, Vec<String>, Box<Command>),
    Identifier(String),
    Chain(Vec<Command>),
    Assign(String),
    Comment(String),
}

impl Display for CommandType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CommandType::String(s) => write!(f, "string {}", s),
            CommandType::Number(n) => write!(f, "number {}", n),
            CommandType::FunctionDefinition(name, _, _) => {
                write!(f, "function definition of {}", name)
            }
            CommandType::Identifier(v) => write!(f, "identifier {}", v),
            CommandType::Chain(_) => write!(f, "chain"),
            CommandType::Assign(a) => write!(f, "assignment to {}", a),
            CommandType::Comment(_) => write!(f, "comment"),
        }
    }
}

pub type ErrorAt = (String, (usize, usize));

#[derive(Debug, Clone)]
pub struct Command {
    pub kind: CommandType,
    pub start: (usize, usize),
    pub end: (usize, usize),
}

type TokenIter<'a> = Peekable<Iter<'a, TokenAt>>;

use std::{collections::HashSet, fmt::Display, iter::Peekable, slice::Iter};

use crate::tokenizer::{Token, TokenAt};

pub(crate) fn parse(tokens: Vec<TokenAt>) -> Result<Vec<Command>, ErrorAt> {
    let mut tokens = tokens.iter().peekable();
    parse_chain(&mut tokens, false)
}

fn token_to_command(token: TokenAt, kind: CommandType) -> Command {
    Command {
        kind,
        start: token.1,
        end: token.1,
    }
}

fn parse_assign(tokens: &mut TokenIter, equal: TokenAt) -> Result<Command, ErrorAt> {
    let name = match tokens.next() {
        Some((Token::Word(name), _)) => name,
        o => {
            return Err((
                format!(
                    "Expected variable name, got {}",
                    match o {
                        None => "file end".to_string(),
                        Some(a) => format!("{}", a.0),
                    }
                ),
                equal.1,
            ));
        }
    };
    Ok(Command {
        kind: CommandType::Assign(name.clone()),
        start: equal.1,
        end: equal.1,
    })
}

fn parse_command(tokens: &mut TokenIter, token: TokenAt) -> Result<Command, ErrorAt> {
    if matches!(token, (Token::Assign, _)) {
        return parse_assign(tokens, token);
    }
    match token.0 {
        Token::Word(ref c) => {
            let str = c.clone();
            Ok(token_to_command(token, CommandType::Identifier(str)))
        }
        Token::String(ref s) => {
            let str = s.clone();
            Ok(token_to_command(token, CommandType::String(str)))
        }
        Token::Number(n) => Ok(token_to_command(token, CommandType::Number(n))),
        Token::OpenBracket => Ok(Command {
            kind: CommandType::Chain(parse_chain(tokens, true)?),
            start: (0, 0),
            end: (0, 0),
        }),
        Token::ClosingBracket => Err(("Unexpected closing parenthesis".into(), token.1)),
        Token::Assign => unreachable!(),
        Token::Colon => Err(("Expected function body before colon".into(), token.1)),
        Token::Comment(c) => Ok(Command {
            kind: CommandType::Comment(c),
            start: token.1,
            end: token.1,
        }),
    }
}

/// Parse a list of statements until the end or a closing bracket is reached.
fn parse_chain(tokens: &mut TokenIter, in_brackets: bool) -> Result<Vec<Command>, ErrorAt> {
    let mut commands: Vec<Command> = Vec::new();
    let mut next = tokens.peek().copied();
    if next.is_some() {
        tokens.next();
    };
    loop {
        match next {
            Some(token) => {
                if in_brackets && matches!(token, (Token::ClosingBracket, _)) {
                    break;
                } else {
                    let cmd = parse_command(tokens, token.clone())?;
                    commands.push(cmd)
                };
                if let Some((Token::Colon, at)) = tokens.peek() {
                    let definition = parse_function_definition(tokens, &mut commands, at)?;
                    commands.push(definition);
                };
                next = tokens.peek().copied();
                if next.is_some() {
                    tokens.next();
                };
            }
            None => {
                if in_brackets {
                    next = tokens.next();
                    if next.is_none() {
                        return Err((
                            "Expected closing parenthesis, got file end".to_string(),
                            (0, 0),
                        ));
                    }
                } else {
                    break;
                }
            }
        }
    }
    Ok(commands)
}

fn parse_function_definition(
    tokens: &mut TokenIter,
    commands: &mut Vec<Command>,
    colon: &(usize, usize),
) -> Result<Command, ErrorAt> {
    tokens.next();
    let args;
    let name_token = match commands.pop() {
        Some(Command {
            kind: CommandType::Chain(c),
            start: _,
            end: _,
        }) => {
            let mut found = HashSet::with_capacity(0);
            args = c
                .iter()
                .map(|a| match a {
                    Command {
                        kind: CommandType::Identifier(c),
                        start,
                        end: _,
                    } => {
                        if found.get(c.as_str()).is_some() {
                            return Err((format!("Got parameter {} twice", c), *start));
                        };
                        found.insert(c.as_str());
                        Ok(c.to_owned())
                    }
                    o => Err((
                        format!("Invalid function argument name {}", o.kind),
                        o.start,
                    )),
                })
                .collect::<Result<Vec<String>, ErrorAt>>()?;
            commands.pop()
        }
        o => {
            args = vec![];
            o
        }
    };
    let name = match name_token {
        Some(Command {
            kind: CommandType::Identifier(n),
            start: _,
            end: _,
        }) => n,
        o => {
            return Err((
                format!(
                    "Expected function name before colon, got {}",
                    match o {
                        Some(e) => format!("{}", e.kind),
                        None => "nothing".to_string(),
                    }
                ),
                (colon.0, colon.1.saturating_sub(1)),
            ))
        } // TODO: Make this position better.
    };
    let body = match tokens.next() {
        Some(next) => parse_command(tokens, next.clone())?,
        None => {
            return Err(("Expected function body, got file end".to_string(), *colon));
        }
    };
    Ok(Command {
        kind: CommandType::FunctionDefinition(name, args, Box::new(body)),
        start: *colon,
        end: *colon,
    })
}