use std::io::{self, Read, Write};

use thiserror::Error;

use crate::quality_settings::QualitySettings;

#[derive(Clone, Copy)]
pub struct Header {
    pub bytes: [u8; 3],
    pub version: u8
}


#[derive(Error, Debug)]
pub enum ProtocolWriterError {
    #[error("error using underlying writer")]
    Io(#[from] io::Error)
}

pub type ProtocolWriterResult<T> = Result<T, ProtocolWriterError>;

pub struct ProtocolWriter<W: Write> {
    writer: W
}

#[derive(Error, Debug)]
pub enum ProtocolReaderError {
    #[error("wrong header")]
    WrongHeader,

    #[error("header has unsupported version")]
    WrongVersion,

    #[error("error using underlying reader")]
    Io(#[from] io::Error),
}

pub type ProtocolReaderResult<T> = Result<T, ProtocolReaderError>;

pub struct ProtocolReader<R: Read> {
    reader: R
}

impl<W: Write> ProtocolWriter<W> {
    pub fn new(writer: W) -> Self {
        Self { writer }
    }

    pub fn destroy(self) -> W {
        self.writer
    }

    pub fn write_header(&mut self, value: Header) -> ProtocolWriterResult<()> {
        self.writer.write_all(&value.bytes)?;
        self.writer.write_all(&[value.version])?;
        Ok(())
    }
    
    pub fn write_quality_settings(&mut self, quality: QualitySettings) -> ProtocolWriterResult<()> {
        self.writer.write_all(&quality.values)?;
        Ok(())
    }

    pub fn write_u32_wide(&mut self, value: u32) -> ProtocolWriterResult<()> {
        self.writer.write_all(&value.to_le_bytes())?;
        Ok(())
    }
    pub fn write_i16_wide(&mut self, value: i16) -> ProtocolWriterResult<()> {
        self.writer.write_all(&value.to_le_bytes())?;
        Ok(())
    }
    pub fn write_u8_wide(&mut self, value: u8) -> ProtocolWriterResult<()> {
        self.writer.write_all(&value.to_le_bytes())?;
        Ok(())
    }
    pub fn write_i16_packed(&mut self, value: i16) -> ProtocolWriterResult<()> {
        // We reserve i8::MAX and i16::MAX as signal values that `value` is too big
        // for each respective type
        if (i8::MIN as i16..i8::MAX as i16).contains(&value) {
            self.writer.write_all(&(value as i8).to_le_bytes())?;
            return Ok(())
        } 
        self.writer.write_all(&(i8::MAX).to_le_bytes())?;
        self.writer.write_all(&(value as i16).to_le_bytes())?;
        Ok(())
    }
}

impl<R: Read> ProtocolReader<R> {
    pub fn new(reader: R) -> Self {
        Self { reader }
    }

    pub fn read_header(&mut self) -> ProtocolReaderResult<Header> {
        let mut bytes_buf = [0; 3];
        self.reader.read_exact(&mut bytes_buf)?;

        let mut version_buf = [0; 1];
        self.reader.read_exact(&mut version_buf)?;

        Ok(Header {
            bytes: bytes_buf,
            version: version_buf[0],
        })
    }

    pub fn read_quality_settings(&mut self) -> ProtocolReaderResult<QualitySettings> {
        let mut qsettings_buf = [0; 8];
        self.reader.read_exact(&mut qsettings_buf)?;

        Ok(QualitySettings { values: qsettings_buf })
    }

    pub fn read_u32_wide(&mut self) -> ProtocolReaderResult<u32> {
        let mut u32_buf = [0; 4];
        self.reader.read_exact(&mut u32_buf)?;

        Ok(u32::from_le_bytes(u32_buf))
    }

    pub fn read_i16_wide(&mut self) -> ProtocolReaderResult<i16> {
        let mut i16_buf = [0; 2];
        self.reader.read_exact(&mut i16_buf)?;

        Ok(i16::from_le_bytes(i16_buf))
    }

    pub fn read_u8_wide(&mut self) -> ProtocolReaderResult<u8> {
        let mut u8_buf = [0; 1];
        self.reader.read_exact(&mut u8_buf)?;

        Ok(u8::from_le_bytes(u8_buf))
    }

    pub fn read_i16_packed(&mut self) -> ProtocolReaderResult<i16> {
        let mut i8_buf = [0; 1];
        self.reader.read_exact(&mut i8_buf)?;
        let i8_value = i8::from_le_bytes(i8_buf);
        if i8_value != i8::MAX { return Ok(i8_value as i16) }

        let mut i16_buf = [0; 2];
        self.reader.read_exact(&mut i16_buf)?;
        let i16_value = i16::from_le_bytes(i16_buf);
        Ok(i16_value as i16) 
    }
}