use std::cmp::Ordering;
use chrono::{DateTime, Utc};
use clap::ValueEnum;
use crate::error::AppError;
use crate::io::storage::TaskStore;
use crate::task::{Priority, Task};
#[derive(ValueEnum, Clone)]
pub enum SortBy {
#[value(alias = "d")]
Deadline,
#[value(alias = "p")]
Priority,
}
pub struct TaskUpdate {
content: Option<String>,
description: Option<Option<String>>,
deadline: Option<Option<DateTime<Utc>>>, priority: Option<Option<Priority>>,
}
impl TaskUpdate {
pub fn new(
content: Option<String>,
description: Option<Option<String>>,
deadline: Option<Option<DateTime<Utc>>>,
priority: Option<Option<Priority>>,
) -> Self {
TaskUpdate {
content,
description,
deadline,
priority,
}
}
fn is_empty(&self) -> bool {
self.content.is_none()
&& self.description.is_none()
&& self.deadline.is_none()
&& self.priority.is_none()
}
pub fn change_task(self, no: usize, store: &TaskStore) -> Result<(), AppError> {
if self.is_empty() {
return Err(AppError::NothingToChange);
}
store.update(|tasks| {
if no == 0 || no > tasks.len() {
return Err(AppError::TaskNotFound { no });
}
let task = tasks.get_mut(no - 1).ok_or(AppError::TaskNotFound { no })?;
if let Some(c) = self.content {
task.set_content(c);
}
match self.deadline {
Some(Some(d)) => {
task.set_deadline(Some(d));
}
Some(None) => task.set_deadline(None),
None => {}
}
match self.description {
Some(Some(s)) => {
task.set_description(Some(s));
}
Some(None) => task.set_description(None),
None => {}
}
match self.priority {
Some(Some(p)) => task.set_priority(p),
Some(None) => task.set_priority(Priority::default()),
None => {}
}
Ok(())
})
}
}
pub fn add_task(
store: &TaskStore,
content: String,
description: Option<String>,
deadline: Option<DateTime<Utc>>,
priority: Option<Priority>,
) -> Result<(), AppError> {
store.update(|tasks: &mut Vec<Task>| {
let new_id: usize = tasks.iter().map(|t: &Task| t.id()).max().unwrap_or(0) + 1;
let new_task: Task = Task::new(
new_id,
content,
description,
deadline,
priority.unwrap_or_default(),
);
tasks.push(new_task);
Ok(())
})
}
pub fn list_tasks(store: &TaskStore, sort: Option<SortBy>) -> Result<Option<Vec<Task>>, AppError> {
match sort {
None => {
let tasks: Vec<Task> = store.load()?;
if tasks.is_empty() {
return Ok(None);
}
Ok(Some(tasks))
}
Some(order) => {
store.update(|tasks: &mut Vec<Task>| {
sort_tasks(tasks, order);
Ok(())
})?;
let tasks = store.load()?;
if tasks.is_empty() {
return Ok(None);
}
Ok(Some(tasks))
}
}
}
fn sort_tasks(tasks: &mut [Task], order: SortBy) {
match order {
SortBy::Deadline => {
tasks.sort_by(|a: &Task, b: &Task| match (a.deadline(), b.deadline()) {
(Some(d1), Some(d2)) => d1.cmp(&d2),
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => Ordering::Equal,
})
}
SortBy::Priority => tasks.sort_by_key(|t: &Task| t.priority()),
}
}
pub fn show_details(no: usize, store: &TaskStore) -> Result<Option<Task>, AppError> {
let tasks: Vec<Task> = store.load()?;
if tasks.is_empty() {
Ok(None)
} else if no == 0 || no > tasks.len() {
Err(AppError::TaskNotFound { no })
} else {
let task: Task = tasks
.get(no - 1)
.cloned()
.ok_or(AppError::TaskNotFound { no })?;
Ok(Some(task))
}
}
pub fn complete_task(no: usize, store: &TaskStore) -> Result<(), AppError> {
store.update(|tasks| {
if no == 0 || no > tasks.len() {
Err(AppError::TaskNotFound { no })
} else {
let task = tasks.get_mut(no - 1).ok_or(AppError::TaskNotFound { no })?;
task.complete();
Ok(())
}
})
}
pub fn incomplete_task(no: usize, store: &TaskStore) -> Result<(), AppError> {
store.update(|tasks| {
if no == 0 || no > tasks.len() {
Err(AppError::TaskNotFound { no })
} else {
let task = tasks.get_mut(no - 1).ok_or(AppError::TaskNotFound { no })?;
task.incomplete();
Ok(())
}
})
}
pub fn delete_task(no: usize, store: &TaskStore) -> Result<(), AppError> {
store.update(|tasks| {
if no == 0 || no > tasks.len() {
return Err(AppError::TaskNotFound { no });
}
tasks.remove(no - 1);
Ok(())
})
}
pub fn undo_task_preview(store: &TaskStore) -> Result<Vec<Task>, AppError> {
let backup_tasks = store.load_backup()?;
let current = store.load()?;
if backup_tasks == current {
return Err(AppError::NothingToUndo);
}
Ok(backup_tasks)
}
pub fn undo_task_apply(store: &TaskStore) -> Result<(), AppError> {
store.restore_backup()
}