use csv;

use crate::config::{Config, Delimiter};
use crate::select::SelectColumns;
use crate::util;
use crate::CliResult;

static USAGE: &str = "
Fill empty cells of a CSV file by filling them with any non-empty value seen
before (this is usually called forward filling), or with any constant value
given to the -v, --value flag.

For instance, replacing empty values with 0 everywhere in the file:

    $ xan fill -v 0 data.csv > filled.csv

Usage:
    xan fill [options] [<input>]
    xan fill --help

fill options:
    -s, --select <cols>  Selection of columns to fill.
    -v, --value <value>  Fill empty cells using provided value instead of using
                         last non-empty value.

Common options:
    -h, --help             Display this message
    -o, --output <file>    Write output to <file> instead of stdout.
    -n, --no-headers       When set, the file will be considered as having no
                           headers.
    -d, --delimiter <arg>  The field delimiter for reading CSV data.
                           Must be a single character.
";

#[derive(Deserialize)]
struct Args {
    arg_input: Option<String>,
    flag_select: SelectColumns,
    flag_value: Option<String>,
    flag_no_headers: bool,
    flag_delimiter: Option<Delimiter>,
    flag_output: Option<String>,
}

pub fn run(argv: &[&str]) -> CliResult<()> {
    let args: Args = util::get_args(USAGE, argv)?;
    let rconf = Config::new(&args.arg_input)
        .delimiter(args.flag_delimiter)
        .no_headers(args.flag_no_headers)
        .select(args.flag_select);

    let mut rdr = rconf.reader()?;
    let mut wtr = Config::new(&args.flag_output).writer()?;

    let headers = rdr.byte_headers()?;

    let sel = rconf.selection(headers)?;
    let mask = sel.indexed_mask(headers.len());

    rconf.write_headers(&mut rdr, &mut wtr)?;

    let mut previous: Option<csv::ByteRecord> = None;

    for result in rdr.byte_records() {
        let record = result?;

        match previous.as_mut() {
            None => {
                wtr.write_byte_record(&record)?;
                previous = Some(record);
            }
            Some(previous_record) => {
                let filled_record = mask
                    .iter()
                    .enumerate()
                    .map(|(i, opt)| {
                        let current_cell = &record[i];

                        if opt.is_some() {
                            if !current_cell.is_empty() {
                                current_cell
                            } else if let Some(value) = &args.flag_value {
                                value.as_bytes()
                            } else {
                                &previous_record[i]
                            }
                        } else {
                            current_cell
                        }
                    })
                    .collect::<csv::ByteRecord>();

                wtr.write_byte_record(&filled_record)?;

                *previous_record = filled_record;
            }
        };
    }

    Ok(wtr.flush()?)
}