use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io;
use std::io::Write;
use crate::frame::{AsBit, Frame};
use crate::frame::length::{LengthWritable, WireLength};
use crate::frame::mask::mask;
use crate::frame::wire::FrameWritable;

#[derive(Debug)]
pub enum FrameWriteError {
    IoError(io::Error),
    MaskEnabledButMissingKey
}
impl Display for FrameWriteError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::IoError(e) => write!(f, "io error: {}", e),
            Self::MaskEnabledButMissingKey => write!(f, "frame masking enabled but key not present")
        }
    }
}
impl Error for FrameWriteError {}
impl From<io::Error> for FrameWriteError {
    fn from(value: io::Error) -> Self {
        Self::IoError(value)
    }
}

impl<W: Write> FrameWritable for W {
    type Error = FrameWriteError;

    fn write_frame(&mut self, frame: &Frame) -> Result<(), Self::Error> {
        // build the flags and opcode byte
        let flags: u8 = (frame.fin.as_bit() << 7) | (frame.rsv1.as_bit() << 6) | (frame.rsv2.as_bit() << 5) | (frame.rsv3.as_bit() << 4);
        let opcode: u8 = (frame.opcode.clone()) as u8;
        let byte_0 = flags | opcode;

        trace!("wrote frame tinyheader, {:b}", byte_0);

        self.write_all(&[byte_0])?;

        // write the mask bit and length
        WireLength::from(frame.payload_len).write_length(frame.mask, self)?;

        trace!("wrote mask and length {}", frame.payload_len);

        let mut data = frame.payload_data.clone();

        trace!("data length: {}", data.len());
        trace!("data: {:x?}", data);

        if frame.mask {
            if let Some(key) = frame.masking_key {
                self.write_all(&key)?;

                mask(&mut data, key);
            } else {
                return Err(FrameWriteError::MaskEnabledButMissingKey); // TODO: we should do this earlier, before stuff is written to the socket
            }
        }

        self.write_all(&data)?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;
    use crate::frame::{Frame, Opcode};
    use crate::frame::wire::FrameWritable;

    #[test]
    fn encoding_6455_5_7_a() {
        let mut buf: Cursor<Vec<u8>> = Cursor::new(Vec::new());

        let frame = Frame {
            fin: true,
            rsv1: false,
            rsv2: false,
            rsv3: false,
            opcode: Opcode::Text,
            mask: false,
            payload_len: 5,
            masking_key: None,
            payload_data: "Hello".to_string().as_bytes().to_vec(),
        };

        buf.write_frame(&frame).unwrap();

        assert_eq!(buf.into_inner(), vec![0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]);
    }

    #[test]
    fn encoding_6455_5_7_b() {
        let mut buf: Cursor<Vec<u8>> = Cursor::new(Vec::new());

        let frame = Frame {
            fin: true,
            rsv1: false,
            rsv2: false,
            rsv3: false,
            opcode: Opcode::Text,
            mask: true,
            payload_len: 5,
            masking_key: Some([0x37, 0xfa, 0x21, 0x3d]),
            payload_data: "Hello".to_string().as_bytes().to_vec(),
        };

        buf.write_frame(&frame).unwrap();

        assert_eq!(buf.into_inner(), vec![0x81, 0x85, 0x37, 0xfa, 0x21, 0x3d, 0x7f, 0x9f, 0x4d, 0x51, 0x58]);
    }

    #[test]
    fn encoding_6455_5_7_e_simplified() {
        let mut buf: Cursor<Vec<u8>> = Cursor::new(Vec::new());

        let frame = Frame {
            fin: true,
            rsv1: false,
            rsv2: false,
            rsv3: false,
            opcode: Opcode::Text,
            mask: false,
            payload_len: 256,
            masking_key: None,
            payload_data: "Hello".to_string().as_bytes().to_vec(),
        };

        buf.write_frame(&frame).unwrap();

        assert_eq!(buf.into_inner(), vec![0x81, 0x7e, 0x01, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f]);
    }

    #[test]
    fn encoding_6455_5_7_f_simplified() {
        let mut buf: Cursor<Vec<u8>> = Cursor::new(Vec::new());

        let frame = Frame {
            fin: true,
            rsv1: false,
            rsv2: false,
            rsv3: false,
            opcode: Opcode::Text,
            mask: false,
            payload_len: 65536,
            masking_key: None,
            payload_data: "Hello".to_string().as_bytes().to_vec(),
        };

        buf.write_frame(&frame).unwrap();

        assert_eq!(buf.into_inner(), vec![0x81, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f]);
    }
}