use crate::*;

use assembler::*;
use assembler::DefinitionType::*;
use assembler::SymbolRole::*;


/// Compiles multiple source code files into one.
pub struct Compiler {
    pub source_path: PathBuf,
    pub resolver: Resolver,
}

impl Compiler {
    pub fn from_string<P: AsRef<Path>>(source_code: String, path: P) -> Self {
        let source_unit = SourceUnit::from_string(source_code, &path, parse_symbols);
        Self {
            source_path: path.as_ref().to_path_buf(),
            resolver: Resolver::new(source_unit)
        }
    }

    pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, FileError> {
        let source_unit = SourceUnit::from_path(&path, None, parse_symbols)?;
        Ok(Self {
            source_path: path.as_ref().to_path_buf(),
            resolver: Resolver::new(source_unit)
        })
    }

    /// Find library files descending from the parent directory.
    pub fn include_libs_from_parent(&mut self, ext: &str) {
        if let Some(parent_path) = self.source_path.parent() {
            let parent_path = parent_path.to_owned();
            self.include_libs_from_path(&parent_path, ext);
        }
    }

    /// Find library files at or descending from a path.
    pub fn include_libs_from_path(&mut self, path: &Path, ext: &str) {
        let libraries = gather_from_path(path, Some(ext), parse_symbols);
        self.resolver.add_library_source_units(libraries);
        self.resolver.resolve();
    }

    /// Find library files from a PATH-style environment variable.
    pub fn include_libs_from_path_variable(&mut self, name: &str, ext: &str) {
        let libraries = gather_from_path_variable(name, Some(ext), parse_symbols);
        self.resolver.add_library_source_units(libraries);
        self.resolver.resolve();
    }

    pub fn error(&self) -> Option<ResolverError> {
        self.resolver.error()
    }

    pub fn get_compiled_source(&mut self) -> Result<String, MergeError> {
        self.resolver.calculate_hierarchy();
        self.resolver.get_merged_source_code(push_source_code)
    }
}


/// Parse all symbols from a source code string.
fn parse_symbols(source_code: &str, path: Option<&Path>) -> Option<Vec<Symbol>> {
    let syntactic = match parse_syntactic(source_code, path) {
        Ok(syntactic) => syntactic,
        Err(_errors) => return None,
    };
    let semantic = match parse_semantic(syntactic) {
        Ok(semantic) => semantic,
        Err(_errors) => return None,
    };
    Some(SymbolParser::new().parse(&semantic))
}


// Extract symbol definitions from a list of semantic tokens.
pub struct SymbolParser {
    pub macro_name: Option<String>,
    pub symbols: Vec<Symbol>,
}

impl SymbolParser {
    pub fn new() -> Self {
        Self {
            macro_name: None,
            symbols: Vec::new(),
        }
    }

    fn record_symbol(&mut self, name: &str, source: &SourceSpan, role: SymbolRole) {
        let name = name.to_string();
        let namespace = match &self.macro_name {
            Some(macro_name) => vec![macro_name.to_owned()],
            None => vec![],
        };
        let source = source.to_owned();
        self.symbols.push(Symbol { name, namespace, source, role });

    }

    pub fn parse(mut self, semantic: &[Tracked<SemanticToken>]) -> Vec<Symbol> {
        for token in semantic {
            let source = &token.source;
            match &token.value {
                SemanticToken::MacroDefinition(definition) => {
                    // Record macro definition.
                    self.record_symbol(
                        &definition.name,
                        &definition.name.source,
                        Definition(MustPrecedeReference),
                    );
                    self.macro_name = Some(definition.name.to_string());

                    for argument in &definition.arguments {
                        self.record_symbol(
                            &argument.name,
                            &argument.source,
                            Definition(MustPrecedeReference),
                        );
                    }
                    match &definition.body {
                        MacroDefinitionBody::Integer(integer) => {
                            self.parse_integer_token(&integer, &integer.source)
                        }
                        MacroDefinitionBody::Invocation(invocation) => {
                            self.parse_invocation(&invocation, &invocation.source)
                        }
                        MacroDefinitionBody::Block(tokens) => {
                            for token in tokens {
                                self.parse_block_token(&token, &token.source);
                            }
                        }
                    }
                    self.macro_name = None;
                }
                SemanticToken::BlockToken(token) => {
                    self.parse_block_token(token, &source);
                }
            }
        }
        return self.symbols;
    }

    fn parse_expression(&mut self, expression: &Expression, _source: &SourceSpan) {
        for token in &expression.tokens {
            let source = &token.source;
            match &token.value {
                ExpressionToken::IntegerToken(integer) => {
                    self.parse_integer_token(integer, source);
                }
                ExpressionToken::Invocation(invocation) => {
                    self.parse_invocation(invocation, source);
                }
                ExpressionToken::Operator(_) => (),
            }
        }
    }

    fn parse_invocation(&mut self, invocation: &Invocation, source: &SourceSpan) {
        self.record_symbol(
            &invocation.name,
            &source,
            Reference,
        );

        for argument in &invocation.arguments {
            let source = &argument.source;
            match &argument.value {
                InvocationArgument::IntegerToken(integer) => {
                    self.parse_integer_token(integer, &source);
                }
                InvocationArgument::BlockToken(block) => {
                    self.parse_block_token(block, &source);
                }
                InvocationArgument::Invocation(invocation) => {
                    self.parse_invocation(invocation, &source);
                }
                InvocationArgument::String(_) => (),
            }
        }
    }

    fn parse_block_token(&mut self, token: &BlockToken, source: &SourceSpan) {
        match token {
            BlockToken::LabelDefinition(name) => {
                self.record_symbol(
                    &name,
                    &source,
                    Definition(CanFollowReference),
                );
            }
            BlockToken::PinnedAddress(integer) => {
                self.parse_integer_token(integer, &integer.source);
            }
            BlockToken::ConditionalBlock(condition) => {
                self.parse_integer_token(&condition.predicate, &condition.predicate.source);
                self.parse_block_token(&condition.body, &condition.body.source);
            }
            BlockToken::WordTemplate(word_template) => {
                for field in &word_template.fields {
                    self.record_symbol(
                        &field.name.to_string(),
                        &field.source,
                        Reference,
                    );
                }
            }
            BlockToken::Block(tokens) => {
                for token in tokens {
                    self.parse_block_token(token, &token.source);
                }
            }
            BlockToken::Invocation(invocation) => {
                self.parse_invocation(invocation, source);
            }
        }
    }

    fn parse_integer_token(&mut self, token: &IntegerToken, source: &SourceSpan) {
        match &token {
            IntegerToken::Expression(expression) => {
                self.parse_expression(&expression, source)
            }
            IntegerToken::Invocation(invocation) => {
                self.parse_invocation(&invocation, source)
            }
            IntegerToken::IntegerLiteral(_) => (),
        }
    }
}


/// Push source code to a source compilation string.
fn push_source_code(compilation: &mut String, source_file: &SourceFile) {
    // Skip blank files.
    let source_code = &source_file.source_code;
    if source_code.chars().all(|c| c.is_whitespace()) { return; }
    // Ensure that the previous section is followed by two newline characters.
    if !compilation.is_empty() {
        if !compilation.ends_with('\n') { compilation.push('\n'); }
        if !compilation.ends_with("\n\n") { compilation.push('\n'); }
    }
    // Push a path comment and the source code.
    let path_str = source_file.path.as_os_str().to_string_lossy();
    let path_comment = format!("(: {path_str} )\n");
    compilation.push_str(&path_comment);
    compilation.push_str(&source_code);
}