use std::path::Path;
use umya_spreadsheet::{Range, Worksheet, reader};

use crate::arguments::{Arguments, TrimSpaces};
use crate::error::Error;

pub fn xlsxtocsv(args: &Arguments) -> Result<(), Error> {
    let book = reader::xlsx::read(Path::new(&args.file))
        .expect(format!("Can't open {}", args.file).as_str());

    // get the sheet from name or number if specified, else the first of the spreadsheet
    let sheet = match book.get_sheet_by_name(&args.worksheet) {
        Some(sheet) => sheet,
        None => {
            let sheetnum: u32 = match args.worksheet.parse() {
                Ok(sheetnum) => sheetnum,
                Err(_) => return Err(Error::new("cannot open sheet")),
            };
            let sheet = match book.get_sheet(&(sheetnum as usize)) {
                Some(sheet) => sheet,
                None => return Err(Error::new("cannot open sheet")),
            };
            sheet
        }
    };

    // set the merged cells policy
    let (horiz, vert) = match args.fill_merged_cells {
        crate::arguments::FillMergedCells::None => (false, false),
        crate::arguments::FillMergedCells::Horizontal => (true, false),
        crate::arguments::FillMergedCells::Vertical => (false, true),
        crate::arguments::FillMergedCells::Both => (true, true),
    };

    // get all the merged cells
    let merged_cells = MergedCells::new(sheet, horiz, vert);

    // get size of the worksheet
    let (num_cols, num_rows) = sheet.get_highest_column_and_row();

    // TODO get every hidden columns

    // for each row...
    for i in 1..=num_rows {

        // Avoid hidden rows if asked for
        if !args.include_hidden_lines {
            match sheet.get_row_dimension(&i) {
                Some(dim) => {
                    if *dim.get_hidden() {
                        continue;
                    }
                }
                None => continue,
            }
        }

        // for each column in row...
        let mut first = true;
        for j in 1..=num_cols {
            if first {
                first = false;
            } else {
                print!("{}", args.separator);
            }

            let cell = match sheet.get_cell((j, i)) {
                Some(cell) => cell,
                None => continue,
            };

            // get value from cell depending on merged cells and fill merged policy
            let cell_coordinate = cell.get_coordinate();
            let mut value;
            if let Some((col, row)) = merged_cells.in_merged_cell(
                *cell_coordinate.get_col_num(),
                *cell_coordinate.get_row_num(),
            ) {
                value = match sheet.get_cell((col, row)) {
                    Some(merged_cell) => merged_cell.get_formatted_value(),
                    None => String::from(""),
                }
            } else {
                value = cell.get_formatted_value();
            }

            // apply modifications to cells value (trim spaces, replace separator chars, line breaks etc.)
            value = match args.trim {
                TrimSpaces::End => String::from(value.trim_end()),
                TrimSpaces::Start => String::from(value.trim_start()),
                TrimSpaces::Both => String::from(value.trim()),
                TrimSpaces::None => value,
            };
            value = value.replace('\r', "").replace('\n', " ");
            if let Some(ref replacement) = args.replace_separator_by {
                value = value.replace(args.separator, replacement);
            } else {
                if value.contains(args.separator) {
                    return Err(Error::new(
                        format!(
                            "Cell {} contains separator char, use -r to choose a replacement char",
                            cell.get_coordinate().get_coordinate()
                        )
                        .as_str(),
                    ));
                }
            }
            print!("{}", value);
        }
        println!("");
    }

    Ok(())
}

struct MergedCells {
    merged_cells: Vec<Range>,
    fill_horizontal: bool,
    fill_vertical: bool,
}

impl MergedCells {
    pub fn new(sheet: &Worksheet, fill_horizontal: bool, fill_vertical: bool) -> Self {
        let merged = sheet.get_merge_cells();
        let mut merged_cells: Vec<Range> = vec![];

        for cell in merged {
            merged_cells.push(cell.clone());
        }

        MergedCells {
            merged_cells,
            fill_horizontal,
            fill_vertical,
        }
    }

    pub fn in_merged_cell(&self, col: u32, row: u32) -> Option<(u32, u32)> {
        for range in &self.merged_cells {
            if col >= *range.get_coordinate_start_col().unwrap().get_num()
                && col <= *range.get_coordinate_end_col().unwrap().get_num()
                && row >= *range.get_coordinate_start_row().unwrap().get_num()
                && row <= *range.get_coordinate_end_row().unwrap().get_num()
            {
                let col_start = range.get_coordinate_start_col().unwrap().get_num().clone();
                let row_start = range.get_coordinate_start_row().unwrap().get_num().clone();

                if self.fill_horizontal && self.fill_vertical
                    || self.fill_horizontal && row == row_start
                    || self.fill_vertical && col == col_start
                    || col == col_start && row == row_start
                {
                    return Some((col_start, row_start));
                }
            }
        }
        None
    }
}
