use crate::syntax::lex::{Punct, WordSegment};

use super::lex::{self, InputRedirect, Keyword, Redirect, Token, WordToken};

#[derive(Debug, thiserror::Error)]
pub enum Error<'a> {
    #[error("{0}")]
    Lex(nom::Err<nom::error::Error<String>>),
    #[error("Unexpected EOF")]
    Incomplete,
    #[error("Expected {expected}, got {got}")]
    Expected { expected: Token<'a>, got: Token<'a> },
    #[error("Unexpected {0}")]
    Unexpected(Token<'a>),
    #[error("Invalid case pattern: {0}")]
    InvalidCasePattern(WordToken<'a>),
}

#[derive(Debug, PartialEq)]
pub struct PipelineElement<'a> {
    pub words: Vec<WordToken<'a>>,
    pub redirects: Vec<Redirect<'a>>,
}

#[derive(Debug, PartialEq)]
pub struct PipelineExpression<'a> {
    pub elements: Vec<PipelineElement<'a>>,
}

#[derive(Debug, PartialEq)]
pub enum BinaryOperator {
    And,
    Or,
    Equal,
    NotEqual,
}

#[derive(Debug, PartialEq)]
pub struct IfExpression<'a> {
    pub condition: Box<Expression<'a>>,
    pub if_true: Vec<Expression<'a>>,
    pub if_false: Option<Vec<Expression<'a>>>,
    pub redirects: Vec<Redirect<'a>>,
}

#[derive(Debug, PartialEq)]
pub struct WhileExpression<'a> {
    pub condtion: Box<Expression<'a>>,
    pub body: Vec<Expression<'a>>,
    pub redirects: Vec<Redirect<'a>>,
}

#[derive(Debug, PartialEq)]
pub struct ForExpression<'a> {
    pub variable: &'a str,
    pub list: Box<PipelineElement<'a>>,
    pub body: Vec<Expression<'a>>,
    pub redirects: Vec<Redirect<'a>>,
}

#[derive(Debug, PartialEq)]
pub struct CaseBranch<'a> {
    pub pattern: WordToken<'a>,
    pub body: Vec<Expression<'a>>,
}

#[derive(Debug, PartialEq)]
pub struct CaseExpression<'a> {
    pub expression: WordToken<'a>,
    pub branches: Vec<CaseBranch<'a>>,
    pub redirects: Vec<Redirect<'a>>,
}

#[derive(Debug, PartialEq)]
pub struct ConditionalExpression<'a> {
    pub body: Box<Expression<'a>>,
}

#[derive(Debug, PartialEq)]
pub struct GroupExpression<'a> {
    pub body: Vec<Expression<'a>>,
    pub redirects: Vec<Redirect<'a>>,
}

#[derive(Debug, PartialEq)]
pub enum Expression<'a> {
    Pipeline(PipelineExpression<'a>),
    Binary(BinaryOperator, Box<Expression<'a>>, Box<Expression<'a>>),
    If(IfExpression<'a>),
    While(WhileExpression<'a>),
    For(ForExpression<'a>),
    Case(CaseExpression<'a>),
    Conditional(ConditionalExpression<'a>),
    Not(Box<Expression<'a>>),
    Group(GroupExpression<'a>),
}

impl From<nom::Err<nom::error::Error<&str>>> for Error<'_> {
    fn from(value: nom::Err<nom::error::Error<&str>>) -> Self {
        let e = value.map_input(|e| e.to_owned());
        Self::Lex(e)
    }
}

fn parse_pipeline_element(mut input: &str) -> Result<(&str, PipelineElement), Error> {
    let mut words = vec![];
    let mut redirects = vec![];
    loop {
        if input.is_empty() {
            break;
        }
        let (rest, token) = lex::lex_token(input)?;
        match token {
            Token::Word(word) => words.push(word),
            Token::Keyword(keyword) => words.push(keyword.to_word()),
            Token::Redirect(Redirect::Input(InputRedirect::Heredoc(_))) => todo!("Heredoc"),
            Token::Redirect(redirect) => redirects.push(redirect),
            _ => {
                if words.is_empty() {
                    return Err(Error::Unexpected(token));
                }
                break;
            }
        }
        input = rest;
    }
    assert!(!words.is_empty());
    Ok((input, PipelineElement { words, redirects }))
}

fn parse_pipeline(mut input: &str) -> Result<(&str, PipelineExpression), Error> {
    let mut elements = vec![];
    let mut redirects = vec![];

    loop {
        if input.is_empty() {
            break;
        }
        input = skip_newline(input)?;
        // Pipeline starts with an element
        let (rest, element) = parse_pipeline_element(input)?;
        elements.push(element);
        input = rest;

        if input.is_empty() {
            break;
        }
        let (rest, token) = lex::lex_token(input)?;
        match token {
            Token::Punct(Punct::Pipe) => {
                input = rest;
            }
            Token::Punct(Punct::Newline) => break,
            Token::Punct(Punct::Semicolon) => {
                input = rest;
                break;
            }
            _ => break,
        }
    }

    // Redirects follow the pipeline
    loop {
        if input.is_empty() {
            break;
        }
        let (rest, token) = lex::lex_token(input)?;

        if let Token::Redirect(redirect) = token {
            input = rest;
            redirects.push(redirect);
        } else {
            break;
        }
    }

    Ok((input, PipelineExpression { elements }))
}

fn parse_until<'i, T, P: Fn(&'i str) -> Result<(&'i str, T), Error<'i>>, Q: Fn(&Token) -> bool>(
    mut input: &'i str,
    parser: P,
    predicate: Q,
) -> Result<(&'i str, Vec<T>), Error<'i>> {
    let mut elements = vec![];
    loop {
        if input.is_empty() {
            break;
        }
        input = skip_newline(input)?;
        let (_, token) = lex::lex_token(input)?;
        if predicate(&token) {
            break;
        }

        let (rest, element) = parser(input)?;
        input = rest;
        elements.push(element);
    }
    Ok((input, elements))
}

fn parse_if(input: &str) -> Result<(&str, Expression), Error> {
    let (mut input, condition) = parse_expression(input)?;
    loop {
        input = skip_newline(input)?;
        let (rest, then_or_semicolon) = lex::lex_token(input)?;
        match then_or_semicolon {
            Token::Punct(Punct::Semicolon) => {
                input = rest;
            }
            Token::Keyword(Keyword::Then) => {
                input = rest;
                break;
            }
            _ => {
                return Err(Error::Expected {
                    expected: Token::Keyword(Keyword::Then),
                    got: then_or_semicolon,
                })
            }
        }
    }
    // Parse until else/fi
    let (input, if_true) = parse_until(input, parse_expression, |t| {
        matches!(t, Token::Keyword(Keyword::Fi | Keyword::Else))
    })?;
    // else/fi
    let (input, else_or_fi) = lex::lex_token(input)?;
    let (input, if_false) = match else_or_fi {
        Token::Keyword(Keyword::Fi) => (input, None),
        Token::Keyword(Keyword::Else) => {
            let (input, if_false) = parse_until(input, parse_expression, |t| {
                matches!(t, Token::Keyword(Keyword::Fi))
            })?;
            // Skip fi
            let (input, _) = lex::lex_token(input)?;
            (input, Some(if_false))
        }
        _ => unreachable!(),
    };
    let (input, redirects) = parse_redirects(input)?;

    Ok((
        input,
        Expression::If(IfExpression {
            condition: Box::new(condition),
            if_true,
            if_false,
            redirects,
        }),
    ))
}

fn parse_while(input: &str) -> Result<(&str, Expression), Error> {
    let (mut input, condition) = parse_expression(input)?;
    loop {
        input = skip_newline(input)?;
        let (rest, do_or_semicolon) = lex::lex_token(input)?;
        match do_or_semicolon {
            Token::Punct(Punct::Semicolon) => {
                input = rest;
            }
            Token::Keyword(Keyword::Do) => {
                input = rest;
                break;
            }
            _ => {
                return Err(Error::Expected {
                    expected: Token::Keyword(Keyword::Do),
                    got: do_or_semicolon,
                })
            }
        }
    }
    let (input, body) = parse_until(input, parse_expression, |t| {
        matches!(t, Token::Keyword(Keyword::Done))
    })?;
    // Skip done
    let (input, _) = lex::lex_token(input)?;
    let (input, redirects) = parse_redirects(input)?;

    Ok((
        input,
        Expression::While(WhileExpression {
            condtion: Box::new(condition),
            body,
            redirects,
        }),
    ))
}

fn parse_for(input: &str) -> Result<(&str, Expression), Error> {
    // xxx in ...; do ... done
    let (input, variable) = lex::lex_token(input)?;
    let Token::Word(variable_word) = &variable else {
        todo!();
    };
    let [WordSegment::Literal(variable)] = variable_word.0[..] else {
        todo!()
    };
    let (input, kw_in) = lex::lex_token(input)?;
    if kw_in != Token::Keyword(Keyword::In) {
        todo!();
    }
    let (mut input, list) = parse_pipeline_element(input)?;
    loop {
        input = skip_newline(input)?;
        let (rest, do_or_semicolon) = lex::lex_token(input)?;
        match do_or_semicolon {
            Token::Punct(Punct::Semicolon) => {
                input = rest;
            }
            Token::Keyword(Keyword::Do) => {
                input = rest;
                break;
            }
            _ => {
                return Err(Error::Expected {
                    expected: Token::Keyword(Keyword::Do),
                    got: do_or_semicolon,
                })
            }
        }
    }
    let (input, body) = parse_until(input, parse_expression, |t| {
        matches!(t, Token::Keyword(Keyword::Done))
    })?;
    // Skip done
    let (input, _) = lex::lex_token(input)?;
    let (input, redirects) = parse_redirects(input)?;

    Ok((
        input,
        Expression::For(ForExpression {
            variable,
            list: Box::new(list),
            body,
            redirects,
        }),
    ))
}

fn parse_conditional(input: &str) -> Result<(&str, Expression), Error> {
    let (input, body) = parse_expression(input)?;
    let (input, rbracket) = lex::lex_token(input)?;
    if rbracket != Token::Punct(Punct::RBracket) {
        return Err(Error::Expected {
            expected: Token::Punct(Punct::RBracket),
            got: rbracket,
        });
    }

    Ok((
        input,
        Expression::Conditional(ConditionalExpression {
            body: Box::new(body),
        }),
    ))
}

fn parse_redirects(mut input: &str) -> Result<(&str, Vec<Redirect>), Error> {
    let mut redirects = vec![];
    loop {
        if input.is_empty() {
            break;
        }
        let (rest, token) = lex::lex_token(input)?;
        if let Token::Redirect(redirect) = token {
            redirects.push(redirect);
            input = rest;
        } else {
            break;
        }
    }
    Ok((input, redirects))
}

fn parse_group(input: &str) -> Result<(&str, Expression), Error> {
    let (input, body) = parse_until(input, parse_expression, |t| {
        *t == Token::Punct(Punct::RBrace)
    })?;
    let (input, rbrace) = lex::lex_token(input)?;
    if rbrace != Token::Punct(Punct::RBrace) {
        return Err(Error::Expected {
            expected: Token::Punct(Punct::RBrace),
            got: rbrace,
        });
    }
    let (input, redirects) = parse_redirects(input)?;

    Ok((
        input,
        Expression::Group(GroupExpression { body, redirects }),
    ))
}

fn skip_newline(mut input: &str) -> Result<&str, Error> {
    loop {
        let (rest, token) = lex::lex_token(input)?;
        if token == Token::Punct(Punct::Newline) {
            input = rest;
        } else {
            break;
        }
    }
    Ok(input)
}

fn parse_case_branch(input: &str) -> Result<(&str, CaseBranch), Error> {
    let (input, pattern) = lex::lex_token(input)?;
    let Token::Word(pattern) = pattern else {
        return Err(Error::Expected {
            expected: Token::Word(WordToken(vec![WordSegment::Literal("PATTERN)")])),
            got: pattern,
        });
    };
    if !pattern.ends_with(')') {
        return Err(Error::InvalidCasePattern(pattern));
    }
    let (input, body) = parse_until(input, parse_expression, |t| {
        *t == Token::Punct(Punct::DoubleSemicolon)
    })?;
    // Skip ;;
    let (input, _) = lex::lex_token(input)?;

    Ok((input, CaseBranch { pattern, body }))
}

fn parse_case(input: &str) -> Result<(&str, Expression), Error> {
    let (input, expression) = lex::lex_token(input)?;
    let Token::Word(expression) = expression else {
        return Err(Error::Expected {
            expected: Token::Word(WordToken(vec![WordSegment::Literal("expression")])),
            got: expression,
        });
    };
    let (input, kw_in) = lex::lex_token(input)?;
    if kw_in != Token::Keyword(Keyword::In) {
        return Err(Error::Expected {
            expected: Token::Keyword(Keyword::In),
            got: kw_in,
        });
    }
    let (input, branches) = parse_until(input, parse_case_branch, |t| {
        *t == Token::Keyword(Keyword::Esac)
    })?;
    // Skip esac
    let (input, _) = lex::lex_token(input)?;
    let (input, redirects) = parse_redirects(input)?;
    Ok((
        input,
        Expression::Case(CaseExpression {
            expression,
            branches,
            redirects,
        }),
    ))
}

fn parse_atom(mut input: &str) -> Result<(&str, Expression), Error> {
    loop {
        let (rest, token) = lex::lex_token(input)?;
        // TODO (...)
        return match token {
            Token::Word(_) | Token::Redirect(_) => {
                let (input, pipeline) = parse_pipeline(input)?;
                Ok((input, Expression::Pipeline(pipeline)))
            }
            Token::Keyword(Keyword::If) => parse_if(rest),
            Token::Keyword(Keyword::For) => parse_for(rest),
            Token::Keyword(Keyword::While) => parse_while(rest),
            Token::Keyword(Keyword::Case) => parse_case(rest),
            Token::Punct(Punct::Not) => {
                let (rest, inner) = parse_atom(rest)?;
                Ok((rest, Expression::Not(Box::new(inner))))
            }
            Token::Punct(Punct::LBrace) => parse_group(rest),
            Token::Punct(Punct::LBracket) => parse_conditional(rest),
            Token::Punct(Punct::Semicolon | Punct::Newline) => {
                input = rest;
                continue;
            }
            _ => Err(Error::Unexpected(token)),
        };
    }
}

fn maybe_binary<'i>(
    lhs: Expression<'i>,
    mut input: &'i str,
) -> Result<(&'i str, Expression<'i>), Error<'i>> {
    if input.is_empty() {
        return Ok((input, lhs));
    }
    let (rest, token) = lex::lex_token(input)?;
    let Some(this_op) = BinaryOperator::from_token(&token) else {
        return Ok((input, lhs));
    };
    input = rest;
    let (rest, rhs) = parse_expression(input)?;
    input = rest;
    // x && (y || z)
    let expr = if let Expression::Binary(that_op, that_lhs, that_rhs) = rhs {
        if this_op.precedence() > that_op.precedence() {
            Expression::Binary(
                that_op,
                Box::new(Expression::Binary(this_op, Box::new(lhs), that_lhs)),
                that_rhs,
            )
        } else {
            Expression::Binary(
                this_op,
                Box::new(lhs),
                Box::new(Expression::Binary(that_op, that_lhs, that_rhs)),
            )
        }
    } else {
        Expression::Binary(this_op, Box::new(lhs), Box::new(rhs))
    };

    Ok((input, expr))
}

pub fn parse_expression(input: &str) -> Result<(&str, Expression), Error> {
    let (input, atom) = parse_atom(input)?;
    maybe_binary(atom, input)
}

pub fn parse_toplevel(input: &str) -> Result<(&str, Expression), Error> {
    let (rest, atom) = parse_atom(input)?;
    // If atom is `xxx()`, try to parse a function declaration
    if let Some(identifier) = atom
        .as_pipeline()
        .and_then(PipelineExpression::as_function_decl)
    {
        let _ = identifier;
        let _ = rest;
        todo!()
    } else {
        parse_expression(input)
    }
}

impl PipelineElement<'_> {
    fn as_function_decl(&self) -> Option<&str> {
        if !self.redirects.is_empty() || self.words.len() != 1 {
            return None;
        }
        let word = &self.words[0];
        if word.0.len() != 1 {
            return None;
        }
        match &word.0[0] {
            WordSegment::Literal(x) if let Some(prefix) = x.strip_suffix("()") => {
                // TODO Check if prefix is a valid identifier
                Some(prefix)
            }
            _ => None,
        }
    }

    fn as_word(&self) -> Option<&WordToken> {
        if !self.redirects.is_empty() || self.words.len() != 1 {
            return None;
        }
        Some(&self.words[0])
    }
}

impl PipelineExpression<'_> {
    fn as_function_decl(&self) -> Option<&str> {
        if self.elements.len() != 1 {
            return None;
        }
        self.elements[0].as_function_decl()
    }

    pub fn as_word(&self) -> Option<&WordToken> {
        self.as_single_command()?.as_word()
    }

    pub fn as_single_command(&self) -> Option<&PipelineElement> {
        if self.elements.len() != 1 {
            return None;
        }
        Some(&self.elements[0])
    }
}

impl Expression<'_> {
    pub fn as_pipeline(&self) -> Option<&PipelineExpression> {
        match self {
            Self::Pipeline(pipeline) => Some(pipeline),
            _ => None,
        }
    }

    pub fn as_word(&self) -> Option<&WordToken> {
        self.as_pipeline()?.as_word()
    }
}

impl BinaryOperator {
    fn from_token(token: &Token) -> Option<Self> {
        match token {
            Token::Punct(Punct::Or) => Some(Self::Or),
            Token::Punct(Punct::And) => Some(Self::And),
            Token::Punct(Punct::Equal) => Some(Self::Equal),
            Token::Punct(Punct::NotEqual) => Some(Self::NotEqual),
            _ => None,
        }
    }

    fn precedence(&self) -> usize {
        match self {
            Self::Or => 1,
            Self::And => 2,
            Self::Equal | Self::NotEqual => 3,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::syntax::{
        lex::{
            InputRedirect, OutputRedirect, OutputRedirectTarget, Redirect, WordSegment, WordToken,
        },
        parse::{
            parse_expression, parse_pipeline, parse_pipeline_element, BinaryOperator, Expression,
            IfExpression, PipelineElement, PipelineExpression,
        },
    };

    #[test]
    fn test_parse_pipeline_element() {
        let cmd = "a b c | d e f";
        assert_eq!(
            parse_pipeline_element(cmd).unwrap().1,
            PipelineElement {
                words: vec![
                    WordToken(vec![WordSegment::Literal("a")]),
                    WordToken(vec![WordSegment::Literal("b")]),
                    WordToken(vec![WordSegment::Literal("c")]),
                ],
                redirects: vec![]
            }
        );
    }

    #[test]
    fn test_parse_pipeline() {
        let cmd = "a b c <aaa.txt | d e f >>bbb.txt 2>&1 && other command";
        assert_eq!(
            parse_pipeline(cmd).unwrap().1,
            PipelineExpression {
                elements: vec![
                    PipelineElement {
                        words: vec![
                            WordToken(vec![WordSegment::Literal("a")]),
                            WordToken(vec![WordSegment::Literal("b")]),
                            WordToken(vec![WordSegment::Literal("c")]),
                        ],
                        redirects: vec![Redirect::Input(InputRedirect::Filename(WordToken(vec![
                            WordSegment::Literal("aaa.txt")
                        ])))],
                    },
                    PipelineElement {
                        words: vec![
                            WordToken(vec![WordSegment::Literal("d")]),
                            WordToken(vec![WordSegment::Literal("e")]),
                            WordToken(vec![WordSegment::Literal("f")]),
                        ],
                        redirects: vec![
                            Redirect::Output(OutputRedirect {
                                source: None,
                                target: OutputRedirectTarget::Filename(WordToken(vec![
                                    WordSegment::Literal("bbb.txt")
                                ])),
                                append: true
                            }),
                            Redirect::Output(OutputRedirect {
                                source: Some("2"),
                                target: OutputRedirectTarget::Descriptor("1"),
                                append: false
                            }),
                        ],
                    },
                ],
            }
        );
    }

    #[test]
    fn test_parse_expression() {
        let cmd = "if a; then b; else c; fi\n";
        assert_eq!(
            parse_expression(cmd).unwrap().1,
            Expression::If(IfExpression {
                condition: Box::new(Expression::Pipeline(PipelineExpression {
                    elements: vec![PipelineElement {
                        words: vec![WordToken(vec![WordSegment::Literal("a")])],
                        redirects: vec![],
                    }],
                })),
                if_true: vec![Expression::Pipeline(PipelineExpression {
                    elements: vec![PipelineElement {
                        words: vec![WordToken(vec![WordSegment::Literal("b")])],
                        redirects: vec![],
                    }],
                })],
                if_false: Some(vec![Expression::Pipeline(PipelineExpression {
                    elements: vec![PipelineElement {
                        words: vec![WordToken(vec![WordSegment::Literal("c")])],
                        redirects: vec![],
                    }],
                })]),
                redirects: vec![]
            })
        );

        let cmd = "a | b && c || d && e\n";
        assert_eq!(
            parse_expression(cmd).unwrap().1,
            Expression::Binary(
                BinaryOperator::Or,
                Box::new(Expression::Binary(
                    BinaryOperator::And,
                    Box::new(Expression::Pipeline(PipelineExpression {
                        elements: vec![
                            PipelineElement {
                                words: vec![WordToken(vec![WordSegment::Literal("a")])],
                                redirects: vec![]
                            },
                            PipelineElement {
                                words: vec![WordToken(vec![WordSegment::Literal("b")])],
                                redirects: vec![]
                            },
                        ],
                    })),
                    Box::new(Expression::Pipeline(PipelineExpression {
                        elements: vec![PipelineElement {
                            words: vec![WordToken(vec![WordSegment::Literal("c")])],
                            redirects: vec![]
                        },]
                    }))
                )),
                Box::new(Expression::Binary(
                    BinaryOperator::And,
                    Box::new(Expression::Pipeline(PipelineExpression {
                        elements: vec![PipelineElement {
                            words: vec![WordToken(vec![WordSegment::Literal("d")])],
                            redirects: vec![]
                        },]
                    })),
                    Box::new(Expression::Pipeline(PipelineExpression {
                        elements: vec![PipelineElement {
                            words: vec![WordToken(vec![WordSegment::Literal("e")])],
                            redirects: vec![]
                        },]
                    })),
                ))
            )
        );
    }
}
