use crate::types::{Task, TaskStatus};
use chrono::Utc;
use std::collections::HashMap;
use uuid::Uuid;
pub struct TaskQueue {
tasks: HashMap<Uuid, Task>,
completed: Vec<Uuid>,
}
impl TaskQueue {
pub fn new() -> Self {
Self {
tasks: HashMap::new(),
completed: Vec::new(),
}
}
pub fn add(&mut self, task: Task) -> Uuid {
let id = task.id;
self.tasks.insert(id, task);
id
}
pub fn next_ready(&self) -> Option<&Task> {
let mut ready: Vec<&Task> = self
.tasks
.values()
.filter(|t| t.is_ready(&self.completed))
.collect();
ready.sort_by_key(|t| t.created_at);
ready.into_iter().next()
}
pub fn all_ready(&self) -> Vec<&Task> {
let mut ready: Vec<&Task> = self
.tasks
.values()
.filter(|t| t.is_ready(&self.completed))
.collect();
ready.sort_by_key(|t| t.created_at);
ready
}
pub fn mark_running(&mut self, id: Uuid) -> bool {
if let Some(task) = self.tasks.get_mut(&id) {
task.status = TaskStatus::Running;
true
} else {
false
}
}
pub fn mark_completed(&mut self, id: Uuid) -> bool {
if let Some(task) = self.tasks.get_mut(&id) {
task.status = TaskStatus::Completed;
task.completed_at = Some(Utc::now());
self.completed.push(id);
true
} else {
false
}
}
pub fn mark_failed(&mut self, id: Uuid, reason: String) -> bool {
if let Some(task) = self.tasks.get_mut(&id) {
task.status = TaskStatus::Failed { reason };
true
} else {
false
}
}
pub fn mark_needs_review(&mut self, id: Uuid) -> bool {
if let Some(task) = self.tasks.get_mut(&id) {
task.status = TaskStatus::NeedsHumanReview;
true
} else {
false
}
}
pub fn get(&self, id: Uuid) -> Option<&Task> {
self.tasks.get(&id)
}
pub fn get_mut(&mut self, id: Uuid) -> Option<&mut Task> {
self.tasks.get_mut(&id)
}
pub fn all_tasks(&self) -> Vec<&Task> {
let mut tasks: Vec<&Task> = self.tasks.values().collect();
tasks.sort_by_key(|t| t.created_at);
tasks
}
pub fn pending_count(&self) -> usize {
self.tasks
.values()
.filter(|t| t.status == TaskStatus::Pending)
.count()
}
pub fn completed_count(&self) -> usize {
self.completed.len()
}
pub fn total_count(&self) -> usize {
self.tasks.len()
}
pub fn is_done(&self) -> bool {
self.tasks.values().all(|t| {
matches!(
t.status,
TaskStatus::Completed | TaskStatus::Failed { .. } | TaskStatus::NeedsHumanReview
)
})
}
pub fn needs_review_count(&self) -> usize {
self.tasks
.values()
.filter(|t| t.status == TaskStatus::NeedsHumanReview)
.count()
}
pub fn has_cycle(&self) -> bool {
let mut visited = HashMap::new();
for &id in self.tasks.keys() {
if self.dfs_cycle(id, &mut visited) {
return true;
}
}
false
}
fn dfs_cycle(&self, id: Uuid, visited: &mut HashMap<Uuid, u8>) -> bool {
match visited.get(&id) {
Some(1) => return true, Some(2) => return false, _ => {}
}
visited.insert(id, 1); if let Some(task) = self.tasks.get(&id) {
for dep in &task.dependencies {
if self.dfs_cycle(*dep, visited) {
return true;
}
}
}
visited.insert(id, 2); false
}
}
impl Default for TaskQueue {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::types::AgentRole;
#[test]
fn test_empty_queue() {
let queue = TaskQueue::new();
assert_eq!(queue.total_count(), 0);
assert_eq!(queue.pending_count(), 0);
assert!(queue.is_done());
assert!(queue.next_ready().is_none());
}
#[test]
fn test_add_and_retrieve() {
let mut queue = TaskQueue::new();
let task = Task::new("Test task", AgentRole::Coder);
let id = task.id;
queue.add(task);
assert_eq!(queue.total_count(), 1);
assert!(queue.get(id).is_some());
assert_eq!(queue.get(id).unwrap().description, "Test task");
}
#[test]
fn test_next_ready_no_deps() {
let mut queue = TaskQueue::new();
let task = Task::new("Ready task", AgentRole::Spec);
queue.add(task);
let ready = queue.next_ready();
assert!(ready.is_some());
assert_eq!(ready.unwrap().description, "Ready task");
}
#[test]
fn test_next_ready_with_deps() {
let mut queue = TaskQueue::new();
let t1 = Task::new("First", AgentRole::Spec);
let t1_id = t1.id;
queue.add(t1);
let t2 = Task::new("Second", AgentRole::Coder).with_dependencies(vec![t1_id]);
queue.add(t2);
let ready = queue.next_ready();
assert_eq!(ready.unwrap().description, "First");
queue.mark_running(t1_id);
queue.mark_completed(t1_id);
let ready = queue.next_ready();
assert_eq!(ready.unwrap().description, "Second");
}
#[test]
fn test_all_ready_parallel() {
let mut queue = TaskQueue::new();
let t1 = Task::new("Task A", AgentRole::Spec);
queue.add(t1);
let t2 = Task::new("Task B", AgentRole::Coder);
queue.add(t2);
let t3 = Task::new("Task C", AgentRole::Tester);
queue.add(t3);
let ready = queue.all_ready();
assert_eq!(ready.len(), 3);
}
#[test]
fn test_mark_completed() {
let mut queue = TaskQueue::new();
let task = Task::new("Complete me", AgentRole::Coder);
let id = task.id;
queue.add(task);
queue.mark_running(id);
assert_eq!(queue.get(id).unwrap().status, TaskStatus::Running);
queue.mark_completed(id);
assert_eq!(queue.get(id).unwrap().status, TaskStatus::Completed);
assert_eq!(queue.completed_count(), 1);
assert!(queue.is_done());
}
#[test]
fn test_mark_failed() {
let mut queue = TaskQueue::new();
let task = Task::new("Fail me", AgentRole::Tester);
let id = task.id;
queue.add(task);
queue.mark_failed(id, "compilation error".to_string());
assert!(matches!(
queue.get(id).unwrap().status,
TaskStatus::Failed { .. }
));
}
#[test]
fn test_mark_needs_review() {
let mut queue = TaskQueue::new();
let task = Task::new("Review me", AgentRole::Reviewer);
let id = task.id;
queue.add(task);
queue.mark_needs_review(id);
assert_eq!(queue.get(id).unwrap().status, TaskStatus::NeedsHumanReview);
}
#[test]
fn test_dependency_chain() {
let mut queue = TaskQueue::new();
let t1 = Task::new("Spec", AgentRole::Spec);
let t1_id = t1.id;
queue.add(t1);
let t2 = Task::new("Code", AgentRole::Coder).with_dependencies(vec![t1_id]);
let t2_id = t2.id;
queue.add(t2);
let t3 = Task::new("Test", AgentRole::Tester).with_dependencies(vec![t2_id]);
let t3_id = t3.id;
queue.add(t3);
let t4 = Task::new("Review", AgentRole::Reviewer).with_dependencies(vec![t2_id, t3_id]);
queue.add(t4);
assert_eq!(queue.all_ready().len(), 1);
queue.mark_running(t1_id);
queue.mark_completed(t1_id);
assert_eq!(queue.all_ready().len(), 1);
queue.mark_running(t2_id);
queue.mark_completed(t2_id);
assert_eq!(queue.all_ready().len(), 1);
queue.mark_running(t3_id);
queue.mark_completed(t3_id);
assert_eq!(queue.all_ready().len(), 1);
}
#[test]
fn test_no_cycle() {
let mut queue = TaskQueue::new();
let t1 = Task::new("A", AgentRole::Spec);
let t1_id = t1.id;
queue.add(t1);
let t2 = Task::new("B", AgentRole::Coder).with_dependencies(vec![t1_id]);
queue.add(t2);
assert!(!queue.has_cycle());
}
#[test]
fn test_cycle_detection() {
let mut queue = TaskQueue::new();
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
let mut t1 = Task::new("A", AgentRole::Spec);
t1.id = id1;
t1.dependencies = vec![id2];
let mut t2 = Task::new("B", AgentRole::Coder);
t2.id = id2;
t2.dependencies = vec![id1];
queue.add(t1);
queue.add(t2);
assert!(queue.has_cycle());
}
#[test]
fn test_is_done() {
let mut queue = TaskQueue::new();
let task = Task::new("Only task", AgentRole::Spec);
let id = task.id;
queue.add(task);
assert!(!queue.is_done());
queue.mark_completed(id);
assert!(queue.is_done());
}
#[test]
fn test_is_done_with_needs_review() {
let mut queue = TaskQueue::new();
let t1 = Task::new("Task 1", AgentRole::Coder);
let t1_id = t1.id;
let t2 = Task::new("Task 2", AgentRole::Reviewer);
let t2_id = t2.id;
queue.add(t1);
queue.add(t2);
queue.mark_completed(t1_id);
assert!(!queue.is_done());
queue.mark_needs_review(t2_id);
assert!(queue.is_done());
}
#[test]
fn test_is_done_with_failed() {
let mut queue = TaskQueue::new();
let task = Task::new("Failing task", AgentRole::Tester);
let id = task.id;
queue.add(task);
queue.mark_failed(id, "error".into());
assert!(queue.is_done());
}
#[test]
fn test_needs_review_count() {
let mut queue = TaskQueue::new();
let t1 = Task::new("Task 1", AgentRole::Coder);
let t1_id = t1.id;
let t2 = Task::new("Task 2", AgentRole::Reviewer);
let t2_id = t2.id;
queue.add(t1);
queue.add(t2);
assert_eq!(queue.needs_review_count(), 0);
queue.mark_needs_review(t1_id);
assert_eq!(queue.needs_review_count(), 1);
queue.mark_needs_review(t2_id);
assert_eq!(queue.needs_review_count(), 2);
}
}