//! Quellen bleiben auch nach Include-Expansion einem Modul und einer Datei zugeordnet.
use crate::{ast, lexer, parser, Diagnostic, SourcePos};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SourceFile {
    pub module: u16,
    pub path: String,
}

#[derive(Debug, Clone, PartialEq)]
pub struct SourceSegment {
    pub file: String,
    pub first_line: u32,
    pub text: String,
}

#[derive(Debug, Clone, PartialEq)]
pub struct SourceUnit {
    pub name: String,
    pub segments: Vec<SourceSegment>,
}

impl SourceUnit {
    pub fn new(name: &str, file: &str, text: &str) -> Self {
        Self {
            name: name.into(),
            segments: vec![SourceSegment {
                file: file.into(),
                first_line: 1,
                text: text.into(),
            }],
        }
    }

    pub fn parse(
        &self,
        module: u16,
        sources: &mut Vec<SourceFile>,
    ) -> (ast::Module, Vec<Diagnostic>) {
        let mut text = String::new();
        let mut origins = Vec::new();
        for segment in &self.segments {
            let file = SourceFile {
                module,
                path: segment.file.clone(),
            };
            let source = sources.iter().position(|s| s == &file).unwrap_or_else(|| {
                sources.push(file);
                sources.len() - 1
            }) as u32;
            for (line, part) in segment.text.split_inclusive('\n').enumerate() {
                origins.push(SourcePos {
                    source,
                    line: segment.first_line + line as u32,
                    column: 1,
                });
                text.push_str(part);
                if !part.ends_with('\n') {
                    text.push('\n');
                }
            }
        }
        let map = |pos: &mut SourcePos| {
            let origin = origins
                .get(pos.line.saturating_sub(1) as usize)
                .copied()
                .or_else(|| {
                    origins.last().map(|p| SourcePos {
                        line: p.line + 1,
                        ..*p
                    })
                })
                .unwrap_or_default();
            pos.source = origin.source;
            pos.line = origin.line;
        };
        let mut lexed = lexer::lex(&text);
        for token in &mut lexed.tokens {
            map(&mut token.pos);
        }
        for diagnostic in &mut lexed.diagnostics {
            map(&mut diagnostic.pos);
        }
        let parsed = parser::parse(&self.name, &lexed.tokens);
        lexed.diagnostics.extend(parsed.diagnostics);
        (parsed.module, lexed.diagnostics)
    }
}

pub fn locate_diagnostics(diagnostics: &mut [Diagnostic], sources: &[SourceFile]) {
    for diagnostic in diagnostics {
        diagnostic.file = sources
            .get(diagnostic.pos.source as usize)
            .map(|s| s.path.clone());
    }
}
