use std::{
    fs::File,
    io::{BufWriter, Write, stderr, stdout},
};

use crate::{error::Error, xlsx::XlsxReader};

#[derive(Clone, Debug)]
pub struct XlsxToCsvLines {
    xlsx_reader: XlsxReader,
    current_row: u32,
    num_rows: u32,
    separator: String,
    end_of_line: String,
}

impl Iterator for XlsxToCsvLines {
    type Item = Result<String, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.current_row > self.num_rows {
            return None;
        }

        let mut row = self.xlsx_reader.get_row(self.current_row);
        match &self.xlsx_reader.args.replace_separator_by {
            Some(replacement) => {
                row = row
                    .iter()
                    .map(|v| v.replace(self.xlsx_reader.args.separator, replacement.as_str()))
                    .collect()
            }
            None => {
                if row
                    .iter()
                    .any(|v| v.contains(self.xlsx_reader.args.separator))
                {
                    return Some(Err(
                        "Some cells contains the separator char. Use a replacement for separator char inside cells.".into(),
                    ));
                }
            }
        }

        self.current_row += 1;

        let mut row = row.join(self.separator.as_str());
        match &self.xlsx_reader.args.replace_end_of_line_by {
            Some(replacement) => row = row.replace(self.end_of_line.as_str(), replacement),
            None => {
                if row.contains(self.end_of_line.as_str()) {
                    return Some(Err("Some cells contains the end of line char. Use a replacement for end of line char inside cells.".into()));
                }
            }
        }

        let row = row + self.end_of_line.as_str();

        Some(Ok(row))
    }
}

pub enum Output {
    File(String),
    Stdout,
    Stderr,
}

pub trait IntoOutput {
    fn into_output(self) -> Output;
}

impl IntoOutput for Output {
    fn into_output(self) -> Output {
        self
    }
}

impl IntoOutput for String {
    fn into_output(self) -> Output {
        Output::File(self)
    }
}

impl IntoOutput for &str {
    fn into_output(self) -> Output {
        Output::File(String::from(self))
    }
}

impl XlsxReader {
    pub fn to_csv_lines(mut self) -> Result<XlsxToCsvLines, Error> {
        self.finish()?;
        let num_rows = self.get_worksheet_dimensions().1;
        let end_of_line = self.args.end_of_line.clone();
        let separator = String::from(self.args.separator);
        Ok(XlsxToCsvLines {
            xlsx_reader: self,
            current_row: 0,
            num_rows,
            separator,
            end_of_line,
        })
    }

    pub fn to_csv(self, output: impl IntoOutput) -> Result<(), Error> {
        let output = output.into_output();
        let mut writer: Box<dyn Write> = match output {
            Output::File(filename) => Box::new(BufWriter::new(File::open(filename)?)),
            Output::Stdout => Box::new(BufWriter::new(stdout().lock())),
            Output::Stderr => Box::new(BufWriter::new(stderr().lock())),
        };

        for line in self.to_csv_lines()? {
            writer.write_all(line?.as_bytes())?;
        }

        Ok(())
    }
}
