use clap::Parser;
use clap::ValueEnum;
use std::fmt;

#[derive(Clone, Debug, ValueEnum, Default)]
pub enum FillMergedCells {
    None,
    Horizontal,
    Vertical,
    #[default]
    Both,
}

impl fmt::Display for FillMergedCells {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FillMergedCells::None => write!(f, "none"),
            FillMergedCells::Horizontal => write!(f, "horizontal"),
            FillMergedCells::Vertical => write!(f, "vertical"),
            FillMergedCells::Both => write!(f, "both"),
        }
    }
}

#[derive(Clone, Debug, ValueEnum, Default)]
pub enum IncludeHidden {
    None,
    Rows,
    Columns,
    #[default]
    Both,
}

impl fmt::Display for IncludeHidden {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            IncludeHidden::None => write!(f, "none"),
            IncludeHidden::Rows => write!(f, "rows"),
            IncludeHidden::Columns => write!(f, "columns"),
            IncludeHidden::Both => write!(f, "both"),
        }
    }
}
#[derive(Clone, Debug, ValueEnum, Default)]
pub enum TrimSpaces {
    End,
    Start,
    Both,
    #[default]
    None,
}

impl fmt::Display for TrimSpaces {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                TrimSpaces::End => "end",
                TrimSpaces::Start => "start",
                TrimSpaces::Both => "both",
                TrimSpaces::None => "none",
            }
        )
    }
}

#[derive(Clone, Debug, ValueEnum, Default)]
pub enum NumberRows {
    AsIs,
    Sequential,
    #[default]
    None,
}

impl fmt::Display for NumberRows {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                NumberRows::AsIs => "as-is",
                NumberRows::Sequential => "sequential",
                NumberRows::None => "none",
            }
        )
    }
}

#[derive(Parser, Debug, Clone)]
pub(crate) struct RawArguments {
    /// Path to the xlsx file
    #[arg()]
    pub file: String,
    /// List all worksheets
    #[arg(short, long)]
    pub list_worksheets: bool,
    /// Separator
    #[arg(short, long, default_value_t = ';')]
    pub separator: char,
    /// Replace separator char in cells by
    #[arg(short, long)]
    pub replace_separator_by: Option<String>,
    /// include hidden lines to output
    #[arg(short, long, default_value_t=IncludeHidden::None)]
    pub include_hidden: IncludeHidden,
    /// If merged cells, fill horizontally, vertically, both, or none
    #[arg(short, long, default_value_t = FillMergedCells::None)]
    pub fill_merged_cells: FillMergedCells,
    /// Chosse worksheet
    #[arg(short, long, default_value_t = String::from("0"))]
    pub worksheet: String,
    /// Choose active worksheet at save time in excel
    #[arg(short, long, default_value_t = false)]
    pub active_worksheet: bool,
    /// Trim white spaces at end of cells
    #[arg(short, long, default_value_t = TrimSpaces::None)]
    pub trim: TrimSpaces,
    /// number the rows in first cell of each line
    #[arg(short, long, default_value_t = NumberRows::None)]
    pub number_rows: NumberRows,
    /// avoid nth first rows of xlsx file
    #[arg(short = 'k', long, default_value_t = 0)]
    pub skip_rows: u32,
    /// avoid empty lines
    #[arg(short, long, default_value_t = false)]
    pub avoid_empty_rows: bool,
    /// consider first line (after row skipped) as header line
    #[arg(short, long, default_value_t = false)]
    pub header: bool,
    /// change end of line character
    #[arg(short, long, default_value_t = String::from("\n"))]
    pub end_of_line: String,
    /// replacement for end of line character inside cells
    #[arg(short = 'R', long)]
    pub replace_end_of_line_by: Option<String>,
}

/// Arguments structure is used to parse shell arguments (using clap)
///
/// Argument is also implicitly used by the XlsxReader .with_...() methods.
#[derive(Debug, Clone)]
pub struct Arguments {
    /// Path to the xlsx file
    pub(crate) file: String,
    /// List all worksheets in the xlsx file
    pub(crate) list_worksheets: bool,
    /// Choose worksheet as name or number
    pub(crate) worksheet: String,
    /// Use the worksheet that was active when the file was last saved
    pub(crate) active_worksheet: bool,
    /// Trim white spaces at end of cells
    /// Include hidden lines to output
    // pub(crate) include_hidden: IncludeHidden,
    pub(crate) include_hidden_rows: bool,
    /// Include hidden columns to output
    pub(crate) include_hidden_columns: bool,
    /// If merged cells, fill horizontally
    // pub(crate) fill_merged_cells: FillMergedCells,
    pub(crate) fill_merged_cells_horizontal: bool,
    /// If merged cells, fill vertically
    pub(crate) fill_merged_cells_vertical: bool,
    /// trim spaces at end of file
    pub(crate) trim: TrimSpaces,
    /// number the rows in first cell of each line
    pub(crate) number_rows: NumberRows,
    /// Avoid nth first rows of xlsx file
    pub(crate) skip_rows: u32,
    /// avoid empty lines
    pub avoid_empty_rows: bool,
    /// first line (after row skipped) is header line
    pub header: bool,
    ///# csv output specific options
    /// Separator for output
    pub(crate) separator: char,
    /// Replace separator char in cells by another
    pub(crate) replace_separator_by: Option<String>,
    /// Set the end of line string, defaults to "\n"
    pub(crate) end_of_line: String,
    /// replacement for end of line string inside cells
    pub(crate) replace_end_of_line_by: Option<String>,
}

impl Arguments {
    pub fn parse() -> Self {
        let raw = RawArguments::parse();
        raw.into()
    }
}

impl Default for Arguments {
    fn default() -> Self {
        Self {
            file: Default::default(),
            list_worksheets: Default::default(),
            separator: ';',
            replace_separator_by: Default::default(),
            include_hidden_rows: Default::default(),
            include_hidden_columns: Default::default(),
            fill_merged_cells_horizontal: Default::default(),
            fill_merged_cells_vertical: Default::default(),
            worksheet: String::from("0"),
            active_worksheet: false,
            trim: Default::default(),
            number_rows: Default::default(),
            skip_rows: Default::default(),
            avoid_empty_rows: false,
            header: false,
            end_of_line: String::from("\n"),
            replace_end_of_line_by: Default::default(),
        }
    }
}

impl From<RawArguments> for Arguments {
    fn from(raw: RawArguments) -> Self {
        let (include_hidden_columns, include_hidden_rows) = match raw.include_hidden {
            IncludeHidden::None => (false, false),
            IncludeHidden::Rows => (false, true),
            IncludeHidden::Columns => (true, false),
            IncludeHidden::Both => (true, true),
        };

        let (fill_merged_cells_horizontal, fill_merged_cells_vertical) = match raw.fill_merged_cells
        {
            FillMergedCells::None => (false, false),
            FillMergedCells::Horizontal => (true, false),
            FillMergedCells::Vertical => (false, true),
            FillMergedCells::Both => (true, true),
        };

        Arguments {
            file: raw.file,
            list_worksheets: raw.list_worksheets,
            separator: raw.separator,
            replace_separator_by: raw.replace_separator_by,
            include_hidden_columns,
            include_hidden_rows,
            fill_merged_cells_horizontal,
            fill_merged_cells_vertical,
            worksheet: raw.worksheet,
            active_worksheet: raw.active_worksheet,
            trim: raw.trim,
            number_rows: raw.number_rows,
            skip_rows: raw.skip_rows,
            avoid_empty_rows: raw.avoid_empty_rows,
            header: raw.header,
            end_of_line: raw.end_of_line,
            replace_end_of_line_by: raw.replace_end_of_line_by,
        }
    }
}

pub trait IntoArgs {
    fn into_args(self) -> Arguments;
}

impl IntoArgs for String {
    fn into_args(self) -> Arguments {
        Arguments {
            file: self,
            ..Default::default()
        }
    }
}

impl IntoArgs for &str {
    fn into_args(self) -> Arguments {
        Arguments {
            file: self.to_string(),
            ..Default::default()
        }
    }
}

impl IntoArgs for Arguments {
    fn into_args(self) -> Arguments {
        self
    }
}
