use super::{Notification, Request};
use std::io::{BufRead, BufReader, Read};

pub(super) struct Reader<'a> {
    input: BufReader<&'a mut dyn Read>,
    temp_buffer: Vec<u8>,
    buffer: Vec<u8>,
}

#[derive(Clone, Debug, PartialEq)]
pub(super) enum BodyResult {
    Request(Request),
    Notification(Notification),
}

const HEADER_END: &[u8] = "\r\n\r\n".as_bytes();
const HEADER_END_LAST: u8 = 10;

impl<'a> Reader<'a> {
    pub(super) fn new(input: &'a mut dyn Read) -> Self {
        Self {
            input: BufReader::new(input),
            temp_buffer: Vec::with_capacity(1024),
            buffer: Vec::with_capacity(4096),
        }
    }

    fn next_body(&mut self) -> &str {
        let size = self.stop_for_size();
        self.buffer.resize(size, 0);
        self.input
            .read_exact(&mut self.buffer)
            .expect("Body not of the expected length");
        str::from_utf8(&self.buffer).expect("UTF-8 must be used")
    }

    fn stop_for_size(&mut self) -> usize {
        self.buffer.clear();
        while !self.buffer.ends_with(HEADER_END) {
            self.input
                .read_until(HEADER_END_LAST, &mut self.temp_buffer)
                .expect("No headers found");
            self.buffer.append(&mut self.temp_buffer);
        }
        let headers = str::from_utf8(&self.buffer);
        let mut headers_split = headers.expect("UTF-8 must be used").split("\r\n");
        loop {
            let mut split = headers_split
                .next()
                .expect("No header found for content-length")
                .split(':');
            let name = split.next().unwrap_or("").trim();
            let value = split.next().unwrap_or("").trim();
            if name.eq_ignore_ascii_case("content-length") {
                return value
                    .parse()
                    .unwrap_or_else(|_| panic!("Bad value for content length: {value}"));
            }
        }
    }
}

impl Iterator for Reader<'_> {
    type Item = BodyResult;

    fn next(&mut self) -> Option<Self::Item> {
        let body = self.next_body();
        if let Ok(request) = serde_json::from_str::<Request>(body) {
            return Some(BodyResult::Request(request));
        }
        if let Ok(notification) = serde_json::from_str::<Notification>(body) {
            return Some(BodyResult::Notification(notification));
        }
        None
    }
}

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

    #[test]
    fn test_request() {
        let request = Request::new(1, "initialize", Value::Null);
        let json = serde_json::to_string(&request).expect("Cannot serialize request");
        let msg = format!("Content-Length: {}\r\n\r\n{json}", json.len());
        let mut input = msg.as_bytes();
        let mut buf = Reader::new(&mut input);
        assert_eq!(buf.next().unwrap(), BodyResult::Request(request));
    }

    #[test]
    fn test_request_multiple() {
        let request = Request::new(1, "initialize", Value::Null);
        let json = serde_json::to_string(&request).expect("Cannot serialize request");
        let mut msg = format!("Content-Length: {}\r\n\r\n{json}", json.len());
        msg += format!("Content-Length: {}\r\n\r\n{json}", json.len()).as_str();
        let mut input = msg.as_bytes();
        let mut buf = Reader::new(&mut input);
        assert_eq!(buf.next().unwrap(), BodyResult::Request(request.clone()));
        assert_eq!(buf.next().unwrap(), BodyResult::Request(request));
    }
}
