use anyhow::{bail, Result};
use parse_int;
use rand;
use std::cmp::Ordering;
use unescape::unescape;

use crate::arg;
use crate::assembler;
use crate::lexer;

pub fn unescape_string(string: &str) -> String {
    match unescape(string) {
        Some(x) => x,
        None => "".to_string(),
    }
}

#[derive(Debug, Clone)]
pub enum Arg {
    Char(char),
    String(String),
    Number(u16),
    Variable(String),
    BinaryExpression {
        left: Box<Self>,
        right: Box<Self>,
        op: BinaryExpressionOperator,
    },
}

impl assembler::ToCode for Arg {
    fn to_code(&self) -> String {
        match self {
            Arg::Char(x) => format!("'{}'", x.escape_default()),
            Arg::String(x) => format!("\"{}\"", x.escape_default()),
            Arg::Number(x) => x.to_string(),
            Arg::Variable(x) => x.clone(),
            Arg::BinaryExpression { left, right, op } => {
                format!("({} {} {})", left.to_code(), op.to_code(), right.to_code())
            }
        }
    }
}

impl assembler::ByteResolvable<assembler::Data> for Arg {
    fn resolve_number(&self, data: &mut assembler::Data) -> Result<u16> {
        match self {
            Arg::Char(x) => Ok(*x as u16),
            Arg::String(x) => {
                let y = unescape_string(x);
                if y.len() == 1 {
                    Ok(y.as_bytes()[0] as u16)
                } else {
                    let bytes = y.as_bytes();

                    Ok(((bytes[0] as u16) << 8) | bytes[1] as u16)
                }
            }
            Arg::Number(x) => Ok(*x),
            Arg::Variable(x) => {
                let mut name = x.clone();
                let mut arg: Option<arg::Arg>;

                loop {
                    arg = data.constants.get(&name).cloned();

                    match arg {
                        Some(a) => {
                            match a {
                                arg::Arg::Variable(n) => {
                                    name = n;
                                }
                                _ => break Ok(a.resolve_number(data)?),
                            };
                        }
                        _ => bail!("Variable does not exist: {}", name),
                    }
                }
            }
            Arg::BinaryExpression { left, right, op } => {
                let left_val = left.resolve_number(data)?;
                let right_val = right.resolve_number(data)?;

                Ok(match op {
                    BinaryExpressionOperator::Not => !left_val,
                    BinaryExpressionOperator::And => left_val & right_val,
                    BinaryExpressionOperator::Nand => !(left_val & right_val),
                    BinaryExpressionOperator::Or => left_val | right_val,
                    BinaryExpressionOperator::Nor => !(left_val | right_val),
                    BinaryExpressionOperator::Xor => left_val ^ right_val,
                    BinaryExpressionOperator::Xnor => !(left_val ^ right_val),
                    BinaryExpressionOperator::Lsh => left_val << right_val,
                    BinaryExpressionOperator::Rsh => left_val >> right_val,
                    BinaryExpressionOperator::Add => left_val.wrapping_add(right_val),
                    BinaryExpressionOperator::Sub => left_val.wrapping_sub(right_val),
                    BinaryExpressionOperator::Mul => left_val.wrapping_mul(right_val),
                    BinaryExpressionOperator::Div => left_val.wrapping_div(right_val),
                    BinaryExpressionOperator::Pow => left_val.wrapping_pow(right_val as u32),
                    BinaryExpressionOperator::Cmp => match left_val.cmp(&right_val) {
                        Ordering::Equal => 0,
                        Ordering::Less => 1,
                        Ordering::Greater => 2,
                    },
                    BinaryExpressionOperator::Eq => (left_val == right_val) as u16,
                    BinaryExpressionOperator::Neq => (left_val != right_val) as u16,
                    BinaryExpressionOperator::Lt => (left_val < right_val) as u16,
                    BinaryExpressionOperator::Gt => (left_val > right_val) as u16,
                    BinaryExpressionOperator::Leq => (left_val <= right_val) as u16,
                    BinaryExpressionOperator::Geq => (left_val >= right_val) as u16,
                    BinaryExpressionOperator::Bol => (left_val & 1 == 1) as u16,
                    BinaryExpressionOperator::Inv => (left_val & 1 != 1) as u16,
                    BinaryExpressionOperator::Rnd => rand::random(),
                })
            }
        }
    }

    fn resolve_bytes(&self, data: &mut assembler::Data) -> Result<Vec<u8>> {
        match self {
            Arg::Char(x) => Ok(vec![*x as u8]),
            Arg::String(x) => Ok(unescape_string(x).bytes().collect()),
            Arg::Number(x) => Ok(vec![(*x >> 8) as u8, *x as u8]),
            Arg::Variable(x) => {
                let mut name = x.clone();
                let mut arg: Option<arg::Arg>;

                loop {
                    arg = data.constants.get(&name).cloned();

                    match arg {
                        Some(a) => {
                            match a {
                                arg::Arg::Variable(n) => {
                                    name = n;
                                }
                                _ => break Ok(a.resolve_bytes(data)?),
                            };
                        }
                        _ => bail!("Variable could not be resolved: {}", name),
                    }
                }
            }
            Arg::BinaryExpression {
                left: _,
                right: _,
                op: _,
            } => {
                let x = self.resolve_number(data)?;

                Ok(vec![(x >> 8) as u8, x as u8])
            }
        }
    }

    fn resolve_string(&self, data: &mut assembler::Data) -> Result<String> {
        match self {
            Arg::Char(x) => Ok(x.to_string()),
            Arg::String(x) => Ok(x.clone()),
            Arg::Number(_) => bail!("Number cannot be resolved as string"),
            Arg::Variable(_) => self.resolve_string(data),
            Arg::BinaryExpression {
                left: _,
                right: _,
                op: _,
            } => bail!("Binary expression cannot be resolved as string"),
        }
    }
}

#[derive(Debug, Clone)]
pub enum BinaryExpressionOperator {
    Not,
    And,
    Nand,
    Or,
    Nor,
    Xor,
    Xnor,
    Lsh,
    Rsh,
    Add,
    Sub,
    Mul,
    Div,
    Pow,
    Cmp,
    Eq,
    Neq,
    Lt,
    Gt,
    Leq,
    Geq,
    Bol,
    Inv,
    Rnd,
}

impl assembler::ToCode for BinaryExpressionOperator {
    fn to_code(&self) -> String {
        match self {
            BinaryExpressionOperator::Not => "~".to_string(),
            BinaryExpressionOperator::And => "&".to_string(),
            BinaryExpressionOperator::Nand => "~&".to_string(),
            BinaryExpressionOperator::Or => "|".to_string(),
            BinaryExpressionOperator::Nor => "~|".to_string(),
            BinaryExpressionOperator::Xor => "^".to_string(),
            BinaryExpressionOperator::Xnor => "~^".to_string(),
            BinaryExpressionOperator::Lsh => "<<".to_string(),
            BinaryExpressionOperator::Rsh => ">>".to_string(),
            BinaryExpressionOperator::Add => "+".to_string(),
            BinaryExpressionOperator::Sub => "-".to_string(),
            BinaryExpressionOperator::Mul => "*".to_string(),
            BinaryExpressionOperator::Div => "/".to_string(),
            BinaryExpressionOperator::Pow => "**".to_string(),
            BinaryExpressionOperator::Cmp => "<=>".to_string(),
            BinaryExpressionOperator::Eq => "==".to_string(),
            BinaryExpressionOperator::Neq => "!=".to_string(),
            BinaryExpressionOperator::Lt => "<".to_string(),
            BinaryExpressionOperator::Gt => ">".to_string(),
            BinaryExpressionOperator::Leq => "<=".to_string(),
            BinaryExpressionOperator::Geq => ">=".to_string(),
            BinaryExpressionOperator::Bol => "!!".to_string(),
            BinaryExpressionOperator::Inv => "!".to_string(),
            BinaryExpressionOperator::Rnd => "?".to_string(),
        }
    }
}

pub fn parse_binary_operation(token: &lexer::Token) -> Result<BinaryExpressionOperator> {
    match token {
        lexer::Token::Not => Ok(BinaryExpressionOperator::Not),
        lexer::Token::And => Ok(BinaryExpressionOperator::And),
        lexer::Token::Nand => Ok(BinaryExpressionOperator::Nand),
        lexer::Token::Or => Ok(BinaryExpressionOperator::Or),
        lexer::Token::Nor => Ok(BinaryExpressionOperator::Nor),
        lexer::Token::Xor => Ok(BinaryExpressionOperator::Xor),
        lexer::Token::Xnor => Ok(BinaryExpressionOperator::Xnor),
        lexer::Token::Lsh => Ok(BinaryExpressionOperator::Lsh),
        lexer::Token::Rsh => Ok(BinaryExpressionOperator::Rsh),
        lexer::Token::Add => Ok(BinaryExpressionOperator::Add),
        lexer::Token::Sub => Ok(BinaryExpressionOperator::Sub),
        lexer::Token::Mul => Ok(BinaryExpressionOperator::Mul),
        lexer::Token::Div => Ok(BinaryExpressionOperator::Div),
        lexer::Token::Pow => Ok(BinaryExpressionOperator::Pow),
        lexer::Token::Cmp => Ok(BinaryExpressionOperator::Cmp),
        lexer::Token::Eq => Ok(BinaryExpressionOperator::Eq),
        lexer::Token::Neq => Ok(BinaryExpressionOperator::Neq),
        lexer::Token::Lt => Ok(BinaryExpressionOperator::Lt),
        lexer::Token::Gt => Ok(BinaryExpressionOperator::Gt),
        lexer::Token::Leq => Ok(BinaryExpressionOperator::Leq),
        lexer::Token::Geq => Ok(BinaryExpressionOperator::Geq),
        lexer::Token::Bol => Ok(BinaryExpressionOperator::Bol),
        lexer::Token::Inv => Ok(BinaryExpressionOperator::Inv),
        lexer::Token::Rnd => Ok(BinaryExpressionOperator::Rnd),
        _ => bail!("Invalid binary expression operator"),
    }
}

pub fn parse_binary_expression_arg(tokens: &mut Vec<&lexer::Token>) -> Result<Arg> {
    if tokens.is_empty() {
        bail!("Malformed binary expression");
    }

    let mut args: Vec<_> = tokens.drain(..3).collect();

    let mut bin_expr = Arg::BinaryExpression {
        left: Box::new(parse_args(vec![args[0]])?[0].clone()),
        right: Box::new(parse_args(vec![args[2]])?[0].clone()),
        op: parse_binary_operation(args[1])?,
    };

    while !tokens.is_empty() {
        args = tokens.drain(..2).collect();
        bin_expr = Arg::BinaryExpression {
            left: Box::new(bin_expr),
            right: Box::new(parse_args(vec![args[1]])?[0].clone()),
            op: parse_binary_operation(args[0])?,
        }
    }

    Ok(bin_expr)
}

pub fn parse_args(tokens: Vec<&lexer::Token>) -> Result<Vec<Arg>> {
    let mut iter = tokens.into_iter();
    let mut args: Vec<Arg> = vec![];
    let mut last_was_comma = true;

    while let Some(token) = iter.next() {
        match token {
            lexer::Token::Comma => {
                last_was_comma = true;
            }
            _ => {
                if !last_was_comma {
                    bail!("Invalid argument separation");
                }

                match token {
                    lexer::Token::Comment(_) => {}
                    lexer::Token::CharLiteral(x) => {
                        let y = unescape_string(x);
                        if y.len() > 1 {
                            bail!("Char cannot hold string")
                        } else if let Some(c) = y.chars().next() {
                            args.push(Arg::Char(c));
                        } else {
                            bail!("Char cannot be empty");
                        }
                    }
                    lexer::Token::StringLiteral(x) => {
                        args.push(Arg::String(x.clone()));
                    }
                    lexer::Token::Literal(x) => {
                        args.push(Arg::Variable(x.clone()));
                    }
                    lexer::Token::Number(x) => {
                        args.push(Arg::Number(parse_int::parse(x)?));
                    }
                    lexer::Token::LParen => {
                        let mut depth: usize = 1;

                        args.push(parse_binary_expression_arg(
                            &mut iter
                                .by_ref()
                                .take_while(|t| match t {
                                    lexer::Token::LParen => {
                                        depth += 1;
                                        true
                                    }
                                    lexer::Token::RParen => {
                                        depth -= 1;
                                        depth != 0
                                    }
                                    _ => true,
                                })
                                .collect(),
                        )?);
                    }
                    _ => bail!("Unexpected token for argument"),
                }

                last_was_comma = false;
            }
        }
    }

    Ok(args)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_unescape_string() {
        assert_eq!(unescape_string("\\n test"), "\n test".to_string());
        assert_eq!(unescape_string(""), "".to_string());
    }
}
