#[derive(Debug, PartialEq)]
pub enum CSVParseResult {
    NeedMoreInput,
    NeedLargerOutputBuf,
    Field { line_end: bool },
    EOF,
}

pub struct CSVParser {
    eol: bool,
    delimiter: u8,
}

impl CSVParser {
    pub fn new(delimiter: u8) -> CSVParser {
        CSVParser {
            eol: false,
            delimiter,
        }
    }

    /// returns (result,input_bytes_read,output_bytes_written)
    pub fn read_field(
        &mut self,
        input: &[u8],
        output: &mut [u8],
        ignore_delimiters_until_eol: bool,
    ) -> (CSVParseResult, usize, usize) {
        if input.is_empty() {
            let result = match self.eol {
                false => {
                    self.eol = true;
                    CSVParseResult::Field { line_end: true }
                }
                true => CSVParseResult::EOF,
            };
            return (result, 0, 0);
        }
        self.eol = false;

        let mut input_bytes_read = 0;
        let mut output_bytes_written = 0;
        loop {
            if input_bytes_read >= input.len() {
                return (
                    CSVParseResult::NeedMoreInput,
                    input_bytes_read,
                    output_bytes_written,
                );
            }
            if output_bytes_written >= output.len() {
                return (
                    CSVParseResult::NeedLargerOutputBuf,
                    input_bytes_read,
                    output_bytes_written,
                );
            }

            let next = input[input_bytes_read];
            input_bytes_read += 1;

            if next == self.delimiter && !ignore_delimiters_until_eol {
                return (
                    CSVParseResult::Field { line_end: false },
                    input_bytes_read,
                    output_bytes_written,
                );
            }
            if next == b'\n' {
                self.eol = true;
                return (
                    CSVParseResult::Field { line_end: true },
                    input_bytes_read,
                    output_bytes_written,
                );
            }
            if next == b'\r' {
                // just silently ignore, not going to deal with insane files with '\r'
                continue;
            }

            output[output_bytes_written] = next;
            output_bytes_written += 1;
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::util::csv::parser::{CSVParseResult, CSVParser};

    #[test]
    fn test_simple() {
        let input = "abc,def".as_bytes();
        let mut output = vec![0; 1024];
        let mut parser = CSVParser::new(b',');
        let mut input_pos = 0;

        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::Field { line_end: false }, 4, 3)
        );
        assert_eq!(input[input_pos..input_pos + 3], output[0..3]);
        input_pos += 4;

        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::NeedMoreInput, 3, 3)
        );
        assert_eq!(input[input_pos..input_pos + 3], output[0..3]);
        input_pos += 3;

        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::Field { line_end: true }, 0, 0)
        );

        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::EOF, 0, 0)
        );
    }

    #[test]
    fn test_newline_end() {
        let input = "timestamp,a_bool,an_int\n101,true,7\n".as_bytes();
        let mut output = vec![0; 1024];
        let mut parser = CSVParser::new(b',');
        let mut input_pos = 0;

        // first line
        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::Field { line_end: false }, 10, 9)
        );
        assert_eq!(input[input_pos..input_pos + 9], output[0..9]);
        input_pos += 10;

        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::Field { line_end: false }, 7, 6)
        );
        assert_eq!(input[input_pos..input_pos + 6], output[0..6]);
        input_pos += 7;

        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::Field { line_end: true }, 7, 6)
        );
        assert_eq!(input[input_pos..input_pos + 6], output[0..6]);
        input_pos += 7;

        // second line
        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::Field { line_end: false }, 4, 3)
        );
        assert_eq!(input[input_pos..input_pos + 3], output[0..3]);
        input_pos += 4;

        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::Field { line_end: false }, 5, 4)
        );
        assert_eq!(input[input_pos..input_pos + 4], output[0..4]);
        input_pos += 5;

        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::Field { line_end: true }, 2, 1)
        );
        assert_eq!(input[input_pos..input_pos + 1], output[0..1]);
        input_pos += 2;

        // eof
        assert_eq!(
            parser.read_field(&input[input_pos..], &mut output, false),
            (CSVParseResult::EOF, 0, 0)
        );
    }
}
