use std::io::Write;

use crate::chopper::error::ChopperResult;
use crate::chopper::sink::Sink;
use crate::chopper::types::{FieldValue, Header, Row};
use crate::util::timestamp_units::TimestampUnits;
use crate::write::csv_output_config::{CSVOutputConfig, TimestampStyle};
use crate::write::util::{check_header, check_row};

pub struct CSVSink<W: 'static + Write> {
    writer: W,
    csv_output_config: CSVOutputConfig,
    field_count: usize,
}

impl<W: 'static + Write> CSVSink<W> {
    pub fn new(
        header: &Header,
        writer: W,
        csv_output_config: CSVOutputConfig,
    ) -> ChopperResult<Self> {
        let mut sink = CSVSink {
            writer,
            csv_output_config,
            field_count: 0,
        };

        sink.write_csv_header(header)?;

        Ok(sink)
    }

    fn write_csv_header(&mut self, header: &Header) -> ChopperResult<()> {
        self.field_count = check_header(header)?;

        let writer = &mut self.writer;
        let field_name = header.field_names().clone();
        let mut first = true;

        if self.csv_output_config.print_time_col() {
            write!(writer, "{},", self.csv_output_config.time_col_name())?;
        }
        for name in field_name {
            if first {
                write!(writer, "{}", name)?;
                first = false;
            } else {
                write!(writer, ",{}", name)?;
            }
        }
        write!(writer, "\n")?;
        Ok(())
    }

    pub fn inner(self) -> W {
        self.writer
    }
}

impl<W: 'static + Write + Send> Sink for CSVSink<W> {
    fn write(&mut self, row: Box<Row>) -> ChopperResult<()> {
        check_row(&row, self.field_count)?;

        let mut first_col = true;
        if self.csv_output_config.print_time_col() {
            let time = match self.csv_output_config.time_col_style() {
                TimestampStyle::Epoch => {
                    let time = match self.csv_output_config.time_col_units() {
                        TimestampUnits::Seconds => row.timestamp / 1_000_000_000,
                        TimestampUnits::Millis => row.timestamp / 1_000_000,
                        TimestampUnits::Micros => row.timestamp / 1_000,
                        TimestampUnits::Nanos => row.timestamp,
                    };
                    time.to_string()
                }
                TimestampStyle::HumanReadable => {
                    let format = match self.csv_output_config.time_col_units() {
                        TimestampUnits::Seconds => "%Y-%m-%dT%H:%M:%S%:z",
                        TimestampUnits::Millis => "%Y-%m-%dT%H:%M:%S%.3f%:z",
                        TimestampUnits::Micros => "%Y-%m-%dT%H:%M:%S%.6f%:z",
                        TimestampUnits::Nanos => "%Y-%m-%dT%H:%M:%S%.9f%:z",
                    };
                    let time = self.csv_output_config.timezone().timestamp(row.timestamp)?;
                    time.format(format).to_string()
                }
            };

            write!(self.writer, "{}", time)?;
            first_col = false;
        }
        let field_values = &row.field_values;
        let delimiter = self.csv_output_config.delimiter();
        for value in field_values {
            if first_col {
                first_col = false;
            } else {
                write!(self.writer, "{}", delimiter)?;
            }

            match value {
                FieldValue::Boolean(x) => {
                    write!(self.writer, "{}", if *x { "true" } else { "false" })?
                }
                FieldValue::Byte(x) => write!(self.writer, "{}", x)?,
                FieldValue::ByteBuf(x) => write!(self.writer, "ByteBuf[len={}]", x.len())?,
                FieldValue::Char(x) => write!(self.writer, "{}", x)?,
                FieldValue::Double(x) => {
                    dtoa::write(&mut self.writer, *x)?;
                }
                FieldValue::Float(x) => {
                    dtoa::write(&mut self.writer, *x)?;
                }
                FieldValue::Int(x) => write!(self.writer, "{}", x)?,
                FieldValue::Long(x) => write!(self.writer, "{}", x)?,
                FieldValue::Short(x) => write!(self.writer, "{}", x)?,
                FieldValue::String(x) => write!(self.writer, "{}", x)?,
                FieldValue::MultiDimDoubleArray(x) => {
                    let dim_str = x
                        .shape()
                        .iter()
                        .map(|d| d.to_string())
                        .collect::<Vec<String>>()
                        .join("x");
                    write!(self.writer, "MultiDimDoubleArray[{}]", dim_str)?
                }
                FieldValue::None => (),
            };
        }
        write!(self.writer, "\n")?;
        Ok(())
    }

    fn flush(&mut self) -> ChopperResult<()> {
        self.writer.flush()?;
        Ok(())
    }
}
