//! Portable, unverknüpfte Modulprodukte. Kein Quelltext und kein ausführbares TBC.
use crate::bytecode::{w_string, CompiledModule, Enc, LoadError, Reader, TBC_VERSION};
use tb_frontend::{
    ast::{Expr, Module, Param, Proc, ProcKind, ProcSig, Stmt, TypeName},
    hir::HCommon,
    lexer::Suffix,
    SourcePos,
};

pub const TBL_VERSION: u16 = 1;
pub use crate::bytecode::RUNTIME_VERSION as TBL_RUNTIME_VERSION;

#[derive(Debug, Clone)]
pub struct ModuleMetadata {
    pub name: String,
    pub pos: SourcePos,
    pub exports: Vec<Stmt>,
    pub declarations: Vec<(String, SourcePos)>,
    pub defined: Vec<String>,
    pub commons: Vec<HCommon>,
}
impl ModuleMetadata {
    pub(crate) fn from_source(module: &Module, exports: Vec<Stmt>, commons: Vec<HCommon>) -> Self {
        Self {
            name: module.name.clone(),
            pos: module
                .body
                .iter()
                .map(tb_frontend::sema::stmt_pos)
                .find(|p| p.line > 0)
                .or_else(|| module.procs.first().map(|p| p.pos))
                .unwrap_or_default(),
            exports,
            declarations: module
                .body
                .iter()
                .filter_map(|s| match s {
                    Stmt::Declare { sig, pos } => Some((sig.name.clone(), *pos)),
                    _ => None,
                })
                .chain(module.procs.iter().map(|p| (p.sig.name.clone(), p.pos)))
                .collect(),
            defined: module.procs.iter().map(|p| p.sig.name.clone()).collect(),
            commons,
        }
    }
    pub(crate) fn proc_pos(&self, name: &str) -> SourcePos {
        self.declarations
            .iter()
            .find(|(n, _)| n == name)
            .map_or(self.pos, |(_, p)| *p)
    }
    /// Eine Deklarationssicht für den vorhandenen Namens-/Typresolver, ohne Programmrümpfe.
    pub(crate) fn declaration_view(&self) -> Module {
        Module {
            name: self.name.clone(),
            body: self
                .exports
                .iter()
                .filter(|s| !matches!(s, Stmt::Declare { .. }))
                .cloned()
                .collect(),
            procs: self
                .exports
                .iter()
                .filter_map(|s| match s {
                    Stmt::Declare { sig, pos } => Some(Proc {
                        sig: sig.clone(),
                        is_static: false,
                        body: vec![],
                        pos: *pos,
                        end_pos: *pos,
                    }),
                    _ => None,
                })
                .collect(),
        }
    }
}
#[derive(Debug, Clone)]
pub struct LibraryModule {
    pub metadata: ModuleMetadata,
    pub code: CompiledModule,
}
#[derive(Debug, Clone, Default)]
pub struct Library {
    pub modules: Vec<LibraryModule>,
}

fn count(out: &mut Vec<u8>, n: usize) -> Result<(), String> {
    u32::try_from(n)
        .map_err(|_| "TBL-Tabelle zu groß")?
        .enc(out);
    Ok(())
}
fn pos(out: &mut Vec<u8>, p: SourcePos) {
    p.source.enc(out);
    p.line.enc(out);
    p.column.enc(out);
}
fn read_pos(r: &mut Reader) -> Result<SourcePos, LoadError> {
    Ok(SourcePos {
        source: r.u32()?,
        line: r.u32()?,
        column: r.u32()?,
    })
}
fn suffix(out: &mut Vec<u8>, s: Option<Suffix>) {
    out.push(s.map_or(0, |s| s.as_char() as u8));
}
fn read_suffix(r: &mut Reader) -> Result<Option<Suffix>, LoadError> {
    let b = r.u8()?;
    if b == 0 {
        Ok(None)
    } else {
        Suffix::from_char(b as char)
            .map(Some)
            .ok_or(LoadError::Corrupt("TBL-Suffix"))
    }
}
fn ty(out: &mut Vec<u8>, t: &TypeName) {
    use TypeName::*;
    out.push(match t {
        Integer => 0,
        Long => 1,
        Single => 2,
        Double => 3,
        Currency => 4,
        Str => 5,
        FixedStr(_) => 6,
        Form => 7,
        Control => 8,
        Udt(_) => 9,
    });
    match t {
        FixedStr(n) => n.enc(out),
        Udt(n) => w_string(out, n),
        _ => {}
    }
}
fn read_ty(r: &mut Reader) -> Result<TypeName, LoadError> {
    use TypeName::*;
    Ok(match r.u8()? {
        0 => Integer,
        1 => Long,
        2 => Single,
        3 => Double,
        4 => Currency,
        5 => Str,
        6 => FixedStr(i64::dec(r)?),
        7 => Form,
        8 => Control,
        9 => Udt(r.string()?),
        _ => return Err(LoadError::Corrupt("TBL-Typ")),
    })
}
fn declaration(out: &mut Vec<u8>, s: &Stmt) -> Result<(), String> {
    match s {
        Stmt::Declare { sig, pos: p } => {
            out.push(0);
            pos(out, *p);
            out.push(if sig.kind == ProcKind::Sub { 0 } else { 1 });
            w_string(out, &sig.name);
            suffix(out, sig.suffix);
            count(out, sig.params.len())?;
            for p in &sig.params {
                w_string(out, &p.name);
                suffix(out, p.suffix);
                p.array.enc(out);
                p.as_type.is_some().enc(out);
                if let Some(t) = &p.as_type {
                    ty(out, t);
                }
            }
        }
        Stmt::TypeDecl {
            name,
            fields,
            pos: p,
        } => {
            out.push(1);
            pos(out, *p);
            w_string(out, name);
            count(out, fields.len())?;
            for (n, t) in fields {
                w_string(out, n);
                ty(out, t);
            }
        }
        Stmt::ConstDecl { items, pos: p } => {
            out.push(2);
            pos(out, *p);
            count(out, items.len())?;
            for (n, s, e) in items {
                w_string(out, n);
                suffix(out, *s);
                match e {
                    Expr::DoubleLit(v, _) => {
                        out.push(0);
                        v.enc(out);
                    }
                    Expr::StrLit(v, _) => {
                        out.push(1);
                        w_string(out, v);
                    }
                    _ => return Err(format!("Nicht aufgelöste TBL-Konstante: {n}")),
                }
            }
        }
        _ => return Err("Ungültige TBL-Exportdeklaration".into()),
    }
    Ok(())
}
fn read_declaration(r: &mut Reader) -> Result<Stmt, LoadError> {
    let tag = r.u8()?;
    let p = read_pos(r)?;
    Ok(match tag {
        0 => {
            let kind = match r.u8()? {
                0 => ProcKind::Sub,
                1 => ProcKind::Function,
                _ => return Err(LoadError::Corrupt("TBL-Prozedurart")),
            };
            let name = r.string()?;
            let suffix = read_suffix(r)?;
            let mut params = vec![];
            for _ in 0..r.count()? {
                params.push(Param {
                    name: r.string()?,
                    suffix: read_suffix(r)?,
                    array: bool::dec(r)?,
                    as_type: if bool::dec(r)? {
                        Some(read_ty(r)?)
                    } else {
                        None
                    },
                });
            }
            Stmt::Declare {
                sig: ProcSig {
                    kind,
                    name,
                    suffix,
                    params,
                },
                pos: p,
            }
        }
        1 => {
            let name = r.string()?;
            let mut fields = vec![];
            for _ in 0..r.count()? {
                fields.push((r.string()?, read_ty(r)?));
            }
            Stmt::TypeDecl {
                name,
                fields,
                pos: p,
            }
        }
        2 => {
            let mut items = vec![];
            for _ in 0..r.count()? {
                let name = r.string()?;
                let suffix = read_suffix(r)?;
                let expr = match r.u8()? {
                    0 => Expr::DoubleLit(f64::dec(r)?, p),
                    1 => Expr::StrLit(r.string()?, p),
                    _ => return Err(LoadError::Corrupt("TBL-Konstante")),
                };
                items.push((name, suffix, expr));
            }
            Stmt::ConstDecl { items, pos: p }
        }
        _ => return Err(LoadError::Corrupt("TBL-Deklaration")),
    })
}
impl Library {
    pub fn to_tbl(&self) -> Result<Vec<u8>, String> {
        self.validate()?;
        let mut out = b"TBL\0".to_vec();
        TBL_VERSION.enc(&mut out);
        TBC_VERSION.enc(&mut out);
        TBL_RUNTIME_VERSION.enc(&mut out);
        count(&mut out, self.modules.len())?;
        for m in &self.modules {
            let mut section = vec![];
            let meta = &m.metadata;
            w_string(&mut section, &meta.name);
            pos(&mut section, meta.pos);
            count(&mut section, meta.exports.len())?;
            for s in &meta.exports {
                declaration(&mut section, s)?;
            }
            count(&mut section, meta.declarations.len())?;
            for (n, p) in &meta.declarations {
                w_string(&mut section, n);
                pos(&mut section, *p);
            }
            count(&mut section, meta.defined.len())?;
            for n in &meta.defined {
                w_string(&mut section, n);
            }
            count(&mut section, meta.commons.len())?;
            for c in &meta.commons {
                c.slot.enc(&mut section);
                c.block.is_some().enc(&mut section);
                if let Some(b) = &c.block {
                    w_string(&mut section, b);
                }
                w_string(&mut section, &c.key);
                c.ty.enc(&mut section);
                pos(&mut section, c.pos);
                c.dims.is_some().enc(&mut section);
                if let Some(dims) = &c.dims {
                    count(&mut section, dims.len())?;
                    for (lo, hi) in dims {
                        for v in [lo, hi] {
                            v.is_some().enc(&mut section);
                            if let Some(v) = v {
                                v.enc(&mut section);
                            }
                        }
                    }
                }
            }
            let code = m.code.to_tbc();
            count(&mut section, code.len())?;
            section.extend(code);
            count(&mut out, section.len())?;
            out.extend(section);
        }
        out.extend_from_slice(&crate::bytecode::checksum(&out).to_le_bytes());
        Ok(out)
    }
    pub fn from_tbl(bytes: &[u8]) -> Result<Self, String> {
        let end = bytes.len().checked_sub(8).ok_or("TBL-Prüfsumme fehlt")?;
        if crate::bytecode::checksum(&bytes[..end])
            != u64::from_le_bytes(bytes[end..].try_into().unwrap())
        {
            return Err("TBL-Prüfsumme stimmt nicht".into());
        }
        let bytes = &bytes[..end];
        let decode = || -> Result<Self, LoadError> {
            let mut r = Reader::new(bytes);
            if r.take(4)? != b"TBL\0" {
                return Err(LoadError::Corrupt("TBL-Magic"));
            }
            if r.u16()? != TBL_VERSION || r.u16()? != TBC_VERSION || r.u32()? != TBL_RUNTIME_VERSION
            {
                return Err(LoadError::Corrupt("TBL-/P-Code-/Runtime-Version"));
            }
            let mut modules = vec![];
            for _ in 0..r.count()? {
                let size = r.u32()? as usize;
                let mut s = Reader::new(r.take(size)?);
                let name = s.string()?;
                let pos = read_pos(&mut s)?;
                let mut exports = vec![];
                for _ in 0..s.count()? {
                    exports.push(read_declaration(&mut s)?);
                }
                let mut declarations = vec![];
                for _ in 0..s.count()? {
                    declarations.push((s.string()?, read_pos(&mut s)?));
                }
                let mut defined = vec![];
                for _ in 0..s.count()? {
                    defined.push(s.string()?);
                }
                let mut commons = vec![];
                for _ in 0..s.count()? {
                    let slot = s.u16()?;
                    let block = if bool::dec(&mut s)? {
                        Some(s.string()?)
                    } else {
                        None
                    };
                    let key = s.string()?;
                    let ty = tb_frontend::hir::HTy::dec(&mut s)?;
                    let pos = read_pos(&mut s)?;
                    let dims = if bool::dec(&mut s)? {
                        let mut dims = vec![];
                        for _ in 0..s.count()? {
                            let mut bound = || {
                                if bool::dec(&mut s)? {
                                    i32::dec(&mut s).map(Some)
                                } else {
                                    Ok(None)
                                }
                            };
                            dims.push((bound()?, bound()?));
                        }
                        Some(dims)
                    } else {
                        None
                    };
                    commons.push(HCommon {
                        slot,
                        block,
                        key,
                        ty,
                        dims,
                        pos,
                    });
                }
                let size = s.u32()? as usize;
                let code = CompiledModule::from_tbc(s.take(size)?)?;
                s.finish()?;
                modules.push(LibraryModule {
                    metadata: ModuleMetadata {
                        name,
                        pos,
                        exports,
                        declarations,
                        defined,
                        commons,
                    },
                    code,
                });
            }
            r.finish()?;
            Ok(Self { modules })
        };
        let library = decode().map_err(|e| format!("Ungültige TBL-Datei: {e}"))?;
        library.validate()?;
        Ok(library)
    }
    pub fn validate(&self) -> Result<(), String> {
        if self.modules.is_empty() || self.modules.len() > u16::MAX as usize {
            return Err("TBL ohne Module oder zu viele Module".into());
        }
        let mut names = std::collections::HashSet::new();
        for m in &self.modules {
            let meta = &m.metadata;
            let code = &m.code;
            if !names.insert(meta.name.to_uppercase()) {
                return Err(format!("Duplicate definition: module {}", meta.name));
            }
            code.validate().map_err(|e| e.to_string())?;
            if code.name != meta.name
                || code.modules.len() != 1
                || code.modules[0].0 != meta.name
                || code.startup_form.is_some()
            {
                return Err("TBL-Modulidentität/Startformular".into());
            }
            let valid_pos = |p: SourcePos| (p.source as usize) < code.sources.len();
            if !valid_pos(meta.pos)
                || meta.declarations.iter().any(|(_, p)| !valid_pos(*p))
                || meta
                    .exports
                    .iter()
                    .any(|s| !valid_pos(tb_frontend::sema::stmt_pos(s)))
            {
                return Err("TBL-Quellposition".into());
            }
            if code.procs[0].kind != tb_frontend::hir::HProcKind::Main
                || code.udts.len() > u16::MAX as usize
                || code.jump_tables.len() > u16::MAX as usize
                || code.data.len() > u32::MAX as usize
                || code.sources.len() > u32::MAX as usize
                || !matches!(code.procs[0].code.last(), Some(crate::bytecode::Instr::End))
                || code.procs.iter().any(|p| p.code.len() > u32::MAX as usize)
            {
                return Err("TBL-Modulrumpf/Grenzwert".into());
            }
            let mut procedure_names = std::collections::HashSet::new();
            for proc in code.procs.iter().skip(1) {
                if proc.kind == tb_frontend::hir::HProcKind::Main
                    || !procedure_names.insert(&proc.name)
                    || proc.params.len() > u8::MAX as usize
                {
                    return Err("TBL-Prozedurtabelle".into());
                }
            }
            if meta
                .declarations
                .iter()
                .any(|(n, _)| !procedure_names.contains(n))
            {
                return Err("TBL-Deklarationsreferenz".into());
            }
            let mut exported = std::collections::HashSet::new();
            let resolve_ty = |t: &TypeName| -> Option<tb_frontend::hir::HTy> {
                use tb_frontend::hir::{HTy, NumTy};
                Some(match t {
                    TypeName::Integer => HTy::Num(NumTy::Int),
                    TypeName::Long => HTy::Num(NumTy::Lng),
                    TypeName::Single => HTy::Num(NumTy::Sng),
                    TypeName::Double => HTy::Num(NumTy::Dbl),
                    TypeName::Currency => HTy::Num(NumTy::Cur),
                    TypeName::Str => HTy::Str,
                    TypeName::FixedStr(n) => HTy::FixedStr(u32::try_from(*n).ok()?),
                    TypeName::Form => HTy::Form,
                    TypeName::Control => HTy::Control,
                    TypeName::Udt(n) => {
                        HTy::Udt(u16::try_from(code.udts.iter().position(|u| u.name == *n)?).ok()?)
                    }
                })
            };
            let suffix_ty = |s: Option<Suffix>| {
                use tb_frontend::hir::{HTy, NumTy};
                match s {
                    Some(Suffix::Integer) => HTy::Num(NumTy::Int),
                    Some(Suffix::Long) => HTy::Num(NumTy::Lng),
                    None | Some(Suffix::Single) => HTy::Num(NumTy::Sng),
                    Some(Suffix::Double) => HTy::Num(NumTy::Dbl),
                    Some(Suffix::Currency) => HTy::Num(NumTy::Cur),
                    Some(Suffix::Str) => HTy::Str,
                }
            };
            for declaration in &meta.exports {
                let invalid = || format!("TBL-Exportvertrag in {}", meta.name);
                match declaration {
                    Stmt::Declare { sig, .. } => {
                        if !exported.insert((0, sig.name.clone()))
                            || !meta.defined.contains(&sig.name)
                        {
                            return Err(invalid());
                        }
                        let proc = code
                            .procs
                            .iter()
                            .skip(1)
                            .find(|p| p.name == sig.name)
                            .ok_or_else(invalid)?;
                        if (sig.kind == ProcKind::Sub)
                            != (proc.kind == tb_frontend::hir::HProcKind::Sub)
                            || proc.ret_ty
                                != (sig.kind == ProcKind::Function).then(|| suffix_ty(sig.suffix))
                            || sig.params.len() != proc.params.len()
                        {
                            return Err(invalid());
                        }
                        for (a, b) in sig.params.iter().zip(&proc.params) {
                            let ty = if let Some(t) = &a.as_type {
                                resolve_ty(t).ok_or_else(invalid)?
                            } else {
                                suffix_ty(a.suffix)
                            };
                            if ty != b.ty || a.array != b.array || b.by_ref == a.array {
                                return Err(invalid());
                            }
                        }
                    }
                    Stmt::TypeDecl { name, fields, .. } => {
                        if !exported.insert((1, name.clone())) {
                            return Err(invalid());
                        }
                        let layout = code
                            .udts
                            .iter()
                            .find(|u| u.name == *name)
                            .ok_or_else(invalid)?;
                        if fields.len() != layout.fields.len() {
                            return Err(invalid());
                        }
                        let mut names = std::collections::HashSet::new();
                        for ((n, t), actual) in fields.iter().zip(&layout.fields) {
                            if !names.insert(n)
                                || crate::codegen::type_init(&resolve_ty(t).ok_or_else(invalid)?)
                                    != *actual
                            {
                                return Err(invalid());
                            }
                        }
                    }
                    Stmt::ConstDecl { items, .. } => {
                        if items.len() != 1 {
                            return Err(invalid());
                        }
                        for (n, _, e) in items {
                            if !exported.insert((2, n.clone()))
                                || !matches!(e,Expr::DoubleLit(v,_) if v.is_finite())
                                    && !matches!(e, Expr::StrLit(..))
                            {
                                return Err(invalid());
                            }
                        }
                    }
                    _ => return Err(invalid()),
                }
            }
            let mut defs = std::collections::HashSet::new();
            for n in &meta.defined {
                if !exported.contains(&(0, n.clone()))
                    || !defs.insert(n)
                    || !code.procs.iter().skip(1).any(|p| p.name == *n)
                {
                    return Err(format!("TBL-Prozedurreferenz: {n}"));
                }
            }
            let mut common_slots = std::collections::HashSet::new();
            for c in &meta.commons {
                if c.slot as usize >= code.globals_init.len()
                    || !common_slots.insert(c.slot)
                    || code.globals_init[c.slot as usize]
                        != if c.dims.is_some() {
                            tb_runtime::value::TypeInit::Empty
                        } else {
                            crate::codegen::type_init(&c.ty)
                        }
                    || !valid_pos(c.pos)
                    || matches!(c.ty,tb_frontend::hir::HTy::Udt(id) if id as usize>=code.udts.len())
                    || c.dims.as_ref().is_some_and(|d| {
                        d.len() > 255
                            || d.iter()
                                .any(|(lo, hi)| lo.zip(*hi).is_some_and(|(lo, hi)| lo > hi))
                    })
                {
                    return Err("TBL-COMMON-Referenz/Bounds".into());
                }
            }
        }
        Ok(())
    }
}
