use std::io::BufRead;

use crate::chopper::error::Result;
use crate::util::csv::parser::{CSVParseResult, CSVParser};

const DEFAULT_BUF_SIZE: usize = 8 * 1024;

pub struct CSVReader<R: BufRead> {
    reader: R,
    parser: CSVParser,
    buf: Vec<u8>,
    min_columns: usize,
    max_columns: usize,
}

impl<R: BufRead> CSVReader<R> {
    pub fn new(reader: R) -> CSVReader<R> {
        Self::with_parser(reader, CSVParser::default())
    }

    pub fn with_parser(reader: R, parser: CSVParser) -> CSVReader<R> {
        CSVReader {
            reader,
            parser,
            buf: vec![0; DEFAULT_BUF_SIZE],
            min_columns: 0,
            max_columns: 0,
        }
    }

    pub fn set_min_columns(&mut self, min_columns: usize) {
        self.min_columns = min_columns;
    }

    pub fn set_max_columns(&mut self, max_columns: usize) {
        self.max_columns = max_columns;
    }

    /// returns Ok(None) if nothing else left to read
    pub fn read_line(&mut self) -> Result<Option<Vec<String>>> {
        let mut fields: Vec<String> = Vec::new();

        let mut pos = 0;
        loop {
            let input = self.reader.fill_buf()?;

            let ignore_delimiters_until_eol = fields.len() + 1 == self.max_columns;

            let cap = self.buf.len();
            let (result, bytes_read, bytes_written) =
                self.parser
                    .read_field(input, &mut self.buf[pos..cap], ignore_delimiters_until_eol);

            self.reader.consume(bytes_read);
            pos += bytes_written;

            match result {
                CSVParseResult::NeedMoreInput => continue,
                CSVParseResult::NeedLargerOutputBuf => {
                    self.buf.resize(cap * 2, 0);
                    continue;
                }
                CSVParseResult::Field { line_end } => {
                    let field = String::from_utf8_lossy(&self.buf[0..pos]);
                    fields.push(field.trim().to_string());

                    if line_end {
                        break;
                    }

                    pos = 0;
                    continue;
                }
                CSVParseResult::EOF => return Ok(None),
            }
        }

        while fields.len() < self.min_columns {
            fields.push(String::new());
        }

        Ok(Some(fields))
    }
}

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

    use vfile::util::buf::reader::BufReader;

    use crate::util::csv::reader::{CSVReader, DEFAULT_BUF_SIZE};

    #[test]
    fn test_small_input() {
        // note this also tests trim
        let test_bytes: &[u8] = "aaaaa\n  bbbbb  \nccccc".as_bytes();
        let buf_reader = BufReader::new(test_bytes);
        let mut csv_reader = CSVReader::new(buf_reader);

        assert_eq!(
            vec!["aaaaa".to_string()],
            csv_reader.read_line().unwrap().unwrap()
        );
        assert_eq!(
            vec!["bbbbb".to_string()],
            csv_reader.read_line().unwrap().unwrap()
        );
        assert_eq!(
            vec!["ccccc".to_string()],
            csv_reader.read_line().unwrap().unwrap()
        );
        assert!(csv_reader.read_line().unwrap().is_none());
    }

    #[test]
    fn test_big_input() {
        let mut vec_a = vec![b'a'; DEFAULT_BUF_SIZE * 2];
        let mut vec_b = vec![b'b'; DEFAULT_BUF_SIZE * 2];
        let mut vec_c = vec![b'c'; DEFAULT_BUF_SIZE * 2];
        let mut test_bytes: Vec<u8> = Vec::new();
        test_bytes.append(&mut vec_a);
        test_bytes.push(b'\n');
        test_bytes.append(&mut vec_b);
        test_bytes.push(b'\n');
        test_bytes.append(&mut vec_c);

        let expected_a: String = std::iter::repeat('a').take(DEFAULT_BUF_SIZE * 2).collect();
        let expected_b: String = std::iter::repeat('b').take(DEFAULT_BUF_SIZE * 2).collect();
        let expected_c: String = std::iter::repeat('c').take(DEFAULT_BUF_SIZE * 2).collect();

        let buf_reader = BufReader::new(Cursor::new(test_bytes));
        let mut csv_reader = CSVReader::new(buf_reader);

        assert_eq!(vec![expected_a], csv_reader.read_line().unwrap().unwrap());
        assert_eq!(vec![expected_b], csv_reader.read_line().unwrap().unwrap());
        assert_eq!(vec![expected_c], csv_reader.read_line().unwrap().unwrap());
        assert!(csv_reader.read_line().unwrap().is_none());
    }

    #[test]
    fn test_min_columns() {
        let test_bytes: &[u8] = "aaaaa".as_bytes();
        let buf_reader = BufReader::new(test_bytes);
        let mut csv_reader = CSVReader::new(buf_reader);
        csv_reader.set_min_columns(3);

        assert_eq!(
            vec!["aaaaa".to_string(), String::new(), String::new()],
            csv_reader.read_line().unwrap().unwrap()
        );
        assert!(csv_reader.read_line().unwrap().is_none());
    }

    #[test]
    fn test_max_columns() {
        let test_bytes: &[u8] = "aaaaa,bbbbb,ccccc,ddddd,eeeee".as_bytes();
        let buf_reader = BufReader::new(test_bytes);
        let mut csv_reader = CSVReader::new(buf_reader);
        csv_reader.set_max_columns(3);

        assert_eq!(
            vec![
                "aaaaa".to_string(),
                "bbbbb".to_string(),
                "ccccc,ddddd,eeeee".to_string()
            ],
            csv_reader.read_line().unwrap().unwrap()
        );
        assert!(csv_reader.read_line().unwrap().is_none());
    }
}
