//! Laufzeitwerte der TBVM: Tagged Enum (Entwurf docs/tbvm-design.md),
//! `Rc` statt GC (der Dialekt kennt keine Zyklen), Arrays/Records als
//! geteilte Handles, Referenzwerte für BYREF-Parameter.
//!
//! Hier lebt außerdem die **Zahlenkonvertierungs-Matrix** (Banker's
//! Rounding, Überlauf → Fehler 6) — die einzige Implementierung der in
//! docs/tbvm-design.md dokumentierten Semantik.

use crate::errors::RuntimeError;
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug, Clone)]
pub enum Value {
    Int(i16),
    Lng(i32),
    Sng(f32),
    Dbl(f64),
    Cur(i64),
    Str(Rc<str>),
    /// Formular-/Control-Objekt mit optionalem Index eines Control-Arrays.
    Obj(u16, Option<i32>),
    Arr(Rc<RefCell<ArrayObj>>),
    Rec(Rc<RefCell<RecordObj>>),
    /// Referenz (BYREF-Parameter-Slot).
    Ref(VarRef),
    /// Nicht-initialisiertes Array-/Record-Handle (Auto-DIM bei Zugriff).
    Empty,
}

/// Referenzziel eines BYREF-Parameters.
#[derive(Debug, Clone)]
pub enum VarRef {
    Global(u16),
    /// Absoluter Index in den Locals-Stack der VM.
    Stack(u32),
    /// Arrayelement (flacher Index).
    Elem(Rc<RefCell<ArrayObj>>, u32),
    /// Record-Feldpfad.
    Field(Rc<RefCell<RecordObj>>, Vec<u16>),
}

/// Initialisierungstyp eines Slots/Elements (serialisierbar im `.tbc`).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeInit {
    Int,
    Lng,
    Sng,
    Dbl,
    Cur,
    Str,
    FixedStr(u32),
    Udt(u16),
    /// Array-/Record-Slot ohne Vorbelegung (Auto-DIM).
    Empty,
}

/// UDT-Layout für die Default-Erzeugung.
#[derive(Debug, Clone)]
pub struct UdtLayout {
    pub name: String,
    pub fields: Vec<TypeInit>,
}

pub fn default_value(init: &TypeInit, udts: &[UdtLayout]) -> Value {
    match init {
        TypeInit::Int => Value::Int(0),
        TypeInit::Lng => Value::Lng(0),
        TypeInit::Sng => Value::Sng(0.0),
        TypeInit::Dbl => Value::Dbl(0.0),
        TypeInit::Cur => Value::Cur(0),
        TypeInit::Str => Value::Str(Rc::from("")),
        TypeInit::FixedStr(n) => Value::Str(Rc::from(" ".repeat(*n as usize).as_str())),
        TypeInit::Udt(id) => {
            let layout = &udts[*id as usize];
            let fields = layout
                .fields
                .iter()
                .map(|f| default_value(f, udts))
                .collect();
            Value::Rec(Rc::new(RefCell::new(RecordObj { fields })))
        }
        TypeInit::Empty => Value::Empty,
    }
}

#[derive(Debug)]
pub struct ArrayObj {
    pub elem: TypeInit,
    /// (Untergrenze, Obergrenze) je Dimension.
    pub dims: Vec<(i32, i32)>,
    pub data: Vec<Value>,
}

impl ArrayObj {
    pub fn new(
        elem: TypeInit,
        dims: Vec<(i32, i32)>,
        udts: &[UdtLayout],
    ) -> Result<Self, RuntimeError> {
        let mut len: usize = 1;
        for (lo, hi) in &dims {
            if hi < lo {
                return Err(RuntimeError::SUBSCRIPT_OUT_OF_RANGE);
            }
            let n = (*hi as i64 - *lo as i64 + 1) as usize;
            len = len.checked_mul(n).ok_or(RuntimeError::OUT_OF_MEMORY)?;
            if len > 64 * 1024 * 1024 {
                return Err(RuntimeError::OUT_OF_MEMORY);
            }
        }
        let mut data = Vec::with_capacity(len);
        for _ in 0..len {
            data.push(default_value(&elem, udts));
        }
        Ok(ArrayObj { elem, dims, data })
    }

    /// Flacher Index (zeilenweise, letzte Dimension läuft am schnellsten);
    /// Bereichsprüfung → Fehler 9.
    pub fn flat_index(&self, idx: &[i32]) -> Result<u32, RuntimeError> {
        if idx.len() != self.dims.len() {
            return Err(RuntimeError::SUBSCRIPT_OUT_OF_RANGE);
        }
        let mut flat: u64 = 0;
        for (i, (lo, hi)) in idx.iter().zip(self.dims.iter()) {
            if i < lo || i > hi {
                return Err(RuntimeError::SUBSCRIPT_OUT_OF_RANGE);
            }
            let span = (*hi as i64 - *lo as i64 + 1) as u64;
            flat = flat * span + (*i as i64 - *lo as i64) as u64;
        }
        Ok(flat as u32)
    }
}

#[derive(Debug)]
pub struct RecordObj {
    pub fields: Vec<Value>,
}

// ---- Konvertierungsmatrix ---------------------------------------------------

/// Kaufmännische Rundung zur nächsten geraden Zahl (Banker's Rounding,
/// Verhalten von CINT/CLNG des Vorbilds).
pub fn banker_round(x: f64) -> f64 {
    let floor = x.floor();
    let diff = x - floor;
    if diff > 0.5 {
        floor + 1.0
    } else if diff < 0.5 {
        floor
    } else {
        // exakt .5 → zur geraden Zahl
        if (floor as i64) % 2 == 0 {
            floor
        } else {
            floor + 1.0
        }
    }
}

pub fn f64_to_i16(x: f64) -> Result<i16, RuntimeError> {
    let r = banker_round(x);
    if !(i16::MIN as f64..=i16::MAX as f64).contains(&r) {
        return Err(RuntimeError::OVERFLOW);
    }
    Ok(r as i16)
}

pub fn f64_to_i32(x: f64) -> Result<i32, RuntimeError> {
    let r = banker_round(x);
    if !(i32::MIN as f64..=i32::MAX as f64).contains(&r) {
        return Err(RuntimeError::OVERFLOW);
    }
    Ok(r as i32)
}

pub fn f64_to_f32(x: f64) -> Result<f32, RuntimeError> {
    if x.is_finite() && x.abs() > f32::MAX as f64 {
        return Err(RuntimeError::OVERFLOW);
    }
    Ok(x as f32)
}

/// f64 → CURRENCY (Festkomma ×10 000, Banker's auf der 4. Nachkommastelle).
pub fn f64_to_cur(x: f64) -> Result<i64, RuntimeError> {
    let scaled = banker_round(x * 10_000.0);
    if !((i64::MIN as f64) < scaled && scaled < (i64::MAX as f64)) {
        return Err(RuntimeError::OVERFLOW);
    }
    Ok(scaled as i64)
}

/// CURRENCY → Ganzzahl (÷10 000, Banker's; ganzzahlig exakt).
pub fn cur_to_i64(c: i64) -> i64 {
    let q = c.div_euclid(10_000);
    let r = c.rem_euclid(10_000);
    // r in 0..10000; runde halb-zu-gerade
    if r > 5_000 {
        q + 1
    } else if r < 5_000 || q % 2 == 0 {
        q
    } else {
        q + 1
    }
}

pub fn cur_to_i16(c: i64) -> Result<i16, RuntimeError> {
    let v = cur_to_i64(c);
    i16::try_from(v).map_err(|_| RuntimeError::OVERFLOW)
}

pub fn cur_to_i32(c: i64) -> Result<i32, RuntimeError> {
    let v = cur_to_i64(c);
    i32::try_from(v).map_err(|_| RuntimeError::OVERFLOW)
}

pub fn cur_to_f64(c: i64) -> f64 {
    c as f64 / 10_000.0
}

/// i32 → i16 mit Bereichsprüfung.
pub fn i32_to_i16(v: i32) -> Result<i16, RuntimeError> {
    i16::try_from(v).map_err(|_| RuntimeError::OVERFLOW)
}

/// Numerischer Wert als f64 (für Builtins, die per Tag dispatchen).
pub fn as_f64(v: &Value) -> f64 {
    match v {
        Value::Int(x) => *x as f64,
        Value::Lng(x) => *x as f64,
        Value::Sng(x) => *x as f64,
        Value::Dbl(x) => *x,
        Value::Cur(x) => cur_to_f64(*x),
        _ => 0.0,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn banker_rounding() {
        assert_eq!(banker_round(0.5), 0.0);
        assert_eq!(banker_round(1.5), 2.0);
        assert_eq!(banker_round(2.5), 2.0);
        assert_eq!(banker_round(-0.5), 0.0);
        assert_eq!(banker_round(-1.5), -2.0);
        assert_eq!(banker_round(2.4), 2.0);
        assert_eq!(banker_round(2.6), 3.0);
    }

    #[test]
    fn overflow_bei_konvertierung() {
        assert!(f64_to_i16(40_000.0).is_err());
        assert!(f64_to_i16(32_767.4).is_ok());
        assert!(f64_to_i32(3e9).is_err());
        assert!(f64_to_f32(1e39).is_err());
    }

    #[test]
    fn currency_rundung() {
        assert_eq!(f64_to_cur(1.5).unwrap(), 15_000);
        assert_eq!(f64_to_cur(-2.25).unwrap(), -22_500);
        // Halb-zu-gerade nur bei exakt darstellbarem .5-Fall (Skalierung
        // 10000.5 ist binär exakt, wenn der Ausgangswert es hergibt):
        assert_eq!(banker_round(10_000.5), 10_000.0);
        assert_eq!(cur_to_i64(15_000), 2); // 1.5 → 2
        assert_eq!(cur_to_i64(25_000), 2); // 2.5 → 2
        assert_eq!(cur_to_i64(-15_000), -2);
    }

    #[test]
    fn array_indexpruefung() {
        let a = ArrayObj::new(TypeInit::Int, vec![(0, 10)], &[]).unwrap();
        assert_eq!(a.flat_index(&[0]).unwrap(), 0);
        assert_eq!(a.flat_index(&[10]).unwrap(), 10);
        assert!(a.flat_index(&[11]).is_err());
        assert!(a.flat_index(&[-1]).is_err());
        let b = ArrayObj::new(TypeInit::Int, vec![(1, 3), (1, 2)], &[]).unwrap();
        assert_eq!(b.flat_index(&[1, 1]).unwrap(), 0);
        assert_eq!(b.flat_index(&[1, 2]).unwrap(), 1);
        assert_eq!(b.flat_index(&[2, 1]).unwrap(), 2);
    }
}
