use std::io::Read;
use std::sync::Mutex;

use libremetaverse_types::compat::ReadWrite;

use crate::Error;
use crate::rendering::EndianAwareBinaryReaderSourceFormat as SourceFormat;

/// A synchronized binary reader whose numeric methods honor the source byte order.
pub struct EndianAwareBinaryReader {
    stream: Mutex<Box<dyn ReadWrite + Send>>,
    format: SourceFormat,
}

impl EndianAwareBinaryReader {
    pub fn new_with_stream(stream: Box<dyn ReadWrite + Send>) -> Result<Self, Error> {
        Self::new_with_stream_source_format(stream, SourceFormat::LittleEndian)
    }

    pub fn new_with_stream_source_format(
        stream: Box<dyn ReadWrite + Send>,
        format: SourceFormat,
    ) -> Result<Self, Error> {
        Ok(Self {
            stream: Mutex::new(stream),
            format,
        })
    }

    fn read_exact<const N: usize>(&self) -> Result<[u8; N], Error> {
        let mut bytes = [0_u8; N];
        self.stream
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .read_exact(&mut bytes)
            .map_err(|_| Error::IndexOutOfRange)?;
        Ok(bytes)
    }

    pub fn read_double(&self) -> Result<f64, Error> {
        let bytes = self.read_exact()?;
        Ok(match self.format {
            SourceFormat::BigEndian => f64::from_be_bytes(bytes),
            SourceFormat::LittleEndian => f64::from_le_bytes(bytes),
        })
    }

    pub fn read_int16(&self) -> Result<i16, Error> {
        let bytes = self.read_exact()?;
        Ok(match self.format {
            SourceFormat::BigEndian => i16::from_be_bytes(bytes),
            SourceFormat::LittleEndian => i16::from_le_bytes(bytes),
        })
    }

    pub fn read_int32(&self) -> Result<i32, Error> {
        let bytes = self.read_exact()?;
        Ok(match self.format {
            SourceFormat::BigEndian => i32::from_be_bytes(bytes),
            SourceFormat::LittleEndian => i32::from_le_bytes(bytes),
        })
    }

    pub fn read_int64(&self) -> Result<i64, Error> {
        let bytes = self.read_exact()?;
        Ok(match self.format {
            SourceFormat::BigEndian => i64::from_be_bytes(bytes),
            SourceFormat::LittleEndian => i64::from_le_bytes(bytes),
        })
    }

    pub fn read_single(&self) -> Result<f32, Error> {
        let bytes = self.read_exact()?;
        Ok(match self.format {
            SourceFormat::BigEndian => f32::from_be_bytes(bytes),
            SourceFormat::LittleEndian => f32::from_le_bytes(bytes),
        })
    }

    pub fn read_u_int32(&self) -> Result<u32, Error> {
        let bytes = self.read_exact()?;
        Ok(match self.format {
            SourceFormat::BigEndian => u32::from_be_bytes(bytes),
            SourceFormat::LittleEndian => u32::from_le_bytes(bytes),
        })
    }

    pub fn read_string_with_method(&self) -> Result<String, Error> {
        let mut bytes = Vec::new();
        let mut stream = self
            .stream
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        loop {
            let mut byte = [0_u8; 1];
            stream
                .read_exact(&mut byte)
                .map_err(|_| Error::IndexOutOfRange)?;
            if byte[0] == 0 {
                break;
            }
            bytes.push(byte[0]);
        }
        Ok(String::from_utf8_lossy(&bytes).into_owned())
    }

    pub fn read_string_with_int32(&self, size: i32) -> Result<String, Error> {
        let size = usize::try_from(size).map_err(|_| Error::Argument)?;
        let mut bytes = vec![0_u8; size];
        let mut stream = self
            .stream
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        let mut read = 0;
        while read < size {
            let count = stream
                .read(&mut bytes[read..])
                .map_err(|_| Error::InvalidOperation)?;
            if count == 0 {
                break;
            }
            read += count;
        }
        bytes.truncate(read);
        Ok(String::from_utf8_lossy(&bytes).trim().to_owned())
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use super::*;

    #[test]
    fn reads_both_byte_orders_and_strings() {
        let mut big = Vec::new();
        big.extend_from_slice(&(-123_i16).to_be_bytes());
        big.extend_from_slice(&42.5_f32.to_be_bytes());
        big.extend_from_slice(b"hello\0 padded ");
        let reader = EndianAwareBinaryReader::new_with_stream_source_format(
            Box::new(Cursor::new(big)),
            SourceFormat::BigEndian,
        )
        .unwrap();
        assert_eq!(reader.read_int16().unwrap(), -123);
        assert_eq!(reader.read_single().unwrap(), 42.5);
        assert_eq!(reader.read_string_with_method().unwrap(), "hello");
        assert_eq!(reader.read_string_with_int32(8).unwrap(), "padded");

        let reader = EndianAwareBinaryReader::new_with_stream(Box::new(Cursor::new(
            0x1234_5678_u32.to_le_bytes().to_vec(),
        )))
        .unwrap();
        assert_eq!(reader.read_u_int32().unwrap(), 0x1234_5678);
    }

    #[test]
    fn reports_short_numeric_reads() {
        let reader =
            EndianAwareBinaryReader::new_with_stream(Box::new(Cursor::new(vec![1, 2]))).unwrap();
        assert_eq!(reader.read_int32(), Err(Error::IndexOutOfRange));
    }
}
