/// A compiled CORDIC instruction operating on indexed slots.
///
/// Slot i holds the result of instruction i. All operand references
/// point to earlier slots (j < i), matching the TrigGraph's topological order.
///
/// CORDIC mapping:
///   Sin, Cos    → rotation mode (angle → sin/cos via shift-and-add)
///   Hypot       → vectoring mode (magnitude via shift-and-add)
///   Atan2       → vectoring mode (angle via shift-and-add)
///   Mul         → fixed-point multiply (shift-and-add)
///   Add, Sub, Neg, Abs, Min, Max, Clamp → direct binary ops
#[derive(Debug, Clone)]
pub enum CORDICInstr {
    InputX,
    InputY,
    InputZ,
    LoadImm(i64),

    Add(u32, u32),
    Sub(u32, u32),
    Mul(u32, u32),
    Div(u32, u32),
    Neg(u32),
    Abs(u32),

    Sin(u32),
    Cos(u32),
    Tan(u32),

    Asin(u32),
    Acos(u32),
    Atan(u32),

    Sinh(u32),
    Cosh(u32),
    Tanh(u32),

    Asinh(u32),
    Acosh(u32),
    Atanh(u32),

    Sqrt(u32),
    Exp(u32),
    Ln(u32),

    Hypot(u32, u32),
    Atan2(u32, u32),

    Min(u32, u32),
    Max(u32, u32),
    Clamp { val: u32, lo: u32, hi: u32 },
}

/// Precomputed arctan table for CORDIC iterations.
/// atan(2^-i) in fixed-point with the given number of fractional bits.
pub fn atan_table(word_bits: u8) -> Vec<i64> {
    let frac_bits = word_bits - 1;
    (0..word_bits)
        .map(|i| {
            let angle = (2.0f64).powi(-(i as i32)).atan();
            (angle * (1i64 << frac_bits) as f64).round() as i64
        })
        .collect()
}

/// CORDIC gain constant K_n = prod(1/sqrt(1 + 2^{-2i})) for n iterations.
pub fn cordic_gain(iterations: u8, frac_bits: u8) -> i64 {
    let mut k = 1.0f64;
    for i in 0..iterations {
        k *= 1.0 / (1.0 + (2.0f64).powi(-2 * i as i32)).sqrt();
    }
    (k * (1i64 << frac_bits) as f64).round() as i64
}

// Binary encoding

const OP_INPUT_X: u8 = 0x00;
const OP_INPUT_Y: u8 = 0x01;
const OP_INPUT_Z: u8 = 0x02;
const OP_LOAD_IMM: u8 = 0x03;
const OP_ADD: u8 = 0x04;
const OP_SUB: u8 = 0x05;
const OP_MUL: u8 = 0x06;
const OP_DIV: u8 = 0x10;
const OP_NEG: u8 = 0x07;
const OP_ABS: u8 = 0x08;
const OP_SIN: u8 = 0x09;
const OP_COS: u8 = 0x0A;
const OP_HYPOT: u8 = 0x0B;
const OP_ATAN2: u8 = 0x0C;
const OP_MIN: u8 = 0x0D;
const OP_MAX: u8 = 0x0E;
const OP_CLAMP: u8 = 0x0F;
const OP_TAN: u8 = 0x11;
const OP_ASIN: u8 = 0x12;
const OP_ACOS: u8 = 0x13;
const OP_ATAN: u8 = 0x14;
const OP_SINH: u8 = 0x15;
const OP_COSH: u8 = 0x16;
const OP_TANH: u8 = 0x17;
const OP_ASINH: u8 = 0x18;
const OP_ACOSH: u8 = 0x19;
const OP_ATANH: u8 = 0x1A;
const OP_SQRT: u8 = 0x1B;
const OP_EXP: u8 = 0x1C;
const OP_LN: u8 = 0x1D;

pub fn encode_instruction(buf: &mut Vec<u8>, instr: &CORDICInstr) {
    match instr {
        CORDICInstr::InputX => buf.push(OP_INPUT_X),
        CORDICInstr::InputY => buf.push(OP_INPUT_Y),
        CORDICInstr::InputZ => buf.push(OP_INPUT_Z),
        CORDICInstr::LoadImm(v) => {
            buf.push(OP_LOAD_IMM);
            buf.extend_from_slice(&v.to_le_bytes());
        }
        CORDICInstr::Add(a, b) => {
            buf.push(OP_ADD);
            buf.extend_from_slice(&a.to_le_bytes());
            buf.extend_from_slice(&b.to_le_bytes());
        }
        CORDICInstr::Sub(a, b) => {
            buf.push(OP_SUB);
            buf.extend_from_slice(&a.to_le_bytes());
            buf.extend_from_slice(&b.to_le_bytes());
        }
        CORDICInstr::Mul(a, b) => {
            buf.push(OP_MUL);
            buf.extend_from_slice(&a.to_le_bytes());
            buf.extend_from_slice(&b.to_le_bytes());
        }
        CORDICInstr::Div(a, b) => {
            buf.push(OP_DIV);
            buf.extend_from_slice(&a.to_le_bytes());
            buf.extend_from_slice(&b.to_le_bytes());
        }
        CORDICInstr::Neg(a) => {
            buf.push(OP_NEG);
            buf.extend_from_slice(&a.to_le_bytes());
        }
        CORDICInstr::Abs(a) => {
            buf.push(OP_ABS);
            buf.extend_from_slice(&a.to_le_bytes());
        }
        CORDICInstr::Sin(a) => {
            buf.push(OP_SIN);
            buf.extend_from_slice(&a.to_le_bytes());
        }
        CORDICInstr::Cos(a) => {
            buf.push(OP_COS);
            buf.extend_from_slice(&a.to_le_bytes());
        }
        CORDICInstr::Tan(a) => { buf.push(OP_TAN); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Asin(a) => { buf.push(OP_ASIN); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Acos(a) => { buf.push(OP_ACOS); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Atan(a) => { buf.push(OP_ATAN); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Sinh(a) => { buf.push(OP_SINH); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Cosh(a) => { buf.push(OP_COSH); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Tanh(a) => { buf.push(OP_TANH); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Asinh(a) => { buf.push(OP_ASINH); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Acosh(a) => { buf.push(OP_ACOSH); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Atanh(a) => { buf.push(OP_ATANH); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Sqrt(a) => { buf.push(OP_SQRT); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Exp(a) => { buf.push(OP_EXP); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Ln(a) => { buf.push(OP_LN); buf.extend_from_slice(&a.to_le_bytes()); }
        CORDICInstr::Hypot(a, b) => {
            buf.push(OP_HYPOT);
            buf.extend_from_slice(&a.to_le_bytes());
            buf.extend_from_slice(&b.to_le_bytes());
        }
        CORDICInstr::Atan2(a, b) => {
            buf.push(OP_ATAN2);
            buf.extend_from_slice(&a.to_le_bytes());
            buf.extend_from_slice(&b.to_le_bytes());
        }
        CORDICInstr::Min(a, b) => {
            buf.push(OP_MIN);
            buf.extend_from_slice(&a.to_le_bytes());
            buf.extend_from_slice(&b.to_le_bytes());
        }
        CORDICInstr::Max(a, b) => {
            buf.push(OP_MAX);
            buf.extend_from_slice(&a.to_le_bytes());
            buf.extend_from_slice(&b.to_le_bytes());
        }
        CORDICInstr::Clamp { val, lo, hi } => {
            buf.push(OP_CLAMP);
            buf.extend_from_slice(&val.to_le_bytes());
            buf.extend_from_slice(&lo.to_le_bytes());
            buf.extend_from_slice(&hi.to_le_bytes());
        }
    }
}

fn read_u32(data: &[u8], pos: usize) -> Option<u32> {
    Some(u32::from_le_bytes(data.get(pos..pos + 4)?.try_into().ok()?))
}

fn read_i64(data: &[u8], pos: usize) -> Option<i64> {
    Some(i64::from_le_bytes(data.get(pos..pos + 8)?.try_into().ok()?))
}

pub fn decode_instruction(data: &[u8]) -> Option<(CORDICInstr, usize)> {
    let op = *data.first()?;
    match op {
        OP_INPUT_X => Some((CORDICInstr::InputX, 1)),
        OP_INPUT_Y => Some((CORDICInstr::InputY, 1)),
        OP_INPUT_Z => Some((CORDICInstr::InputZ, 1)),
        OP_LOAD_IMM => {
            let v = read_i64(data, 1)?;
            Some((CORDICInstr::LoadImm(v), 9))
        }
        OP_ADD => {
            let a = read_u32(data, 1)?;
            let b = read_u32(data, 5)?;
            Some((CORDICInstr::Add(a, b), 9))
        }
        OP_SUB => {
            let a = read_u32(data, 1)?;
            let b = read_u32(data, 5)?;
            Some((CORDICInstr::Sub(a, b), 9))
        }
        OP_MUL => {
            let a = read_u32(data, 1)?;
            let b = read_u32(data, 5)?;
            Some((CORDICInstr::Mul(a, b), 9))
        }
        OP_DIV => {
            let a = read_u32(data, 1)?;
            let b = read_u32(data, 5)?;
            Some((CORDICInstr::Div(a, b), 9))
        }
        OP_NEG => {
            let a = read_u32(data, 1)?;
            Some((CORDICInstr::Neg(a), 5))
        }
        OP_ABS => {
            let a = read_u32(data, 1)?;
            Some((CORDICInstr::Abs(a), 5))
        }
        OP_SIN => {
            let a = read_u32(data, 1)?;
            Some((CORDICInstr::Sin(a), 5))
        }
        OP_COS => {
            let a = read_u32(data, 1)?;
            Some((CORDICInstr::Cos(a), 5))
        }
        OP_TAN => { let a = read_u32(data, 1)?; Some((CORDICInstr::Tan(a), 5)) }
        OP_ASIN => { let a = read_u32(data, 1)?; Some((CORDICInstr::Asin(a), 5)) }
        OP_ACOS => { let a = read_u32(data, 1)?; Some((CORDICInstr::Acos(a), 5)) }
        OP_ATAN => { let a = read_u32(data, 1)?; Some((CORDICInstr::Atan(a), 5)) }
        OP_SINH => { let a = read_u32(data, 1)?; Some((CORDICInstr::Sinh(a), 5)) }
        OP_COSH => { let a = read_u32(data, 1)?; Some((CORDICInstr::Cosh(a), 5)) }
        OP_TANH => { let a = read_u32(data, 1)?; Some((CORDICInstr::Tanh(a), 5)) }
        OP_ASINH => { let a = read_u32(data, 1)?; Some((CORDICInstr::Asinh(a), 5)) }
        OP_ACOSH => { let a = read_u32(data, 1)?; Some((CORDICInstr::Acosh(a), 5)) }
        OP_ATANH => { let a = read_u32(data, 1)?; Some((CORDICInstr::Atanh(a), 5)) }
        OP_SQRT => { let a = read_u32(data, 1)?; Some((CORDICInstr::Sqrt(a), 5)) }
        OP_EXP => { let a = read_u32(data, 1)?; Some((CORDICInstr::Exp(a), 5)) }
        OP_LN => { let a = read_u32(data, 1)?; Some((CORDICInstr::Ln(a), 5)) }
        OP_HYPOT => {
            let a = read_u32(data, 1)?;
            let b = read_u32(data, 5)?;
            Some((CORDICInstr::Hypot(a, b), 9))
        }
        OP_ATAN2 => {
            let a = read_u32(data, 1)?;
            let b = read_u32(data, 5)?;
            Some((CORDICInstr::Atan2(a, b), 9))
        }
        OP_MIN => {
            let a = read_u32(data, 1)?;
            let b = read_u32(data, 5)?;
            Some((CORDICInstr::Min(a, b), 9))
        }
        OP_MAX => {
            let a = read_u32(data, 1)?;
            let b = read_u32(data, 5)?;
            Some((CORDICInstr::Max(a, b), 9))
        }
        OP_CLAMP => {
            let val = read_u32(data, 1)?;
            let lo = read_u32(data, 5)?;
            let hi = read_u32(data, 9)?;
            Some((CORDICInstr::Clamp { val, lo, hi }, 13))
        }
        _ => None,
    }
}
