/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::collections::HashMap;
use chrono::{Datelike, Duration, NaiveDate, Utc};
use colored::Colorize;
use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
use darkfi::{util::time::DateTime, Error, Result};
use crate::primitives::{TaskEvent, TaskInfo};
// Red
const NO_TASK_SCALE: u8 = 50;
const MIN_SCALE: usize = 90;
const MAX_SCALE: usize = 255;
const INCREASE_FACTOR: usize = 25;
// Green
const GREEN: u8 = 40;
// Blue
const BLUE: u8 = 50;
/// Log drawdown gets all assignees of tasks, stores a vec of stopped tasks for each
/// assignee in a hashmap, draw a heatmap of how many stopped tasks in each day of the
/// specified month and assignee.
pub fn drawdown(date: String, tasks: Vec, assignee: Option) -> Result<()> {
let mut ret = HashMap::new();
let assignees = assignees(tasks.clone());
if assignee.is_none() {
println!("Assignees of this month's tasks are: {}", assignees.join(", "));
return Ok(())
}
let asgn = assignee.unwrap();
if !assignees.contains(&asgn) {
eprintln!("Assignee {} not found, run \"tau log -h\" for more information.", asgn);
return Ok(())
}
for assignee in assignees {
let stopped_tasks = tasks
.clone()
.into_iter()
.filter(|task| {
if task.assign.is_empty() {
task.owner == asgn
} else {
task.assign.contains(&asgn)
}
})
.collect::>();
ret.insert(assignee, stopped_tasks);
}
let mut naivedate = to_naivedate(date.clone())?;
println!("log drawdown for {} in {}", asgn, naivedate.format("%b %Y"));
let fdow = if naivedate.month() == 2 && !is_leap_year(naivedate.year()) {
[" ", "1 ", "8 ", "15", "22", " "]
} else {
[" ", "1 ", "8 ", "15", "22", "29"]
};
// Print first day of each week horizontally.
let mut dow_grid =
Grid::new(GridOptions { direction: Direction::LeftToRight, filling: Filling::Spaces(1) });
if ret.contains_key(&asgn) {
for i in fdow {
let cell = Cell::from(i);
dow_grid.add(cell)
}
let grid_display = dow_grid.fit_into_rows(1);
print!("{}", grid_display);
}
let mut grid =
Grid::new(GridOptions { direction: Direction::TopToBottom, filling: Filling::Spaces(1) });
let days_in_month = get_days_from_month(date)? as u32;
if ret.contains_key(&asgn) {
for _ in 0..7 {
let dow = naivedate.weekday().to_string();
let wcell = Cell::from(dow);
grid.add(wcell);
naivedate += Duration::days(1);
}
for day in 1..=days_in_month {
let owner_stopped_tasks = ret.get(&asgn).unwrap().to_owned();
let date_tasks: Vec = owner_stopped_tasks
.into_iter()
.filter(|t| {
// last event is always state stop
let event_date = DateTime::from_timestamp(
t.events.last().unwrap_or(&TaskEvent::default()).timestamp.0,
0,
);
event_date.day == day
})
.collect();
let red_scale = if date_tasks.is_empty() {
NO_TASK_SCALE
} else {
((date_tasks.len() * INCREASE_FACTOR) + MIN_SCALE).clamp(MIN_SCALE, MAX_SCALE) as u8
};
let cell = Cell::from("▀▀".truecolor(red_scale, GREEN, BLUE));
grid.add(cell)
}
}
let grid_display = grid.fit_into_rows(7);
println!("{}", grid_display);
Ok(())
}
fn is_leap_year(year: i32) -> bool {
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
}
fn helper_parse_func(date: String) -> Result<(u32, i32)> {
if date.len() != 4 || date.parse::().is_err() {
return Err(Error::MalformedPacket)
}
let (month, year) = (date[..2].parse::().unwrap(), date[2..].parse::().unwrap());
let year = year + (Utc::now().year() / 100) * 100;
Ok((month, year))
}
pub fn to_naivedate(date: String) -> Result {
let (month, year) = helper_parse_func(date)?;
Ok(NaiveDate::from_ymd_opt(year, month, 1).unwrap())
}
fn get_days_from_month(date: String) -> Result {
let (month, year) = helper_parse_func(date)?;
Ok(NaiveDate::from_ymd_opt(
match month {
12 => year + 1,
_ => year,
},
match month {
12 => 1,
_ => month + 1,
},
1,
)
.unwrap()
.signed_duration_since(NaiveDate::from_ymd_opt(year, month, 1).unwrap())
.num_days())
}
fn assignees(tasks: Vec) -> Vec {
let mut assignees = vec![];
for task in tasks {
// if task is stopped with no assignee specified we give credit to the owner
if task.assign.is_empty() {
if !assignees.contains(&task.owner) {
assignees.push(task.owner)
}
} else {
for assignee in task.assign {
if !assignees.contains(&assignee) {
assignees.push(assignee)
}
}
}
}
assignees
}