use crate::*;


pub struct ConstantExpression {
    pub tokens: Vec<ConstantExpressionToken>,
}

impl ConstantExpression {
    pub fn from_str(string: &str, tokeniser: &Tokeniser) -> Self {
        parse_constant_expression(string, tokeniser)
    }
}

pub struct ConstantExpressionToken {
    pub source: SourceSpan,
    pub variant: ConstantExpressionTokenVariant,
}

pub enum ConstantExpressionTokenVariant {
    SymbolReference(String),
    IntegerLiteral(usize),
    Operator(Operator),
    Error(ConstantExpressionParseError),
}

pub enum Operator {
    Equal,
    NotEqual,
    LessThan,
    GreaterThan,
    Add,
    Subtract,
    LeftShift,
    RightShift,
    And,
    Or,
    Xor,
    Not,
}

pub enum ConstantExpressionParseError {
    InvalidHexadecimalLiteral(String),
}


impl ConstantExpression {
    pub fn evaluate(&self, environment: &Environment) -> Result<usize, ConstantExpressionEvaluationError> {
        use ConstantExpressionTokenVariant as Token;
        use ConstantExpressionEvaluationError as EvalErr;

        let mut stack = Vec::new();
        macro_rules! push {
            ($value:expr) => { stack.push($value) };
        }
        macro_rules! pop {
            ($name:ident) => { let $name = match stack.pop() {
                Some(value) => value,
                None => return Err(EvalErr::StackUnderflow),
            }; };
        }
        macro_rules! truth {
            ($bool:expr) => { match $bool { true => 1, false => 0 } };
        }

        for token in &self.tokens {
            match &token.variant {
                Token::IntegerLiteral(value) => push!(*value),
                Token::SymbolReference(name) => match environment.get_integer(name) {
                    Ok(value) => push!(value),
                    Err(_) => todo!(),
                }
                Token::Operator(operator) => match operator {
                    Operator::Equal       => { pop!(b); pop!(a); push!(truth!(a==b)) },
                    Operator::NotEqual    => { pop!(b); pop!(a); push!(truth!(a!=b)) },
                    Operator::LessThan    => { pop!(b); pop!(a); push!(truth!(a < b)) },
                    Operator::GreaterThan => { pop!(b); pop!(a); push!(truth!(a > b)) },
                    Operator::Add         => { pop!(b); pop!(a); push!(a + b) },
                    Operator::Subtract    => { pop!(b); pop!(a); push!(a - b) },
                    Operator::LeftShift   => { pop!(b); pop!(a); push!(a << b) },
                    Operator::RightShift  => { pop!(b); pop!(a); push!(a >> b) },
                    Operator::And         => { pop!(b); pop!(a); push!(a & b) },
                    Operator::Or          => { pop!(b); pop!(a); push!(a | b) },
                    Operator::Xor         => { pop!(b); pop!(a); push!(a ^ b) },
                    Operator::Not         => {          pop!(a); push!(!a) },
                }
                Token::Error(_) => (),
            }
        }
        match stack.len() {
            0 => Err(EvalErr::NoReturnValue),
            1 => Ok(stack[0]),
            _ => Err(EvalErr::MultipleReturnValues),
        }
    }
}

pub enum ConstantExpressionEvaluationError {
    StackUnderflow,
    MultipleReturnValues,
    NoReturnValue,
}


impl std::fmt::Debug for ConstantExpression {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        use ConstantExpressionTokenVariant as TokenVar;
        for (i, token) in self.tokens.iter().enumerate() {
            let string = match &token.variant {
                TokenVar::SymbolReference(name) => name,
                TokenVar::IntegerLiteral(value) => &value.to_string(),
                TokenVar::Operator(operator) => match operator {
                    Operator::Equal       => "=",
                    Operator::NotEqual    => "!",
                    Operator::LessThan    => "<",
                    Operator::GreaterThan => ">",
                    Operator::Add         => "+",
                    Operator::Subtract    => "-",
                    Operator::LeftShift   => "<<",
                    Operator::RightShift  => ">>",
                    Operator::And         => "&",
                    Operator::Or          => "|",
                    Operator::Xor         => "^",
                    Operator::Not         => "~",
                }
                TokenVar::Error(_) => "<error>",
            };
            match i {
                0 => write!(f, "{string}")?,
                _ => write!(f, " {string}")?,
            }
        }
        return Ok(());
    }
}