Documentation
use std::path::Path;

use basic_xlsx_writer::CellValue;
use calamine::{open_workbook, DataType, Range, Reader, Xlsx};
use err_derive::Error;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

#[derive(Debug, Error)]
pub enum Error {
    #[error(display = "Calamine error: {}", _0)]
    Calamine(calamine::Error),
    #[error(display = "IO error: {}", _0)]
    IoError(#[error(cause)] std::io::Error),
    #[error(display = "RON error: {}", _0)]
    RonDeError(#[error(cause)] ron::de::Error),
    #[error(display = "RON error: {}", _0)]
    RonSerError(#[error(cause)] ron::ser::Error),
}

impl From<calamine::Error> for Error {
    fn from(e: calamine::Error) -> Self {
        Error::Calamine(e)
    }
}

impl From<calamine::XlsxError> for Error {
    fn from(e: calamine::XlsxError) -> Self {
        Error::Calamine(e.into())
    }
}

impl From<std::io::Error> for Error {
    fn from(e: std::io::Error) -> Self {
        Error::IoError(e)
    }
}

impl From<ron::de::Error> for Error {
    fn from(e: ron::de::Error) -> Self {
        Error::RonDeError(e)
    }
}

impl From<ron::ser::Error> for Error {
    fn from(e: ron::ser::Error) -> Self {
        Error::RonSerError(e)
    }
}

/// An enum to represent all different data types that can appear as
/// a value in a worksheet cell
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)]
pub enum Value {
    /// Unsigned integer
    Int(i64),
    /// Float
    Float(f64),
    /// String
    String(String),
    /// Boolean
    Bool(bool),
    /// Error
    Error,
    /// Empty cell
    Empty,
}

impl Value {
    pub fn is_empty(&self) -> bool {
        match self {
            Value::Empty => true,
            Value::String(s) if s.is_empty() => true,
            _ => false,
        }
    }

    pub fn to_string(&self) -> String {
        match self {
            Value::Int(x) => x.to_string(),
            Value::Float(f) => f.to_string(),
            Value::String(s) => s.clone(),
            Value::Bool(s) => s.to_string(),
            Value::Error => "Error".to_owned(),
            Value::Empty => "".to_owned(),
        }
    }
}

impl Into<CellValue> for Value {
    fn into(self: Value) -> CellValue {
        match self {
            Value::Int(i) => CellValue::Number(i as f64),
            Value::Float(f) => CellValue::Number(f),
            Value::Bool(b) => CellValue::Bool(b),
            Value::String(s) => CellValue::String(s),
            _ => CellValue::String("".to_owned()),
        }
    }
}

impl From<DataType> for Value {
    fn from(d: DataType) -> Self {
        match d {
            DataType::Int(i) => Value::Int(i),
            DataType::Float(f) => Value::Float(f),
            DataType::String(s) => Value::String(s),
            DataType::Bool(b) => Value::Bool(b),
            DataType::Error(_) => Value::Error,
            DataType::Empty => Value::Empty,
        }
    }
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct RonWorkbook {
    pub sheets: IndexMap<String, RonWorksheet>,
}

impl RonWorkbook {
    pub fn read_xlsx<P: AsRef<Path>>(file: P) -> Result<RonWorkbook, Error> {
        let mut workbook: Xlsx<_> = open_workbook(file)?;

        let mut ron_workbook = RonWorkbook {
            sheets: Default::default(),
        };

        let sheet_names = workbook.sheet_names().to_vec();
        for sheet in sheet_names {
            let worksheet: Range<DataType> =
                workbook.worksheet_range(&sheet).expect("This was Murphy")?;

            let mut ron_worksheet = RonWorksheet {
                rows: Default::default(),
            };

            for row in worksheet.rows() {
                let l = ron_worksheet.rows.len();
                ron_worksheet
                    .rows
                    .insert(l, row.iter().map(|x| x.clone().into()).collect());
            }

            ron_workbook.sheets.insert(sheet, ron_worksheet);
        }

        Ok(ron_workbook)
    }

    pub fn read_ron<P: AsRef<Path>>(file: P) -> Result<RonWorkbook, Error> {
        let s = std::fs::read_to_string(file)?;
        let workbook = ron::de::from_str(&s)?;

        Ok(workbook)
    }

    pub fn write_xlsx<P: AsRef<Path>>(
        &self,
        file: P,
        col_width: f32,
        skip_empty: bool,
    ) -> Result<(), Error> {
        use basic_xlsx_writer::*;

        let file = file.as_ref();

        let mut wb = Workbook::create(file)?;

        for (sheet_name, sheet) in self.sheets.iter() {
            let mut sheet_rows = sheet.rows.clone();
            sheet_rows.sort_keys();

            let num_rows = sheet_rows.values().map(|row| row.len()).max().unwrap_or(0);

            let mut xlsx_sheet = wb.create_sheet(sheet_name);

            for _ in 0..num_rows {
                xlsx_sheet.add_column(Column { width: col_width });
            }

            wb.write_sheet(&mut xlsx_sheet, |sheet_writer| {
                let sw = sheet_writer;

                let mut last_row_number = 0;
                for (row_number, row) in sheet_rows {
                    if !skip_empty && row_number > last_row_number + 1 {
                        sw.append_blank_rows(row_number - (last_row_number + 1));
                    }

                    sw.append_row(Row::from_iter(
                        row.into_iter().map(|v| Value::to_string(&v)),
                    ))?;
                    last_row_number = row_number;
                }

                Ok(())
            })?;
        }

        wb.close()?;

        Ok(())
    }

    pub fn retain_unique(&mut self, column: usize) {
        for (name, sheet) in &mut self.sheets {
            println!("Sheet {:?}", name);

            let dups = sheet.find_duplicates(column);
            for dup in dups {
                println!("Removing line {}...", dup + 1);
                sheet.rows.remove(&dup);
            }
        }
    }

    pub fn retain_intersecting(&mut self, compare: &RonWorkbook) {
        for (sheet, sheet_new) in self.sheets.values_mut().zip(compare.sheets.values()) {
            let mut remove_rows: Vec<usize> = vec![];

            for (i, value) in sheet.rows.iter() {
                if !sheet_new.rows.values().any(|val| val[0] == value[0]) {
                    remove_rows.push(*i);
                }
            }

            for row in remove_rows {
                sheet.rows.remove(&row);
            }
            sheet.rows.sort_keys();
        }
    }
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct RonWorksheet {
    pub rows: IndexMap<usize, Vec<Value>>,
}

impl RonWorksheet {
    pub fn find_duplicates(&self, column: usize) -> Vec<usize> {
        let mut encountered = IndexMap::new();
        let mut duplicates = vec![];

        for (index, value) in self.rows.iter() {
            let value = value.get(column).unwrap_or(&Value::Empty).to_string();

            if value.is_empty() {
                continue;
            }

            if encountered.get(&value).is_some() {
                println!(
                    "Found duplicate \"{}\" on line {} (first encounter: {})",
                    value,
                    index + 1,
                    encountered[&value] + 1
                );
                duplicates.push(*index);
            } else {
                encountered.insert(value, *index);
            }
        }

        duplicates
    }
}