use crate::error::{Error, Result};

const PARM_SCALETYPE: usize = 0;
const PARM_NELMTS: usize = 2;
const PARM_CLASS: usize = 3;
const PARM_SIZE: usize = 4;
const PARM_SIGN: usize = 5;
const PARM_ORDER: usize = 6;
const PARM_FILAVAIL: usize = 7;
const PARM_FILVAL: usize = 8;

const CLS_INTEGER: u32 = 0;
const CLS_FLOAT: u32 = 1;
const SIGN_UNSIGNED: u32 = 0;
const SIGN_TWOS: u32 = 1;
const ORDER_LE: u32 = 0;
const ORDER_BE: u32 = 1;
const HEADER_LEN: usize = 21;

#[derive(Debug, Clone, Copy)]
struct Parms {
    size: usize,
    minbits: usize,
    order: u32,
}

/// Decompress HDF5 ScaleOffset-filtered chunks, appending decoded bytes to
/// `out`.
pub fn decompress_into(data: &[u8], client_data: &[u32], out: &mut Vec<u8>) -> Result<()> {
    can_apply_scaleoffset(client_data)?;

    if client_data.len() <= PARM_ORDER {
        return Err(Error::InvalidFormat(
            "scaleoffset filter missing datatype parameters".into(),
        ));
    }

    let scale_type = client_data[PARM_SCALETYPE];
    let nelmts = scaleoffset_usize(client_data[PARM_NELMTS], "scaleoffset element count")?;
    let class = client_data[PARM_CLASS];
    let size = scaleoffset_usize(client_data[PARM_SIZE], "scaleoffset datatype size")?;
    let sign = client_data[PARM_SIGN];
    let order = client_data[PARM_ORDER];

    if size == 0 {
        return Err(Error::InvalidFormat(
            "scaleoffset datatype size is zero".into(),
        ));
    }
    if size > 16 {
        return Err(Error::Unsupported(format!(
            "scaleoffset datatype size {size} exceeds 16-byte arithmetic support"
        )));
    }
    if order != ORDER_LE && order != ORDER_BE {
        return Err(Error::InvalidFormat(format!(
            "invalid scaleoffset byte order {order}"
        )));
    }
    validate_scaleoffset_datatype(class, sign, size, scale_type)?;
    if data.len() < HEADER_LEN {
        return Err(Error::InvalidFormat("scaleoffset data too short".into()));
    }

    let minbits = scaleoffset_usize(
        read_u32_le_at(data, 0, "scaleoffset minimum bit count")?,
        "scaleoffset minimum bit count",
    )?;
    validate_scaleoffset_parameters(class, sign, size, minbits)?;
    let minval_size = usize::from(data[4]);
    if minval_size > 16 {
        return Err(Error::InvalidFormat(
            "invalid scaleoffset minimum value header".into(),
        ));
    }
    let minval = read_le_u128(checked_window(
        data,
        5,
        minval_size,
        "scaleoffset minimum value header",
    )?);

    let out_len = nelmts
        .checked_mul(size)
        .ok_or_else(|| Error::InvalidFormat("scaleoffset output size overflow".into()))?;
    let start = out.len();
    out.resize(
        start
            .checked_add(out_len)
            .ok_or_else(|| Error::InvalidFormat("scaleoffset output size overflow".into()))?,
        0,
    );
    let out = &mut out[start..];

    if minbits == size * 8 {
        let raw_end = HEADER_LEN.checked_add(out_len).ok_or_else(|| {
            Error::InvalidFormat("scaleoffset full-precision data too short".into())
        })?;
        let raw = data.get(HEADER_LEN..raw_end).ok_or_else(|| {
            Error::InvalidFormat("scaleoffset full-precision data too short".into())
        })?;
        out.copy_from_slice(raw);
    } else if minbits != 0 {
        let parms = Parms {
            size,
            minbits,
            order,
        };
        let mut stream = BitStream::new(&data[HEADER_LEN..]);
        for idx in 0..nelmts {
            let data_offset = idx
                .checked_mul(size)
                .ok_or_else(|| Error::InvalidFormat("scaleoffset output offset overflow".into()))?;
            decompress_atomic(out, data_offset, &mut stream, parms)?;
        }
    }

    match class {
        CLS_INTEGER => {
            let fill = if client_data.get(PARM_FILAVAIL).copied().unwrap_or(0) != 0 {
                Some(read_fill_value(client_data, size, order))
            } else {
                None
            };
            postprocess_integer(out, size, sign, order, minbits, minval, fill)?
        }
        CLS_FLOAT if scale_type == 0 => {
            postprocess_float(out, size, order, minbits, minval, client_data)?
        }
        CLS_FLOAT => {
            return Err(Error::Unsupported(format!(
                "scaleoffset float scale type {scale_type}"
            )));
        }
        other => {
            return Err(Error::Unsupported(format!(
                "scaleoffset datatype class {other}"
            )));
        }
    }

    Ok(())
}

pub fn can_apply_scaleoffset(client_data: &[u32]) -> Result<()> {
    if client_data.len() <= PARM_ORDER {
        return Err(Error::InvalidFormat(
            "scaleoffset filter missing datatype parameters".into(),
        ));
    }
    let scale_type = client_data[PARM_SCALETYPE];
    let class = client_data[PARM_CLASS];
    let size = scaleoffset_usize(client_data[PARM_SIZE], "scaleoffset datatype size")?;
    let sign = client_data[PARM_SIGN];
    let order = client_data[PARM_ORDER];
    if size == 0 {
        return Err(Error::InvalidFormat(
            "scaleoffset datatype size is zero".into(),
        ));
    }
    if size > 16 {
        return Err(Error::Unsupported(format!(
            "scaleoffset datatype size {size} exceeds 16-byte arithmetic support"
        )));
    }
    if order != ORDER_LE && order != ORDER_BE {
        return Err(Error::InvalidFormat(format!(
            "invalid scaleoffset byte order {order}"
        )));
    }
    validate_scaleoffset_datatype(class, sign, size, scale_type)
}

pub fn scaleoffset_convert(
    data: &mut [u8],
    element_size: usize,
    from_order: u32,
    to_order: u32,
) -> Result<()> {
    if from_order != ORDER_LE && from_order != ORDER_BE {
        return Err(Error::InvalidFormat(format!(
            "invalid scaleoffset byte order {from_order}"
        )));
    }
    if to_order != ORDER_LE && to_order != ORDER_BE {
        return Err(Error::InvalidFormat(format!(
            "invalid scaleoffset byte order {to_order}"
        )));
    }
    if element_size == 0 {
        return Err(Error::InvalidFormat(
            "scaleoffset datatype size is zero".into(),
        ));
    }
    if from_order != to_order {
        for chunk in data.chunks_exact_mut(element_size) {
            chunk.reverse();
        }
    }
    Ok(())
}

pub fn scaleoffset_log2(value: u64) -> Option<u32> {
    (value != 0).then(|| u64::BITS - 1 - value.leading_zeros())
}

/// HDF5 ScaleOffset filter entry point, appending the result to `out`.
pub fn filter_scaleoffset_into(
    data: &[u8],
    client_data: &[u32],
    reverse: bool,
    out: &mut Vec<u8>,
) -> Result<()> {
    if reverse {
        decompress_into(data, client_data, out)
    } else {
        scaleoffset_compress_into(data, client_data, out)
    }
}

/// Compress ScaleOffset-filtered data, appending the encoded bytes to `out`.
pub fn scaleoffset_compress_into(
    data: &[u8],
    client_data: &[u32],
    out: &mut Vec<u8>,
) -> Result<()> {
    can_apply_scaleoffset(client_data)?;
    let scale_type = client_data[PARM_SCALETYPE];
    let nelmts = scaleoffset_usize(client_data[PARM_NELMTS], "scaleoffset element count")?;
    let class = client_data[PARM_CLASS];
    let size = scaleoffset_usize(client_data[PARM_SIZE], "scaleoffset datatype size")?;
    let sign = client_data[PARM_SIGN];
    let order = client_data[PARM_ORDER];
    let expected = nelmts
        .checked_mul(size)
        .ok_or_else(|| Error::InvalidFormat("scaleoffset input size overflow".into()))?;
    if data.len() < expected {
        return Err(Error::InvalidFormat(
            "scaleoffset input data too short".into(),
        ));
    }

    let mut packed = Vec::with_capacity(expected);
    let (minbits, minval) = match class {
        CLS_INTEGER => {
            scaleoffset_precompress_i_into(&data[..expected], size, sign, order, &mut packed)?
        }
        CLS_FLOAT if scale_type == 0 => scaleoffset_precompress_fd_into(
            &data[..expected],
            size,
            order,
            client_data,
            &mut packed,
        )?,
        CLS_FLOAT => {
            return Err(Error::Unsupported(format!(
                "scaleoffset float scale type {scale_type}"
            )));
        }
        other => {
            return Err(Error::Unsupported(format!(
                "scaleoffset datatype class {other}"
            )));
        }
    };

    let header_start = out.len();
    out.resize(
        header_start
            .checked_add(HEADER_LEN)
            .ok_or_else(|| Error::InvalidFormat("scaleoffset output size overflow".into()))?,
        0,
    );
    let header = &mut out[header_start..header_start + HEADER_LEN];
    let minbits_u32 = u32::try_from(minbits).map_err(|_| {
        Error::InvalidFormat("scaleoffset minimum bit count does not fit in u32".into())
    })?;
    header[..4].copy_from_slice(&minbits_u32.to_le_bytes());
    header[4] = size as u8;
    write_uint(
        checked_window_mut(header, 5, size, "scaleoffset minimum value header")?,
        ORDER_LE,
        minval,
    );

    if minbits == size * 8 {
        out.extend_from_slice(&data[..expected]);
    } else if minbits != 0 {
        let mut writer = BitWriter::new(out);
        let parms = Parms {
            size,
            minbits,
            order,
        };
        for idx in 0..nelmts {
            let data_offset = idx
                .checked_mul(size)
                .ok_or_else(|| Error::InvalidFormat("scaleoffset packed offset overflow".into()))?;
            scaleoffset_compress_one_atomic(&packed, data_offset, &mut writer, parms)?;
        }
        writer.finish();
    }
    Ok(())
}

pub fn scaleoffset_precompress_i_into(
    data: &[u8],
    size: usize,
    sign: u32,
    order: u32,
    packed: &mut Vec<u8>,
) -> Result<(usize, u128)> {
    validate_scaleoffset_parameters(CLS_INTEGER, sign, size, 0)?;
    if size == 0 || data.len() % size != 0 {
        return Err(Error::InvalidFormat(
            "scaleoffset integer input size mismatch".into(),
        ));
    }
    if data.is_empty() {
        return Ok((0, 0));
    }
    let minval = data
        .chunks_exact(size)
        .map(|chunk| read_uint(chunk, order))
        .min()
        .unwrap_or(0);
    let max_delta = data
        .chunks_exact(size)
        .map(|chunk| read_uint(chunk, order).wrapping_sub(minval))
        .max()
        .unwrap_or(0);
    let minbits = if max_delta == 0 {
        0
    } else {
        u64::try_from(max_delta)
            .ok()
            .and_then(scaleoffset_log2)
            .and_then(|bits| usize::try_from(bits).ok())
            .and_then(|bits| bits.checked_add(1))
            .unwrap_or(size * 8)
            .min(size * 8)
    };
    let start = packed.len();
    packed.resize(
        start
            .checked_add(data.len())
            .ok_or_else(|| Error::InvalidFormat("scaleoffset packed size overflow".into()))?,
        0,
    );
    for (idx, chunk) in data.chunks_exact(size).enumerate() {
        let offset =
            start
                .checked_add(idx.checked_mul(size).ok_or_else(|| {
                    Error::InvalidFormat("scaleoffset packed offset overflow".into())
                })?)
                .ok_or_else(|| Error::InvalidFormat("scaleoffset packed offset overflow".into()))?;
        let value = read_uint(chunk, order);
        write_uint(
            checked_window_mut(packed, offset, size, "scaleoffset packed integer value")?,
            order,
            value.wrapping_sub(minval),
        );
    }
    Ok((minbits, minval))
}

pub fn scaleoffset_precompress_fd_into(
    data: &[u8],
    size: usize,
    order: u32,
    client_data: &[u32],
    packed: &mut Vec<u8>,
) -> Result<(usize, u128)> {
    if !matches!(size, 4 | 8) || data.len() % size != 0 {
        return Err(Error::InvalidFormat(
            "scaleoffset floating-point input size mismatch".into(),
        ));
    }
    if data.is_empty() {
        return Ok((0, 0));
    }
    let scale = client_data
        .get(1)
        .copied()
        .ok_or_else(|| Error::InvalidFormat("scaleoffset missing scale factor".into()))?
        .to_ne_bytes();
    let scale = i32::from_ne_bytes(scale);
    let multiplier = 10f64.powi(scale);
    let min = data
        .chunks_exact(size)
        .map(|chunk| read_float(chunk, size, order))
        .fold(f64::INFINITY, f64::min);
    let max_delta = data
        .chunks_exact(size)
        .map(|chunk| {
            ((read_float(chunk, size, order) - min) * multiplier)
                .round()
                .max(0.0) as u128
        })
        .max()
        .unwrap_or(0);
    let minbits = if max_delta == 0 {
        0
    } else {
        usize::try_from(u128::BITS - max_delta.leading_zeros())
            .map_err(|_| Error::InvalidFormat("scaleoffset bit count overflow".into()))?
    }
    .min(size * 8);
    let start = packed.len();
    packed.resize(
        start
            .checked_add(data.len())
            .ok_or_else(|| Error::InvalidFormat("scaleoffset packed size overflow".into()))?,
        0,
    );
    for (idx, chunk) in data.chunks_exact(size).enumerate() {
        let delta = ((read_float(chunk, size, order) - min) * multiplier)
            .round()
            .max(0.0) as u128;
        let offset =
            start
                .checked_add(idx.checked_mul(size).ok_or_else(|| {
                    Error::InvalidFormat("scaleoffset packed offset overflow".into())
                })?)
                .ok_or_else(|| Error::InvalidFormat("scaleoffset packed offset overflow".into()))?;
        write_uint(
            checked_window_mut(packed, offset, size, "scaleoffset packed float value")?,
            order,
            delta,
        );
    }
    let minval = if size == 4 {
        (min as f32).to_bits() as u128
    } else {
        min.to_bits() as u128
    };
    Ok((minbits, minval))
}

fn read_float(chunk: &[u8], size: usize, order: u32) -> f64 {
    if size == 4 {
        let mut raw = [0u8; 4];
        raw.copy_from_slice(chunk);
        if order == ORDER_LE {
            f32::from_le_bytes(raw) as f64
        } else {
            f32::from_be_bytes(raw) as f64
        }
    } else {
        let mut raw = [0u8; 8];
        raw.copy_from_slice(chunk);
        if order == ORDER_LE {
            f64::from_le_bytes(raw)
        } else {
            f64::from_be_bytes(raw)
        }
    }
}

fn validate_scaleoffset_datatype(
    class: u32,
    sign: u32,
    size: usize,
    scale_type: u32,
) -> Result<()> {
    match class {
        CLS_INTEGER => {
            if sign != SIGN_UNSIGNED && sign != SIGN_TWOS {
                return Err(Error::InvalidFormat(format!(
                    "invalid scaleoffset integer sign {sign}"
                )));
            }
        }
        CLS_FLOAT => {
            if !matches!(size, 4 | 8) {
                return Err(Error::Unsupported(format!(
                    "scaleoffset floating-point size {size}"
                )));
            }
            if scale_type != 0 {
                return Err(Error::Unsupported(format!(
                    "scaleoffset float scale type {scale_type}"
                )));
            }
        }
        other => {
            return Err(Error::Unsupported(format!(
                "scaleoffset datatype class {other}"
            )));
        }
    }
    Ok(())
}

fn validate_scaleoffset_parameters(
    class: u32,
    sign: u32,
    size: usize,
    minbits: usize,
) -> Result<()> {
    if minbits > size * 8 {
        return Err(Error::InvalidFormat(
            "invalid scaleoffset minimum bit count".into(),
        ));
    }
    if class == CLS_INTEGER && sign != SIGN_UNSIGNED && sign != SIGN_TWOS {
        return Err(Error::InvalidFormat(format!(
            "invalid scaleoffset integer sign {sign}"
        )));
    }
    Ok(())
}

fn decompress_atomic(
    out: &mut [u8],
    data_offset: usize,
    stream: &mut BitStream<'_>,
    parms: Parms,
) -> Result<()> {
    let dtype_bits = parms.size * 8;
    if parms.minbits == 0 || parms.minbits > dtype_bits {
        return Err(Error::InvalidFormat(
            "invalid scaleoffset minimum bit count".into(),
        ));
    }

    if parms.order == ORDER_LE {
        let begin = parms.size - 1 - (dtype_bits - parms.minbits) / 8;
        for k in (0..=begin).rev() {
            decompress_byte(out, data_offset, k, begin, stream, parms, dtype_bits)?;
        }
    } else {
        let begin = (dtype_bits - parms.minbits) / 8;
        for k in begin..parms.size {
            decompress_byte(out, data_offset, k, begin, stream, parms, dtype_bits)?;
        }
    }
    Ok(())
}

fn scaleoffset_compress_one_atomic(
    input: &[u8],
    data_offset: usize,
    writer: &mut BitWriter<'_>,
    parms: Parms,
) -> Result<()> {
    let dtype_bits = parms.size * 8;
    if parms.minbits == 0 || parms.minbits > dtype_bits {
        return Err(Error::InvalidFormat(
            "invalid scaleoffset minimum bit count".into(),
        ));
    }
    if parms.order == ORDER_LE {
        let begin = parms.size - 1 - (dtype_bits - parms.minbits) / 8;
        for k in (0..=begin).rev() {
            scaleoffset_compress_one_byte(input, data_offset, k, begin, writer, parms, dtype_bits)?;
        }
    } else {
        let begin = (dtype_bits - parms.minbits) / 8;
        for k in begin..parms.size {
            scaleoffset_compress_one_byte(input, data_offset, k, begin, writer, parms, dtype_bits)?;
        }
    }
    Ok(())
}

fn decompress_byte(
    out: &mut [u8],
    data_offset: usize,
    k: usize,
    begin: usize,
    stream: &mut BitStream<'_>,
    parms: Parms,
    dtype_bits: usize,
) -> Result<()> {
    let bits_to_copy = if k == begin {
        8 - (dtype_bits - parms.minbits) % 8
    } else {
        8
    };
    let bits = stream.read_bits(bits_to_copy)? as u8;
    let out_idx = data_offset
        .checked_add(k)
        .ok_or_else(|| Error::InvalidFormat("scaleoffset output offset overflow".into()))?;
    if out_idx >= out.len() {
        return Err(Error::InvalidFormat(
            "scaleoffset output offset out of range".into(),
        ));
    }
    out[out_idx] = bits;
    Ok(())
}

fn scaleoffset_compress_one_byte(
    input: &[u8],
    data_offset: usize,
    k: usize,
    begin: usize,
    writer: &mut BitWriter<'_>,
    parms: Parms,
    dtype_bits: usize,
) -> Result<()> {
    let bits_to_copy = if k == begin {
        8 - (dtype_bits - parms.minbits) % 8
    } else {
        8
    };
    let idx = data_offset
        .checked_add(k)
        .ok_or_else(|| Error::InvalidFormat("scaleoffset input offset overflow".into()))?;
    let byte = *input
        .get(idx)
        .ok_or_else(|| Error::InvalidFormat("scaleoffset input offset out of range".into()))?;
    let mask = if bits_to_copy == 8 {
        0xff
    } else {
        ((1u16 << bits_to_copy) - 1) as u8
    };
    writer.write_bits(u16::from(byte & mask), bits_to_copy)
}

fn postprocess_integer(
    out: &mut [u8],
    size: usize,
    sign: u32,
    order: u32,
    minbits: usize,
    minval: u128,
    fill: Option<u128>,
) -> Result<()> {
    let fill_marker = if minbits > 0 && minbits < 128 {
        Some((1u128 << minbits) - 1)
    } else if minbits == 128 {
        Some(u128::MAX)
    } else {
        None
    };

    for chunk in out.chunks_exact_mut(size) {
        let value = read_uint(chunk, order);
        let value = if let (Some(fill), Some(marker)) = (fill, fill_marker) {
            if value == marker {
                fill
            } else {
                minval.wrapping_add(value)
            }
        } else if minbits == 0 {
            minval
        } else {
            minval.wrapping_add(value)
        };
        write_uint(chunk, order, value);

        debug_assert!(sign == SIGN_UNSIGNED || sign == SIGN_TWOS);
    }
    Ok(())
}

fn postprocess_float(
    out: &mut [u8],
    size: usize,
    order: u32,
    minbits: usize,
    minval: u128,
    client_data: &[u32],
) -> Result<()> {
    let scale = client_data
        .get(1)
        .copied()
        .ok_or_else(|| Error::InvalidFormat("scaleoffset missing scale factor".into()))?
        .to_ne_bytes();
    let scale = i32::from_ne_bytes(scale);
    let divisor = 10f64.powi(scale);
    let marker = if minbits > 0 && minbits < 128 {
        Some((1u128 << minbits) - 1)
    } else {
        None
    };
    let fill = if client_data.get(PARM_FILAVAIL).copied().unwrap_or(0) != 0 {
        Some(read_fill_value(client_data, size, order))
    } else {
        None
    };

    match size {
        4 => {
            let min = f64::from(f32::from_le_bytes(low_u32(minval).to_le_bytes()));
            let fill = fill.map(|v| f32::from_le_bytes(low_u32(v).to_le_bytes()));
            for chunk in out.chunks_exact_mut(size) {
                let packed = read_uint(chunk, order);
                let value = if let (Some(marker), Some(fill)) = (marker, fill) {
                    if packed == marker {
                        fill
                    } else {
                        (signed_low_i64(packed) as f64 / divisor + min) as f32
                    }
                } else {
                    (signed_low_i64(packed) as f64 / divisor + min) as f32
                };
                write_float32(chunk, order, value);
            }
        }
        8 => {
            let min = f64::from_le_bytes(low_u64(minval).to_le_bytes());
            let fill = fill.map(|v| f64::from_le_bytes(low_u64(v).to_le_bytes()));
            for chunk in out.chunks_exact_mut(size) {
                let packed = read_uint(chunk, order);
                let value = if let (Some(marker), Some(fill)) = (marker, fill) {
                    if packed == marker {
                        fill
                    } else {
                        signed_low_i64(packed) as f64 / divisor + min
                    }
                } else {
                    signed_low_i64(packed) as f64 / divisor + min
                };
                write_float64(chunk, order, value);
            }
        }
        _ => {
            return Err(Error::Unsupported(format!(
                "scaleoffset floating-point size {size}"
            )));
        }
    }

    Ok(())
}

fn read_uint(bytes: &[u8], order: u32) -> u128 {
    let mut value = 0u128;
    if order == ORDER_LE {
        for (idx, byte) in bytes.iter().take(16).enumerate() {
            value |= u128::from(*byte) << (idx * 8);
        }
    } else {
        for byte in bytes.iter().take(16) {
            value = (value << 8) | u128::from(*byte);
        }
    }
    value
}

fn low_u32(value: u128) -> u32 {
    let bytes = value.to_le_bytes();
    u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}

fn low_u64(value: u128) -> u64 {
    let bytes = value.to_le_bytes();
    u64::from_le_bytes([
        bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
    ])
}

fn signed_low_i64(value: u128) -> i64 {
    i64::from_le_bytes(low_u64(value).to_le_bytes())
}

fn write_uint(bytes: &mut [u8], order: u32, value: u128) {
    if order == ORDER_LE {
        for (idx, byte) in bytes.iter_mut().take(16).enumerate() {
            *byte = (value >> (idx * 8)) as u8;
        }
    } else {
        let n = bytes.len().min(16);
        for (idx, byte) in bytes.iter_mut().take(n).enumerate() {
            *byte = (value >> ((n - idx - 1) * 8)) as u8;
        }
    }
}

fn read_le_u128(bytes: &[u8]) -> u128 {
    let mut value = 0u128;
    for (idx, byte) in bytes.iter().take(16).enumerate() {
        value |= u128::from(*byte) << (idx * 8);
    }
    value
}

fn read_u32_le_at(data: &[u8], offset: usize, context: &str) -> Result<u32> {
    let bytes = checked_window(data, offset, 4, context)?;
    Ok(u32::from_le_bytes(bytes.try_into().map_err(|_| {
        Error::InvalidFormat(format!("{context} is truncated"))
    })?))
}

fn scaleoffset_usize(value: u32, context: &'static str) -> Result<usize> {
    usize::try_from(value)
        .map_err(|_| Error::InvalidFormat(format!("{context} does not fit in usize")))
}

fn checked_window<'a>(
    data: &'a [u8],
    offset: usize,
    len: usize,
    context: &str,
) -> Result<&'a [u8]> {
    let end = offset
        .checked_add(len)
        .ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))?;
    data.get(offset..end)
        .ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))
}

fn checked_window_mut<'a>(
    data: &'a mut [u8],
    offset: usize,
    len: usize,
    context: &str,
) -> Result<&'a mut [u8]> {
    let end = offset
        .checked_add(len)
        .ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))?;
    data.get_mut(offset..end)
        .ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))
}

fn read_fill_value(client_data: &[u32], size: usize, order: u32) -> u128 {
    let mut raw = [0u8; 16];
    let mut pos = 0usize;
    for value in client_data.iter().skip(PARM_FILVAL) {
        let bytes = value.to_le_bytes();
        for byte in bytes {
            if pos < size {
                raw[pos] = byte;
                pos += 1;
            }
        }
    }
    read_uint(&raw[..size], order)
}

fn write_float32(bytes: &mut [u8], order: u32, value: f32) {
    let raw = if order == ORDER_LE {
        value.to_le_bytes()
    } else {
        value.to_be_bytes()
    };
    bytes[..4].copy_from_slice(&raw);
}

fn write_float64(bytes: &mut [u8], order: u32, value: f64) {
    let raw = if order == ORDER_LE {
        value.to_le_bytes()
    } else {
        value.to_be_bytes()
    };
    bytes[..8].copy_from_slice(&raw);
}

struct BitStream<'a> {
    data: &'a [u8],
    byte: usize,
    bits_left: usize,
}

impl<'a> BitStream<'a> {
    fn new(data: &'a [u8]) -> Self {
        Self {
            data,
            byte: 0,
            bits_left: 8,
        }
    }

    fn read_bits(&mut self, mut nbits: usize) -> Result<u16> {
        if nbits > 16 {
            return Err(Error::InvalidFormat("scaleoffset bit run too long".into()));
        }

        let mut value = 0u16;
        while nbits > 0 {
            let byte = *self
                .data
                .get(self.byte)
                .ok_or_else(|| Error::InvalidFormat("scaleoffset data too short".into()))?;
            let take = self.bits_left.min(nbits);
            let shift = self.bits_left - take;
            let mask = if take == 8 {
                0xff
            } else {
                ((1u16 << take) - 1) as u8
            };
            value = (value << take) | u16::from((byte >> shift) & mask);
            self.bits_left -= take;
            nbits -= take;
            if self.bits_left == 0 {
                self.byte += 1;
                self.bits_left = 8;
            }
        }
        Ok(value)
    }
}

struct BitWriter<'a> {
    out: &'a mut Vec<u8>,
    current: u8,
    bits_used: usize,
}

impl<'a> BitWriter<'a> {
    fn new(out: &'a mut Vec<u8>) -> Self {
        Self {
            out,
            current: 0,
            bits_used: 0,
        }
    }

    fn next_byte(&mut self) {
        self.out.push(self.current);
        self.current = 0;
        self.bits_used = 0;
    }

    fn write_bits(&mut self, value: u16, mut nbits: usize) -> Result<()> {
        if nbits > 16 {
            return Err(Error::InvalidFormat("scaleoffset bit run too long".into()));
        }
        while nbits > 0 {
            let free = 8 - self.bits_used;
            let take = free.min(nbits);
            let shift = nbits - take;
            let mask = if take == 16 {
                u16::MAX
            } else {
                (1u16 << take) - 1
            };
            let bits = u8::try_from((value >> shift) & mask)
                .map_err(|_| Error::InvalidFormat("scaleoffset bit run exceeds byte".into()))?;
            self.current |= bits << (free - take);
            self.bits_used += take;
            nbits -= take;
            if self.bits_used == 8 {
                self.next_byte();
            }
        }
        Ok(())
    }

    fn finish(mut self) {
        if self.bits_used != 0 {
            self.next_byte();
        }
    }
}

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

    fn header(minbits: u32, minval_size: u8) -> Vec<u8> {
        let mut data = vec![0u8; HEADER_LEN];
        data[..4].copy_from_slice(&minbits.to_le_bytes());
        data[4] = minval_size;
        data
    }

    fn decompress_err(data: &[u8], params: &[u32]) -> Error {
        let mut out = Vec::new();
        decompress_into(data, params, &mut out).unwrap_err()
    }

    #[test]
    fn rejects_missing_client_data() {
        let err = decompress_err(&[], &[0, 0, 1]);
        assert!(
            err.to_string()
                .contains("scaleoffset filter missing datatype parameters"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn rejects_invalid_float_scale_type() {
        let params = vec![1, 2, 1, CLS_FLOAT, 4, SIGN_UNSIGNED, ORDER_LE];
        let err = decompress_err(&header(0, 0), &params);
        assert!(
            err.to_string().contains("scaleoffset float scale type 1"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn rejects_full_precision_output_size_mismatch() {
        let params = vec![2, 0, 2, CLS_INTEGER, 4, SIGN_UNSIGNED, ORDER_LE];
        let mut data = header(32, 0);
        data.extend_from_slice(&1u32.to_le_bytes());
        let err = decompress_err(&data, &params);
        assert!(
            err.to_string()
                .contains("scaleoffset full-precision data too short"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn rejects_invalid_integer_sign_even_for_empty_chunks() {
        let params = vec![2, 0, 0, CLS_INTEGER, 4, 99, ORDER_LE];
        let err = decompress_err(&header(0, 0), &params);
        assert!(
            err.to_string()
                .contains("invalid scaleoffset integer sign 99"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn rejects_unsupported_datatype_class_before_chunk_header() {
        let params = vec![2, 0, 0, 99, 4, SIGN_UNSIGNED, ORDER_LE];
        let err = decompress_err(&[], &params);
        assert!(
            err.to_string().contains("scaleoffset datatype class 99"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn rejects_unsupported_float_size_before_chunk_header() {
        let params = vec![0, 0, 0, CLS_FLOAT, 2, SIGN_UNSIGNED, ORDER_LE];
        let err = decompress_err(&[], &params);
        assert!(
            err.to_string()
                .contains("scaleoffset floating-point size 2"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn rejects_unsupported_float_scale_type_before_chunk_header() {
        let params = vec![1, 0, 0, CLS_FLOAT, 4, SIGN_UNSIGNED, ORDER_LE];
        let err = decompress_err(&[], &params);
        assert!(
            err.to_string().contains("scaleoffset float scale type 1"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn rejects_minbits_larger_than_datatype_even_for_empty_chunks() {
        let params = vec![2, 0, 0, CLS_INTEGER, 4, SIGN_UNSIGNED, ORDER_LE];
        let err = decompress_err(&header(33, 0), &params);
        assert!(
            err.to_string()
                .contains("invalid scaleoffset minimum bit count"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn rejects_datatype_sizes_beyond_internal_integer_width() {
        let params = vec![2, 0, 0, CLS_INTEGER, 17, SIGN_UNSIGNED, ORDER_LE];
        let err = decompress_err(&header(0, 0), &params);
        assert!(
            err.to_string()
                .contains("scaleoffset datatype size 17 exceeds 16-byte arithmetic support"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn decompress_byte_rejects_output_offset_overflow() {
        let parms = Parms {
            size: 1,
            minbits: 8,
            order: ORDER_LE,
        };
        let mut out = [0u8; 1];
        let mut stream = BitStream::new(&[0xff]);
        let err = decompress_byte(&mut out, usize::MAX, 1, 1, &mut stream, parms, 8).unwrap_err();
        assert!(
            err.to_string()
                .contains("scaleoffset output offset overflow"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn decompress_byte_rejects_output_offset_out_of_range() {
        let parms = Parms {
            size: 1,
            minbits: 8,
            order: ORDER_LE,
        };
        let mut out = [0u8; 1];
        let mut stream = BitStream::new(&[0xff]);
        let err = decompress_byte(&mut out, 1, 0, 0, &mut stream, parms, 8).unwrap_err();
        assert!(
            err.to_string()
                .contains("scaleoffset output offset out of range"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn checked_window_rejects_offset_overflow() {
        let err = checked_window(&[], usize::MAX, 1, "scaleoffset test window").unwrap_err();
        assert!(
            err.to_string()
                .contains("scaleoffset test window offset overflow"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn checked_window_mut_rejects_offset_overflow() {
        let mut data = [];
        let err = checked_window_mut(&mut data, usize::MAX, 1, "scaleoffset test mutable window")
            .unwrap_err();
        assert!(
            err.to_string()
                .contains("scaleoffset test mutable window offset overflow"),
            "unexpected error: {err}"
        );
    }

    #[test]
    fn scaleoffset_integer_compress_roundtrips() {
        let params = vec![2, 0, 4, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_LE];
        let input = [10u16, 11, 12, 16]
            .into_iter()
            .flat_map(u16::to_le_bytes)
            .collect::<Vec<_>>();
        let mut compressed = Vec::new();
        scaleoffset_compress_into(&input, &params, &mut compressed).unwrap();
        let mut decoded = Vec::new();
        decompress_into(&compressed, &params, &mut decoded).unwrap();
        assert_eq!(decoded, input);
    }
}
