use crate::*;

use syntactic as syn;
use syn::TokenVariant as SynVar;
use semantic::*;

use std::collections::VecDeque;


macro_rules! fn_is_syn_variant {
    ($name:ident, $variant:ty) => { paste::paste! {
        fn [< is_ $name >](token: &syn::Token) -> bool {
            match token.variant { $variant => true, _ => false, }
    } } }; }
fn_is_syn_variant!(block_open, syn::TokenVariant::BlockOpen);
fn_is_syn_variant!(block_close, syn::TokenVariant::BlockClose);
fn_is_syn_variant!(separator, syn::TokenVariant::Separator);
fn_is_syn_variant!(terminator, syn::TokenVariant::MacroDefinitionTerminator);


pub struct Tokens {
    tokens: VecDeque<syn::Token>,
}

impl Tokens {
    pub fn new<T: Into<VecDeque<syn::Token>>>(tokens: T) -> Self {
        Self { tokens: tokens.into() }
    }

    pub fn pop(&mut self) -> Option<syn::Token> {
        self.tokens.pop_front()
    }

    pub fn pop_if(&mut self, predicate: fn(&syn::Token) -> bool) -> Option<syn::Token> {
        match predicate(self.tokens.front()?) {
            true => self.tokens.pop_front(),
            false => None,
        }
    }

    pub fn unpop(&mut self, token: syn::Token) {
        self.tokens.push_front(token);
    }

    /// Pull tokens until the predicate returns true, otherwise return Err.
    pub fn pull_until(&mut self, predicate: fn(&syn::Token) -> bool) -> Result<Self, ()> {
        let mut output = VecDeque::new();
        while let Some(token) = self.tokens.pop_front() {
            match predicate(&token) {
                true => return Ok(Self::new(output)),
                false => output.push_back(token),
            };
        }
        return Err(());
    }

    pub fn take(&mut self) -> Self {
        Self { tokens: std::mem::take(&mut self.tokens) }
    }

    pub fn len(&self) -> usize {
        self.tokens.len()
    }
}


pub struct ProgramParser {
    tokens: Tokens,
    definitions: Vec<Definition>,
    invocations: Vec<Invocation>,
    errors: Vec<ParseError>,
}

impl ProgramParser {
    pub fn new(syntactic_tokens: Vec<syn::Token>) -> Self {
        Self {
            tokens: Tokens::new(syntactic_tokens),
            definitions: Vec::new(),
            invocations: Vec::new(),
            errors: Vec::new(),
        }
    }

    pub fn parse(mut self) -> Program {
        while let Some(syn) = self.tokens.pop() {
            if let SynVar::MacroDefinition(name) = syn.variant {
                // Collect all tokens up to the next definition terminator.
                let Ok(definition_tokens) = self.tokens.pull_until(is_terminator) else {
                    let variant = ParseErrorVariant::UnterminatedMacroDefinition;
                    self.errors.push(ParseError { source: syn.source, variant});
                    break;
                };
                // Parse macro definition arguments.
                match DefinitionParser::new(name, syn.source, definition_tokens).parse() {
                    Ok(definition) => self.definitions.push(definition),
                    Err(errors) => self.errors.extend(errors),
                };
            }
        }

        Program {
            definitions: self.definitions,
            invocations: self.invocations,
            errors: self.errors,
        }
    }
}


pub struct DefinitionParser {
    name: String,
    source: SourceSpan,
    tokens: Tokens,
    arguments: Vec<ArgumentDefinition>,
    errors: Vec<ParseError>,
}

impl DefinitionParser {
    pub fn new(name: String, source: SourceSpan, tokens: Tokens) -> Self {
        Self {
            name,
            tokens,
            source,
            arguments: Vec::new(),
            errors: Vec::new(),
        }
    }

    pub fn parse(mut self) -> Result<Definition, Vec<ParseError>> {
        while let Some(definition) = self.parse_argument_definition() {
            self.arguments.push(definition)
        }
        if self.errors.is_empty() {
            let variant = self.parse_body();
            Ok(Definition {
                name: self.name,
                source: self.source,
                arguments: self.arguments,
                variant,
            })
        } else {
            Err(self.errors)
        }
    }

    fn parse_argument_definition(&mut self) -> Option<ArgumentDefinition> {
        // Only continue if the first token is a separator.
        self.tokens.pop_if(is_separator)?;

        // Pop argument tokens.
        let is_block = match self.tokens.pop_if(is_block_open) {
            Some(_) => true,
            None => false,
        };
        let token = self.tokens.pop();
        if is_block {
            self.tokens.pop_if(is_block_close);
        }
        // Parse argument token.
        let token = token?;
        let source = token.source;
        if let SynVar::Symbol(name) = token.variant {
            let variant = ArgumentDefinitionVariant::Integer;
            Some(ArgumentDefinition { name, source, variant })
        } else {
            let name = self.name.clone();
            let variant = ParseErrorVariant::InvalidArgumentDefinition(name);
            self.errors.push(ParseError { source, variant});
            None
        }
    }

    fn parse_body(&mut self) -> DefinitionVariant {
        // Attempt to parse an IntegerDefinition.
        if self.tokens.len() == 1 {
            let token = self.tokens.pop().unwrap();
            match token.variant {
                SynVar::DecimalLiteral(value) | SynVar::HexadecimalLiteral(value) => {
                    return DefinitionVariant::Integer(IntegerDefinition {
                        source: token.source,
                        variant: IntegerDefinitionVariant::Literal(value),
                    });
                }
                SynVar::ConstantExpression(expr) => {
                    return DefinitionVariant::Integer(IntegerDefinition {
                        source: token.source,
                        variant: IntegerDefinitionVariant::Constant(expr),
                    });
                }
                SynVar::Symbol(name) => {
                    return DefinitionVariant::Reference(ReferenceDefinition {
                        source: token.source,
                        name,
                    });
                }
                _ => (),
            }
            self.tokens.unpop(token);
        }

        // Parse the remaining tokens as a BlockDefinition.
        let block = BlockParser::new(self.tokens.take()).parse();
        return DefinitionVariant::Block(block);
    }
}


/// Parse an entire block, excluding delimiters.
pub struct BlockParser {
    tokens: Tokens,
    block_tokens: Vec<BlockToken>,
    errors: Vec<ParseError>,
}

impl BlockParser {
    pub fn new(tokens: Tokens) -> Self {
        Self { tokens, block_tokens: Vec::new(), errors: Vec::new() }
    }

    pub fn parse(mut self) -> BlockDefinition {
        while let Some(token) = self.tokens.pop() {
            let source = token.source;
            match token.variant {
                SynVar::Symbol(name) => {
                    let mut arguments = Vec::new();
                    while let Some(argument) = self.parse_invocation_argument() {
                        arguments.push(argument);
                    }
                    let invocation = Invocation { name, arguments };
                    let variant = BlockTokenVariant::Invocation(invocation);
                    let block_token = BlockToken { source, variant };
                    self.block_tokens.push(block_token);
                }
                SynVar::PackedBinaryLiteral(pbl) => {
                    let variant = BlockTokenVariant::Word(pbl);
                    let block_token = BlockToken { source, variant };
                    self.block_tokens.push(block_token);
                }
                _ => {
                    let variant = ParseErrorVariant::InvalidToken;
                    self.errors.push(ParseError { source, variant })
                }
            }
        }
        BlockDefinition { tokens: self.block_tokens, errors: self.errors }
    }

    fn parse_invocation_argument(&mut self) -> Option<DefinitionVariant> {
        // Only continue if the first token is a separator.
        self.tokens.pop_if(is_separator)?;

        if let Some(block_open) = self.tokens.pop_if(is_block_open) {
            let source = block_open.source;
            if let Ok(block_tokens) = self.tokens.pull_until(is_block_close) {
                let block = BlockParser::new(block_tokens).parse();
                Some(DefinitionVariant::Block(block))
            } else {
                let variant = ParseErrorVariant::UnterminatedBlockDefinition;
                self.errors.push(ParseError { source, variant });
                None
            }
        } else {
            let token = self.tokens.pop()?;
            let source = token.source;
            match token.variant {
                SynVar::Symbol(name) => {
                    let reference = ReferenceDefinition { source, name };
                    Some(DefinitionVariant::Reference(reference))
                }
                SynVar::DecimalLiteral(value) | SynVar::HexadecimalLiteral(value) => {
                    let variant = IntegerDefinitionVariant::Literal(value);
                    let integer = IntegerDefinition { source, variant };
                    Some(DefinitionVariant::Integer(integer))
                }
                SynVar::ConstantExpression(expr) => {
                    let variant = IntegerDefinitionVariant::Constant(expr);
                    let integer = IntegerDefinition { source, variant };
                    Some(DefinitionVariant::Integer(integer))
                }
                _ => {
                    let variant = ParseErrorVariant::InvalidToken;
                    self.errors.push(ParseError { source, variant });
                    None
                }
            }

        }
    }
}