//! Macros used throughout the assembler

use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting
///
/// # Examples
/// ```rs
/// use assembler::macros::dsa;
/// // With formatting:
/// let nodes = dsa!(hash, "mov r1, {}", 42)?;
///
/// // Without formatting:
/// let nodes = dsa!(hash, "mov r1, 42")?;
/// ```
#[macro_export]
macro_rules! dsa {
    // Version with formatting arguments
    ($hash:expr, $input:expr, $($args:expr),+) => {{
        let input = format!($input, $($args),+);
        let tokens = $crate::lexer::lexer(input, $hash)?;
        let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
        parsed
    }};
    // Version without formatting
    ($hash:expr, $input:expr) => {{
        let input = String::from($input);
        let tokens = $crate::lexer::lexer(input, $hash)?;
        let parsed = $crate::parser::Parser::parse_nodes(tokens)?;
        parsed
    }};
}

/// Creates a new Node with the given symbol, opcode, and tokens
#[macro_export]
macro_rules! node {
    ($symbol: expr, $opcode: expr, args: $tokens: expr) => {
        $crate::assembler::model::Node::new($symbol.clone(), $opcode.clone(), $tokens.clone())
    };

    ($symbol: expr, $opcode: expr, $($tokens: expr),+) => {
        $crate::assembler::model::Node::new(
            $symbol.clone(),
            $opcode.clone(),
            vec![$(node!(@convert_token $tokens)),+]
        )
    };

    ($symbol: expr, $opcode: expr) => {
        $crate::assembler::model::Node::new(
            $symbol.clone(),
            $opcode.clone(),
            Vec::new()
        )
    };

    (@convert_token $token: literal) => {
        $crate::assembler::model::Token::Immediate($token)
    };

    (@convert_token $token: expr) => {
        $token.clone()
    };
}

/// Extracts a specific token type from a token
#[macro_export]
macro_rules! expect_token {
    ($token:expr, Symbol) => {
        match $token {
            $crate::assembler::model::Token::Symbol(value) => Ok(value.clone()),
            other => Err($crate::assembler::AssembleError::UnexpectedToken(
                other.clone(),
                $crate::assembler::model::TokenType::Symbol,
            )),
        }
    };
    ($token:expr, Register) => {
        match $token {
            $crate::assembler::model::Token::Register(value) => Ok(value.clone()),
            other => Err($crate::assembler::AssembleError::UnexpectedToken(
                other.clone(),
                $crate::assembler::model::TokenType::Register,
            )),
        }
    };
    ($token:expr, Immediate) => {
        match $token {
            $crate::assembler::model::Token::Immediate(value) => Ok(value.clone()),
            other => Err($crate::assembler::AssembleError::UnexpectedToken(
                other.clone(),
                $crate::assembler::model::TokenType::Immediate,
            )),
        }
    };
    ($token:expr, StringLit) => {
        match $token {
            $crate::assembler::model::Token::StringLit(value) => Ok(value.clone()),
            other => Err($crate::assembler::AssembleError::UnexpectedToken(
                other.clone(),
                $crate::assembler::model::TokenType::StringLit,
            )),
        }
    };
    ($token:expr, Opcode) => {
        match $token {
            $crate::assembler::model::Token::Opcode(value) => Ok(value.clone()),
            other => Err($crate::assembler::AssembleError::UnexpectedToken(
                other.clone(),
                $crate::assembler::model::TokenType::Opcode,
            )),
        }
    };
}

/// Checks if a token matches any of the specified types
#[macro_export]
macro_rules! expect_type {
    ($token:expr, $($variant:ident),+) => {{
        let token = $token;
        match &token {
            $(
                $crate::assembler::model::Token::$variant(_) => Ok(token.clone()),
            )+
            other => {
                let expected_type = expect_type!(@get_first_type $($variant),+);
                Err($crate::assembler::AssembleError::UnexpectedToken(
                    other.clone().clone(),
                    expected_type,
                ))
            }
        }
    }};

    (@get_first_type Symbol $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Symbol };
    (@get_first_type Register $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Register };
    (@get_first_type Immediate $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Immediate };
    (@get_first_type StringLit $(, $rest:ident)*) => { $crate::assembler::model::TokenType::StringLit };
    (@get_first_type Opcode $(, $rest:ident)*) => { $crate::assembler::model::TokenType::Opcode };
}
