use crate::filtermodifier::FilterModifier;
use crate::interpreter::Ast;
use crate::options::Options;
use std::iter::Peekable;
use std::str::Chars;

#[derive(Debug)]
pub struct Parser<'a> {
    expr: Peekable<Chars<'a>>,
    pos: u64,
    source: String,

    pub advanced: bool,
}

impl<'a> Parser<'a> {
    pub fn new(expr: &'a str) -> Self {
        Self {
            source: expr.to_string(),
            expr: expr.chars().peekable(),
            pos: 0,
            advanced: false,
        }
    }

    pub fn advanced(mut self) -> Self {
        self.advanced = true;
        self
    }

    pub fn backup(&self) -> Self {
        Self {
            expr: self.expr.clone(),
            source: self.source.clone(),
            pos: self.pos,
            advanced: self.advanced,
        }
    }

    pub fn restore(&mut self, other: Self) {
        self.expr = other.expr;
        self.pos = other.pos;
        self.source = other.source;
        self.advanced = other.advanced;
    }

    pub fn accept(&mut self, c: char, options: Options) -> Result<(), Options> {
        self.expect(c, options)?;

        self.pos += 1;
        self.expr.next();
        Ok(())
    }

    pub fn accept_string(&mut self, text: &str, options: Options) -> Result<(), Options> {
        let backup = self.backup();
        for c in text.chars() {
            if let Err(e) = self.accept(c, options.clone()) {
                self.restore(backup);
                return Err(e);
            }
        }

        Ok(())
    }

    pub fn expect(&mut self, c: char, options: Options) -> Result<(), Options> {
        while let Some(i) = self.expr.peek() {
            if !i.is_whitespace() {
                break;
            }
            self.pos += 1;
            self.expr.next();
        }

        let pk = self.expr.peek();
        if pk == Some(&c) {
            Ok(())
        } else {
            Err(options.add(c).pos(self.pos))
        }
    }

    pub fn accept_any(
        &mut self,
        c: &[char],
        mut options: Options,
        name: Option<Options>,
    ) -> Result<char, Options> {
        for i in c {
            match self.accept(*i, options.clone()) {
                Ok(_) => return Ok(*i),
                Err(o) => {
                    if name.is_none() {
                        options = options.merge(o)
                    }
                }
            }
        }

        if let Some(n) = name {
            options = options.merge(n)
        }

        Err(options)
    }

    pub fn parse(&mut self) -> Result<Ast, Options> {
        let result = self.parse_expr(Options::new(self.source.clone()))?;

        if self.expr.next().is_some() {
            return Err(Options::new(self.source.clone())
                .pos(self.pos)
                .message("unexpected trailing character(s)"));
        }

        Ok(result)
    }

    pub fn parse_expr(&mut self, options: Options) -> Result<Ast, Options> {
        self.parse_sum(options)
    }

    pub fn parse_sum(&mut self, options: Options) -> Result<Ast, Options> {
        let mut res = self.parse_term(options.clone())?;

        while let Ok(op) = self.accept_any(&['+', '-'], options.clone(), None) {
            let right = self.parse_term(options.clone())?;

            res = match op {
                '+' => Ast::Add(Box::new(res), Box::new(right)),
                '-' => Ast::Sub(Box::new(res), Box::new(right)),
                _ => unreachable!(),
            }
        }

        Ok(res)
    }

    pub fn parse_term(&mut self, mut options: Options) -> Result<Ast, Options> {
        let mut res = self.parse_factor(options.clone())?;

        loop {
            let opres = self.accept_any(&['*', '/'], options.clone(), None);
            let mut op = if let Ok(i) = opres {
                i
            } else if self.accept_string("mod", options.clone()).is_ok() {
                '%'
            } else {
                options.add_str("mod").add_str("//");
                break;
            };

            if op == '/' && self.accept('/', options.clone()).is_ok() {
                op = 'i'
            } else {
                options = options.add('/');
            }

            let right = self.parse_factor(options.clone())?;

            res = match op {
                '*' => Ast::Mul(Box::new(res), Box::new(right)),
                '/' => Ast::Div(Box::new(res), Box::new(right)),
                'i' => Ast::IDiv(Box::new(res), Box::new(right)),
                '%' => Ast::Mod(Box::new(res), Box::new(right)),
                _ => unreachable!(),
            }
        }

        Ok(res)
    }

    pub fn parse_factor(&mut self, options: Options) -> Result<Ast, Options> {
        let backup = self.backup();

        Ok(match self.accept('-', options.clone()) {
            Ok(_) => Ast::Minus(Box::new(self.parse_power(options)?)),
            Err(o) => {
                self.restore(backup);

                return self.parse_power(o);
            }
        })
    }

    pub fn parse_power(&mut self, options: Options) -> Result<Ast, Options> {
        let mut res = self.parse_atom(options.clone())?;
        if self.accept_string("**", options.clone()).is_ok() {
            let right = self.parse_factor(options)?;
            res = Ast::Power(Box::new(res), Box::new(right));
        }

        Ok(res)
    }

    pub fn parse_atom(&mut self, options: Options) -> Result<Ast, Options> {
        let backup = self.backup();
        Ok(match self.parse_dice(options) {
            Err(mut o) => {
                self.restore(backup);

                let backup = self.backup();
                if self.accept('(', o.clone()).is_ok() {
                    let sm = self.parse_sum(o.clone())?;
                    self.accept(')', o)
                        .map_err(|e| e.message("missing closing parenthesis"))?;

                    return Ok(sm);
                } else {
                    o = o
                        .add('(')
                        .message("tried to parse expression between parenthesis");
                    self.restore(backup);
                }

                self.parse_number(o.message("tried to parse dice roll"))?
            }
            Ok(i) => i,
        })
    }

    pub fn parse_dice(&mut self, mut options: Options) -> Result<Ast, Options> {
        let backup = self.backup();

        let rolls = if self.advanced && self.accept('(', options.clone()).is_ok() {
            let sm = self.parse_sum(options.clone())?;
            self.accept(')', options.clone())
                .map_err(|e| e.message("missing closing parenthesis"))?;

            Some(Box::new(sm))
        } else {
            if self.advanced {
                options = options
                    .add('(')
                    .message("tried to parse expression between parenthesis");
            }
            self.restore(backup);
            self.parse_number(options.clone()).map(Box::new).ok()
        };

        self.accept('d', options.clone())?;
        let dpos = self.pos - 1;

        let backup = self.backup();
        let sides = if self.advanced && self.accept('(', options.clone()).is_ok() {
            let sm = self.parse_sum(options.clone())?;
            self.accept(')', options.clone())
                .map_err(|e| e.message("missing closing parenthesis"))?;

            Some(Box::new(sm))
        } else {
            if self.advanced {
                options = options
                    .add('(')
                    .message("tried to parse expression between parenthesis");
            }
            self.restore(backup);

            self.parse_number_or_percent(options.clone())
                .map(Box::new)
                .ok()
        };

        let fm = if self.accept_string("kh", options.clone()).is_ok()
            || self.accept('h', options.clone()).is_ok()
        {
            FilterModifier::KeepHighest(Box::new(
                self.parse_number(options)
                    .unwrap_or_else(|_| Ast::Const("1".to_string())),
            ))
        } else if self.accept_string("dl", options.clone()).is_ok()
            || self.accept('l', options.clone()).is_ok()
        {
            FilterModifier::DropLowest(Box::new(
                self.parse_number(options)
                    .unwrap_or_else(|_| Ast::Const("1".to_string())),
            ))
        } else if self.accept_string("dh", options.clone()).is_ok() {
            FilterModifier::DropHighest(Box::new(
                self.parse_number(options)
                    .unwrap_or_else(|_| Ast::Const("1".to_string())),
            ))
        } else if self.accept_string("kl", options.clone()).is_ok() {
            FilterModifier::KeepLowest(Box::new(
                self.parse_number(options)
                    .unwrap_or_else(|_| Ast::Const("1".to_string())),
            ))
        } else {
            FilterModifier::None
        };

        Ok(Ast::Dice(rolls, sides, fm, dpos))
    }

    pub fn parse_number_or_percent(&mut self, options: Options) -> Result<Ast, Options> {
        if self.accept('%', options.clone()).is_ok() {
            Ok(Ast::Const("100".to_ascii_lowercase()))
        } else {
            self.parse_number(options.add('%'))
        }
    }

    pub fn parse_number(&mut self, options: Options) -> Result<Ast, Options> {
        const DIGITS: &[char] = &['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '.'];
        let digits_name = Options::new("".to_string()).add_str("0-9");

        let mut number = vec![self
            .accept_any(&DIGITS, options.clone(), Some(digits_name.clone()))
            .map_err(|e| {
                options
                    .clone()
                    .merge(e)
                    .add('(')
                    .message("tried to parse a number")
            })?];

        loop {
            let backup = self.backup();

            let digit = match self.accept_any(&DIGITS, options.clone(), Some(digits_name.clone())) {
                Ok(i) => i,
                Err(_) => {
                    self.restore(backup);
                    break;
                }
            };

            number.push(digit)
        }

        let string: String = number.iter().collect();

        Ok(Ast::Const(string))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::filtermodifier::FilterModifier;
    use crate::interpreter::{Ast, Value, DEFAULT_SIDES};

    #[test]
    pub fn add() {
        let mut p = Parser::new("3 + 5");
        assert_eq!(
            p.parse().unwrap().interp(&mut Vec::new()).unwrap(),
            Value::Int(8)
        );
    }

    #[test]
    pub fn sub() {
        let mut p = Parser::new("3 - 5");
        assert_eq!(
            p.parse().unwrap().interp(&mut Vec::new()).unwrap(),
            Value::Int(-2)
        );
    }

    #[test]
    pub fn mul() {
        let mut p = Parser::new("3 * 5");
        assert_eq!(
            p.parse().unwrap().interp(&mut Vec::new()).unwrap(),
            Value::Int(15)
        );
    }

    #[test]
    pub fn div() {
        let mut p = Parser::new("15/3");
        let ast = p.parse().unwrap();
        assert_eq!(ast.interp(&mut Vec::new()).unwrap(), Value::Float(5.0));
    }

    #[test]
    pub fn pemdas() {
        let mut p = Parser::new("3 * 5 + -5");
        let ast = p.parse().unwrap();
        assert_eq!(ast.interp(&mut Vec::new()).unwrap(), Value::Int(10));
    }

    #[test]
    pub fn parens() {
        let mut p = Parser::new("3 * (5 + -5)");
        let ast = p.parse().unwrap();
        assert_eq!(ast.interp(&mut Vec::new()).unwrap(), Value::Int(0))
    }

    #[test]
    pub fn meme() {
        let mut p = Parser::new("6 / 2 * (1 + 2)");
        let ast = p.parse().unwrap();
        assert_eq!(ast.interp(&mut Vec::new()).unwrap(), Value::Float(9.0))
    }

    #[test]
    pub fn long_sum() {
        let mut p = Parser::new("3 + 3 + 3");
        let ast = p.parse().unwrap();
        assert_eq!(ast.interp(&mut Vec::new()).unwrap(), Value::Int(9));
    }

    #[test]
    pub fn long_mul() {
        let mut p = Parser::new("3 * 3 * 3");
        let ast = p.parse().unwrap();
        assert_eq!(ast.interp(&mut Vec::new()).unwrap(), Value::Int(27));
    }

    #[test]
    pub fn idiv() {
        let mut p = Parser::new("3 // 5");
        let ast = p.parse().unwrap();
        assert_eq!(ast.interp(&mut Vec::new()).unwrap(), Value::Int(0));
    }

    #[test]
    pub fn dice_none() {
        let mut p = Parser::new("d");
        let ast = p.parse().unwrap();
        assert_eq!(ast, Ast::Dice(None, None, FilterModifier::None, 0));

        let mut rolls = Vec::new();
        let res = ast.interp(&mut rolls).unwrap();

        assert_eq!(rolls.len(), 1);

        let roll = &rolls[0].1;
        assert_eq!(DEFAULT_SIDES, roll.sides.to_string());

        assert_eq!(res, Value::Int(roll.total));
    }

    #[test]
    pub fn dice_6() {
        let mut p = Parser::new("d6");
        let ast = p.parse().unwrap();
        assert_eq!(
            ast,
            Ast::Dice(
                None,
                Some(Box::new(Ast::Const("6".to_string()))),
                FilterModifier::None,
                0
            )
        );

        let mut rolls = Vec::new();
        let res = ast.interp(&mut rolls).unwrap();

        assert_eq!(rolls.len(), 1);

        let roll = &rolls[0].1;

        assert_eq!(res, Value::Int(roll.total as i64));
    }

    #[test]
    pub fn dice_3_6() {
        let mut p = Parser::new("3d6");
        let ast = p.parse().unwrap();
        assert_eq!(
            ast,
            Ast::Dice(
                Some(Box::new(Ast::Const("3".to_string()))),
                Some(Box::new(Ast::Const("6".to_string()))),
                FilterModifier::None,
                1
            )
        );

        let mut rolls = Vec::new();
        let res = ast.interp(&mut rolls).unwrap();

        assert_eq!(rolls.len(), 1);

        let roll0 = &rolls[0].1.vals[0];
        let roll1 = &rolls[0].1.vals[1];
        let roll2 = &rolls[0].1.vals[2];

        assert_eq!(res, Value::Int((roll0 + roll1 + roll2) as i64));
    }

    #[test]
    pub fn dice_0() {
        let mut p = Parser::new("0d6");
        let ast = p.parse().unwrap();
        assert_eq!(
            ast,
            Ast::Dice(
                Some(Box::new(Ast::Const("0".to_string()))),
                Some(Box::new(Ast::Const("6".to_string()))),
                FilterModifier::None,
                1
            )
        );

        let mut rolls = Vec::new();
        let res = ast.interp(&mut rolls).unwrap();

        assert_eq!(rolls.len(), 1);

        assert_eq!(res, Value::Int(0));
    }

    #[test]
    pub fn dice_float() {
        let mut p = Parser::new("3.5d6");
        let ast = p.parse().unwrap();
        assert_eq!(
            ast,
            Ast::Dice(
                Some(Box::new(Ast::Const("3.5".to_string()))),
                Some(Box::new(Ast::Const("6".to_string()))),
                FilterModifier::None,
                3
            )
        );

        ast.interp(&mut Vec::new()).expect_err("result was okay");
    }

    #[test]
    pub fn dice_dfloat() {
        let mut p = Parser::new("3d3.5");
        let ast = p.parse().unwrap();
        assert_eq!(
            ast,
            Ast::Dice(
                Some(Box::new(Ast::Const("3".to_string()))),
                Some(Box::new(Ast::Const("3.5".to_string()))),
                FilterModifier::None,
                1
            )
        );

        ast.interp(&mut Vec::new()).expect_err("result was okay");
    }

    #[test]
    pub fn dice_d0() {
        let mut p = Parser::new("3d0");
        let ast = p.parse().unwrap();
        assert_eq!(
            ast,
            Ast::Dice(
                Some(Box::new(Ast::Const("3".to_string()))),
                Some(Box::new(Ast::Const("0".to_string()))),
                FilterModifier::None,
                1
            )
        );

        ast.interp(&mut Vec::new()).expect_err("result was okay");
    }

    #[test]
    pub fn dice_dpercent() {
        let mut p = Parser::new("d%");
        let ast = p.parse().unwrap();
        assert_eq!(
            ast,
            Ast::Dice(
                None,
                Some(Box::new(Ast::Const("100".to_string()))),
                FilterModifier::None,
                0
            )
        );

        let mut rolls = Vec::new();
        let res = ast.interp(&mut rolls).unwrap();

        assert_eq!(rolls.len(), 1);

        let roll = &rolls[0].1;

        assert_eq!(roll.sides.get(), 100);
        assert_eq!(res, Value::Int(roll.total));
    }

    #[test]
    pub fn dice_kl() {
        let mut p = Parser::new("5d%kl");
        let ast = p.parse().unwrap();

        let mut rolls = Vec::new();
        let res = ast.interp(&mut rolls).unwrap();

        assert_eq!(rolls.len(), 1);

        let roll = &rolls[0].1;

        assert_eq!(roll.vals.len(), 1);
        assert_eq!(res, Value::Int(roll.total));
    }

    #[test]
    pub fn pow() {
        let mut p = Parser::new("5 ** 2");
        let ast = p.parse().unwrap();
        assert_eq!(ast.interp(&mut Vec::new()).unwrap(), Value::Int(25));
    }

    #[test]
    pub fn compound() {
        let mut p = Parser::new("(3d5)d(5d3)");
        p.advanced = true;
        let _ = p.parse().unwrap();
    }
}
