Documentation
#![allow(unused_variables)]

use tina::{
    indexmap::IndexMap,
    serde_json::{Number, Value},
    tina::{
        core::service::dict::IDictService,
        data::{excel::ExcelFileData, AppResult},
        excel::{IExcelRow, IExcelSheet},
        file::FileData,
        server::{
            application::{AppConfig, Application, FromApplication},
            session::Session,
        },
        util::excel::ExcelUtil,
    },
};

#[macro_use]
extern crate tina;

#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Debug)]
#[serde(crate = "tina::serde")]
struct RowData {
    name: Option<String>,
    age: Option<i32>,
    status: Option<String>,
    up: Option<bool>,
}

impl RowData {
    fn new(name: impl Into<String>, age: i32, status: impl Into<String>, up: bool) -> Self {
        Self {
            name: Some(name.into()),
            age: Some(age),
            status: Some(status.into()),
            up: Some(up),
        }
    }
}

#[async_trait]
impl IExcelSheet for RowData {
    /// 获取 Sheet 名称
    async fn get_sheet_name(&self, session: &Session) -> AppResult<String> {
        Ok("测试".to_string())
    }
}

#[async_trait]
impl IExcelRow for RowData {
    /// 获取 Sheet列 与 属性的 映射
    async fn get_field_column_mapping(&self, session: &Session) -> AppResult<IndexMap<String, String>> {
        Ok(vec![("name", "姓名"), ("age", "年龄"), ("status", "状态"), ("up", "标志")]
            .into_iter()
            .map(|(k, v)| (k.to_string(), v.to_string()))
            .collect())
    }
    /// 获取 Sheet列 的 字典映射
    async fn get_field_dict(&self, session: &Session) -> AppResult<IndexMap<String, String>> {
        Ok(vec![("status", "status_dict")].into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect())
    }
    /// 获取 Sheet列 的 枚举映射
    async fn get_field_enum_mapping(&self, session: &Session) -> AppResult<IndexMap<String, IndexMap<String, String>>> {
        Ok(vec![("up", vec![("true", ""), ("false", "")])]
            .into_iter()
            .map(|(k, v)| {
                (k.to_string(), v.into_iter().map(|(k1, v1)| (k1.to_string(), v1.to_string())).collect::<IndexMap<String, String>>())
            })
            .collect())
    }
    /// 获取 Sheet 数据
    async fn get_field_datas(&self, session: &Session) -> AppResult<IndexMap<String, Value>> {
        Ok(vec![
            ("name", self.name.as_ref().map(|v| Value::String(v.to_string())).unwrap_or(Value::Null)),
            ("age", self.age.map(|v| Value::Number(Number::from(v))).unwrap_or(Value::Null)),
            ("status", self.status.as_ref().map(|v| Value::String(v.to_string())).unwrap_or(Value::Null)),
            ("up", self.up.map(Value::Bool).unwrap_or(Value::Null)),
        ]
        .into_iter()
        .map(|(k, v)| (k.to_string(), v))
        .collect())
    }
}

struct TestDictService;
#[async_trait]
impl IDictService for TestDictService {
    /// 根据字典类型查询字典数据
    async fn get_dict_data_mapping_by_keys(
        &self,
        session: &Session,
        dict_type: &str,
        dict_keys: &[&str],
    ) -> AppResult<IndexMap<String, String>> {
        self.get_dict_data_mapping_by_types(session, vec![dict_type].as_slice())
            .await
            .map(|v| v.into_iter().next().map(|v1| v1.1).unwrap_or_default())
    }
    /// 根据字典类型查询字典数据
    async fn get_dict_data_mapping_by_types(
        &self,
        session: &Session,
        dict_types: &[&str],
    ) -> AppResult<IndexMap<String, IndexMap<String, String>>> {
        Ok(dict_types
            .iter()
            .map(|&dict_type| match dict_type {
                "status_dict" => (
                    dict_type.to_string(),
                    vec![("0", "正常"), ("1", "禁用")]
                        .into_iter()
                        .map(|(k, v)| (k.to_string(), v.to_string()))
                        .collect::<IndexMap<String, String>>(),
                ),
                _ => (dict_type.to_string(), IndexMap::default()),
            })
            .collect())
    }
}

#[test]
fn write() -> Result<(), Box<dyn std::error::Error>> {
    let path = format!("{}/{}", ".", "test.xlsx");
    let mut excel = ExcelFileData::new(path, true);
    let handle = excel.create_write_handle()?;
    let header_format = excel.generate_header_format(&handle)?;
    let bool_format = excel.generate_bool_format(&handle)?;
    let number_format = excel.generate_number_format(&handle)?;
    let string_format = excel.generate_simple_string_format(&handle)?;
    excel.add_header(
        &handle,
        &"abc".to_string(),
        &vec!["姓名", "年龄", "升级"],
        Some(&header_format),
        Some(&header_format),
        Some(&header_format),
    )?;
    for i in 1..=100 {
        excel.add_data(
            &handle,
            &"abc".to_string(),
            &RowData {
                name: Some("小生".to_string()),
                age: Some(i),
                up: Some(i % 3 == 0),
                status: Some("1".to_string()),
            },
            Some(&bool_format),
            Some(&number_format),
            Some(&string_format),
        )?;
    }
    excel.flush_and_close(handle)?;
    Ok(())
}

#[test]
fn export_import() {
    let rt = tina::tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("build runtime failed");
    rt.block_on(async move {
        let mut config = AppConfig::default();
        config.set_dict_service(TestDictService);
        let application = Application::from(config);
        let session = Session::from_application(application, false)?;

        {
            let data = vec![RowData::new("张三", 20, "0", true), RowData::new("李四", 12, "1", false), RowData::new("王五", 18, "2", true)];

            let mut data1 = data.clone();
            data1.reverse();
            let file = ExcelUtil::write_excel(&session, "", false, move || {
                let d = data1.pop();
                async move { Ok(d) as AppResult<Option<RowData>> }
            })
            .await?;

            let data2 = ExcelUtil::import::<RowData>(&session, FileData::from_local_file(file.store_path.as_str(), true)?).await?;
            assert_ne!(data, data2);
        }
        {
            let data = vec![RowData::new("张三", 20, "0", true), RowData::new("李四", 12, "1", false)];

            let mut data1 = data.clone();
            data1.reverse();
            let file = ExcelUtil::write_excel(&session, "", false, move || {
                let d = data1.pop();
                async move { Ok(d) as AppResult<Option<RowData>> }
            })
            .await?;

            let data2 = ExcelUtil::import::<RowData>(&session, FileData::from_local_file(file.store_path.as_str(), true)?).await?;
            assert_eq!(data, data2);
        }

        Ok(()) as AppResult<()>
    })
    .expect("execute failed");
}

#[test]
#[ignore]
#[allow(unreachable_code)]
fn export_import_loop() {
    let rt = tina::tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("build runtime failed");
    rt.block_on(async move {
        let mut config = AppConfig::default();
        config.set_dict_service(TestDictService);
        let application = Application::from(config);
        let session = Session::from_application(application, false)?;

        loop {
            let data = vec![RowData::new("张三", 20, "0", true); 10000];

            let mut data1 = data.clone();
            data1.reverse();
            let file = ExcelUtil::write_excel(&session, "", false, move || {
                let d = data1.pop();
                async move { Ok(d) as AppResult<Option<RowData>> }
            })
            .await?;

            let data2 = ExcelUtil::import::<RowData>(&session, FileData::from_local_file(file.store_path.as_str(), true)?).await?;
            assert_eq!(data, data2);
        }

        Ok(()) as AppResult<()>
    })
    .expect("execute failed");
}