use crate::{Error, Marker, WriteBuffer};

use super::write_unsigned;

/// Encode the most efficient representation of a signed value.
///
/// Note: if `value` is positive in the range [0, 128), it may be encoded as an unsigned value.
/// This behaviour might change in the future.
pub fn write_signed(buf: &mut WriteBuffer, value: isize) -> Result<Marker, Error> {
    if 0 <= value && value < 128 {
        // Exception: if the value can be encoded using a positive immediate, use that for efficiency
        write_unsigned(buf, value as _)
    } else if -32 <= value && value < 0 {
        let marker = Marker::ImmNeg { value: value as _ };
        buf.write(1, |buf| {
            // Safety: 1 byte available in buf.
            unsafe { *buf.as_mut_ptr() = marker.encoding() }
        })?;

        Ok(marker)
    } else if i8::MIN as isize <= value && value <= i8::MAX as isize {
        write_i8(buf, value as _)
    } else if i16::MIN as isize <= value && value <= i16::MAX as isize {
        write_i16(buf, value as _)
    } else if i32::MIN as isize <= value && value <= i32::MAX as isize {
        write_i32(buf, value as _)
    } else {
        write_i64(buf, value as _)
    }
}

/// Encode an 8-bit signed value.
pub fn write_i8(buf: &mut WriteBuffer, value: i8) -> Result<Marker, Error> {
    buf.write(2, |buf| {
        // Safety: 2 bytes available in buf.
        unsafe {
            let buf = buf.as_mut_ptr();
            *buf = Marker::I8.encoding();

            let buf = buf.add(1);
            *buf = *value.to_be_bytes().get_unchecked(0);
        }
    })?;

    Ok(Marker::I8)
}

/// Encode a 16-bit signed value.
pub fn write_i16(buf: &mut WriteBuffer, value: i16) -> Result<Marker, Error> {
    buf.write(3, |buf| {
        // Safety: 3 bytes available in buf.
        unsafe {
            let buf = buf.as_mut_ptr();
            *buf = Marker::I16.encoding();

            let buf = buf.add(1);
            buf.copy_from_nonoverlapping(i16::to_be_bytes(value).as_ptr(), 2);
        }
    })?;

    Ok(Marker::I16)
}

/// Encode a 32-bit signed value.
pub fn write_i32(buf: &mut WriteBuffer, value: i32) -> Result<Marker, Error> {
    buf.write(5, |buf| {
        // Safety: 5 bytes available in buf.
        unsafe {
            let buf = buf.as_mut_ptr();
            *buf = Marker::I32.encoding();

            let buf = buf.add(1);
            buf.copy_from_nonoverlapping(i32::to_be_bytes(value).as_ptr(), 4);
        }
    })?;

    Ok(Marker::I32)
}

/// Encode a 64-bit signed value.
pub fn write_i64(buf: &mut WriteBuffer, value: i64) -> Result<Marker, Error> {
    buf.write(9, |buf| {
        // Safety: 9 bytes available in buf.
        unsafe {
            let buf = buf.as_mut_ptr();
            *buf = Marker::I64.encoding();

            let buf = buf.add(1);
            buf.copy_from_nonoverlapping(i64::to_be_bytes(value).as_ptr(), 8);
        }
    })?;

    Ok(Marker::I64)
}
