use cord_trig::{TrigGraph, TrigOp};
use crate::ops::*;

/// A compiled CORDIC program ready for binary serialization or execution.
///
/// Each instruction produces its result into a slot matching its index.
/// The instruction list parallels the TrigGraph node list — compilation
/// is a direct 1:1 mapping with constants folded to fixed-point.
#[derive(Debug, Clone)]
pub struct CORDICProgram {
    pub word_bits: u8,
    pub instructions: Vec<CORDICInstr>,
    pub output: u32,
    pub atan_table: Vec<i64>,
    pub gain: i64,
}

#[derive(Debug, Clone)]
pub struct CompileConfig {
    pub word_bits: u8,
}

impl Default for CompileConfig {
    fn default() -> Self {
        Self { word_bits: 32 }
    }
}

impl CORDICProgram {
    /// Compile a TrigGraph into a CORDIC program.
    ///
    /// Each TrigOp node becomes one CORDICInstr. Constants are converted
    /// from f64 to fixed-point at compile time. Everything else is a
    /// direct structural mapping.
    pub fn compile(graph: &TrigGraph, config: &CompileConfig) -> Self {
        let frac_bits = config.word_bits - 1;
        let to_fixed = |val: f64| -> i64 {
            (val * (1i64 << frac_bits) as f64).round() as i64
        };

        let instructions: Vec<CORDICInstr> = graph.nodes.iter().map(|op| {
            match op {
                TrigOp::InputX => CORDICInstr::InputX,
                TrigOp::InputY => CORDICInstr::InputY,
                TrigOp::InputZ => CORDICInstr::InputZ,
                TrigOp::Const(v) => CORDICInstr::LoadImm(to_fixed(*v)),
                TrigOp::Add(a, b) => CORDICInstr::Add(*a, *b),
                TrigOp::Sub(a, b) => CORDICInstr::Sub(*a, *b),
                TrigOp::Mul(a, b) => CORDICInstr::Mul(*a, *b),
                TrigOp::Div(a, b) => CORDICInstr::Div(*a, *b),
                TrigOp::Neg(a) => CORDICInstr::Neg(*a),
                TrigOp::Abs(a) => CORDICInstr::Abs(*a),
                TrigOp::Sin(a) => CORDICInstr::Sin(*a),
                TrigOp::Cos(a) => CORDICInstr::Cos(*a),
                TrigOp::Tan(a) => CORDICInstr::Tan(*a),
                TrigOp::Asin(a) => CORDICInstr::Asin(*a),
                TrigOp::Acos(a) => CORDICInstr::Acos(*a),
                TrigOp::Atan(a) => CORDICInstr::Atan(*a),
                TrigOp::Sinh(a) => CORDICInstr::Sinh(*a),
                TrigOp::Cosh(a) => CORDICInstr::Cosh(*a),
                TrigOp::Tanh(a) => CORDICInstr::Tanh(*a),
                TrigOp::Asinh(a) => CORDICInstr::Asinh(*a),
                TrigOp::Acosh(a) => CORDICInstr::Acosh(*a),
                TrigOp::Atanh(a) => CORDICInstr::Atanh(*a),
                TrigOp::Sqrt(a) => CORDICInstr::Sqrt(*a),
                TrigOp::Exp(a) => CORDICInstr::Exp(*a),
                TrigOp::Ln(a) => CORDICInstr::Ln(*a),
                TrigOp::Hypot(a, b) => CORDICInstr::Hypot(*a, *b),
                TrigOp::Atan2(a, b) => CORDICInstr::Atan2(*a, *b),
                TrigOp::Min(a, b) => CORDICInstr::Min(*a, *b),
                TrigOp::Max(a, b) => CORDICInstr::Max(*a, *b),
                TrigOp::Clamp { val, lo, hi } => CORDICInstr::Clamp {
                    val: *val,
                    lo: *lo,
                    hi: *hi,
                },
            }
        }).collect();

        CORDICProgram {
            word_bits: config.word_bits,
            instructions,
            output: graph.output,
            atan_table: atan_table(config.word_bits),
            gain: cordic_gain(config.word_bits, frac_bits),
        }
    }

    /// Serialize to binary representation.
    pub fn to_bytes(&self) -> Vec<u8> {
        let mut buf = Vec::new();

        // Header: magic + word_bits
        buf.extend_from_slice(b"CORD");
        buf.push(self.word_bits);

        // Atan table
        let table_len = self.atan_table.len() as u16;
        buf.extend_from_slice(&table_len.to_le_bytes());
        for &val in &self.atan_table {
            buf.extend_from_slice(&val.to_le_bytes());
        }

        // Gain
        buf.extend_from_slice(&self.gain.to_le_bytes());

        // Output slot
        buf.extend_from_slice(&self.output.to_le_bytes());

        // Instructions
        let instr_len = self.instructions.len() as u32;
        buf.extend_from_slice(&instr_len.to_le_bytes());
        for instr in &self.instructions {
            encode_instruction(&mut buf, instr);
        }

        buf
    }

    /// Deserialize from binary.
    pub fn from_bytes(data: &[u8]) -> Option<Self> {
        if data.len() < 5 || &data[0..4] != b"CORD" {
            return None;
        }
        let word_bits = data[4];
        let mut pos = 5;

        // Atan table
        let table_len = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
        pos += 2;
        let mut atan_table = Vec::with_capacity(table_len);
        for _ in 0..table_len {
            let val = i64::from_le_bytes(data[pos..pos + 8].try_into().ok()?);
            atan_table.push(val);
            pos += 8;
        }

        // Gain
        let gain = i64::from_le_bytes(data[pos..pos + 8].try_into().ok()?);
        pos += 8;

        // Output slot
        let output = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
        pos += 4;

        // Instructions
        let instr_len = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
        pos += 4;
        let mut instructions = Vec::with_capacity(instr_len);
        for _ in 0..instr_len {
            let (instr, consumed) = decode_instruction(&data[pos..])?;
            instructions.push(instr);
            pos += consumed;
        }

        Some(CORDICProgram { word_bits, instructions, output, atan_table, gain })
    }
}
