//! Different aggregators for output.
//!
//! An aggregator is something that "controls the order" of the output. Aggregators can either save
//! all items that they're and output them once the search is finished, or write them straight
//! to the output stream.
//!
//! Aggregators must be shareable across threads, as the search will be multi-threaded. This is why
//! an Aggregator must make sure that the data is protected by a mutex or similar.
use super::{super::LogResult, formats::Format, sorting::Sorting};

use std::{io::Write, sync::Mutex};

pub trait Aggregator: Sync {
    fn push_item(&self, item: LogResult, format: &dyn Format, stream: &mut dyn Write);
    // When the `unsized_locals` feature is stable, we could rewrite this to finish(self, ...).
    fn finish(self: Box<Self>, format: &dyn Format, stream: &mut dyn Write);
}

/// An aggregator that just pushes through each item to the output stream without any sorting or
/// whatsoever.
pub struct WriteThrough;

impl Aggregator for WriteThrough {
    fn push_item(&self, item: LogResult, format: &dyn Format, stream: &mut dyn Write) {
        let text = format.format_result(&item);
        stream.write_all(text.as_bytes()).unwrap();
        stream.flush().unwrap();
    }

    fn finish(self: Box<Self>, _: &dyn Format, _: &mut dyn Write) {}
}

/// An aggregator that keeps all found logs in memory and sorts them before outputting them.
#[derive(Debug)]
pub struct SortedOutput {
    sorting: Sorting,
    items: Mutex<Vec<LogResult>>,
}

impl SortedOutput {
    pub fn new(sorting: Sorting) -> Self {
        SortedOutput {
            sorting,
            items: Mutex::new(vec![]),
        }
    }
}

impl Aggregator for SortedOutput {
    fn push_item(&self, item: LogResult, _: &dyn Format, _: &mut dyn Write) {
        self.items.lock().unwrap().push(item)
    }

    fn finish(self: Box<Self>, format: &dyn Format, stream: &mut dyn Write) {
        let SortedOutput { sorting, items } = *self;
        let mut items = items.into_inner().unwrap();
        items.sort_unstable_by(|a, b| sorting.cmp(a, b));
        for item in items {
            let text = format.format_result(&item);
            stream.write_all(text.as_bytes()).unwrap();
        }
        stream.flush().unwrap();
    }
}