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

pub struct CSVParser {
    eol: bool,
    in_quotes: bool,
    last_read: u8,
    delimiter: u8,
    parse_quotes: bool,
}

impl Default for CSVParser {
    fn default() -> Self {
        CSVParser::new(b',', false)
    }
}

impl CSVParser {
    pub fn new(delimiter: u8, parse_quotes: bool) -> CSVParser {
        CSVParser {
            eol: false,
            in_quotes: false,
            last_read: 0,
            delimiter,
            parse_quotes,
        }
    }

    /// 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 last = self.last_read;
            let next = input[input_bytes_read];
            self.last_read = next;
            input_bytes_read += 1;

            if next == b'\n' {
                self.eol = true;
                self.in_quotes = false;
                self.last_read = 0;
                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;
            }
            if next == self.delimiter && !self.in_quotes && !ignore_delimiters_until_eol {
                return (
                    CSVParseResult::Field { line_end: false },
                    input_bytes_read,
                    output_bytes_written,
                );
            }
            if self.parse_quotes && next == b'"' {
                if self.in_quotes {
                    self.in_quotes = false;
                    continue;
                } else {
                    self.in_quotes = true;
                    if last == b'"' {
                        // this is the case where the string looked like: "...""
                        // so second quote made in_quotes false, but actually it was just a quoted
                        // quote, so we need to go back to the in_quotes mode and emit the quote
                        // and we do that by falling through
                    } else {
                        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',', true);
        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',', true);
        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)
        );
    }

    #[test]
    fn test_quotes_1() {
        let input = "ab\"cd\"ef,".as_bytes();
        let mut output = vec![0; 1024];
        let mut parser = CSVParser::new(b',', true);

        assert_eq!(
            parser.read_field(input, &mut output, false),
            (CSVParseResult::Field { line_end: false }, 9, 6)
        );
        output.truncate(6);
        assert_eq!(String::from_utf8(output).unwrap(), "abcdef");
    }

    #[test]
    fn test_quotes_1_false() {
        let input = "ab\"cd\"ef,".as_bytes();
        let mut output = vec![0; 1024];
        let mut parser = CSVParser::new(b',', false);

        assert_eq!(
            parser.read_field(input, &mut output, false),
            (CSVParseResult::Field { line_end: false }, 9, 8)
        );
        output.truncate(8);
        assert_eq!(String::from_utf8(output).unwrap(), "ab\"cd\"ef");
    }

    #[test]
    fn test_quotes_2() {
        let input = "ab\"cd\"\"\"ef,".as_bytes();
        let mut output = vec![0; 1024];
        let mut parser = CSVParser::new(b',', true);

        assert_eq!(
            parser.read_field(input, &mut output, false),
            (CSVParseResult::Field { line_end: false }, 11, 7)
        );
        output.truncate(7);
        assert_eq!(String::from_utf8(output).unwrap(), "abcd\"ef");
    }

    #[test]
    fn test_quotes_3() {
        let input = "ab\"cdef\n".as_bytes();
        let mut parser = CSVParser::new(b',', true);

        let mut output = vec![0; 1024];
        assert_eq!(
            parser.read_field(input, &mut output, false),
            (CSVParseResult::Field { line_end: true }, 8, 6)
        );
        output.truncate(6);
        assert_eq!(String::from_utf8(output).unwrap(), "abcdef");

        let mut output = vec![0; 1024];
        assert_eq!(
            parser.read_field(input, &mut output, false),
            (CSVParseResult::Field { line_end: true }, 8, 6)
        );
        output.truncate(6);
        assert_eq!(String::from_utf8(output).unwrap(), "abcdef");
    }

    #[test]
    fn test_quotes_4() {
        let input = "ab\",cd\"\"\"ef,".as_bytes();
        let mut output = vec![0; 1024];
        let mut parser = CSVParser::new(b',', true);

        assert_eq!(
            parser.read_field(input, &mut output, false),
            (CSVParseResult::Field { line_end: false }, 12, 8)
        );
        output.truncate(8);
        assert_eq!(String::from_utf8(output).unwrap(), "ab,cd\"ef");
    }

    #[test]
    fn test_quotes_4_false() {
        let input = "ab\",cd\"\"\"ef,".as_bytes();
        let mut output = vec![0; 1024];
        let mut parser = CSVParser::new(b',', false);

        assert_eq!(
            parser.read_field(input, &mut output, false),
            (CSVParseResult::Field { line_end: false }, 4, 3)
        );
        output.truncate(3);
        assert_eq!(String::from_utf8(output).unwrap(), "ab\"");
    }
}
