Documentation
use std::{rc::Rc, str::FromStr};

use indexmap::IndexMap;

use crate::{
    json_value::JsonValue,
    processor::{Context, Process, ProcessDecision, Result as ProcessResult, Titles},
    reader::from_string,
    selection::{Get, SelectionParseError, read_getter},
};

#[derive(Clone)]
pub struct Grouper {
    group_by: Rc<dyn Get>,
}

impl FromStr for Grouper {
    type Err = SelectionParseError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let source = s.to_string();
        let mut reader = from_string(&source);
        reader.eat_whitespace()?;
        let group_by = read_getter(&mut reader)?;
        reader.eat_whitespace()?;
        if let Some(ch) = reader.peek()? {
            return Err(SelectionParseError::ExpectingEof(
                reader.where_am_i(),
                ch as char,
            ));
        }
        Ok(Grouper { group_by })
    }
}

impl Grouper {
    pub fn create_process(&self, next: Box<dyn Process>) -> Box<dyn Process> {
        Box::new(GrouperProcess {
            data: IndexMap::new(),
            next,
            group_by: self.group_by.clone(),
        })
    }
}

struct GrouperProcess {
    data: IndexMap<String, Vec<JsonValue>>,
    next: Box<dyn Process>,
    group_by: Rc<dyn Get>,
}

impl Process for GrouperProcess {
    fn complete(&mut self) -> ProcessResult<()> {
        let mut data = IndexMap::new();
        for (key, value) in &self.data {
            let value = value.clone().into();
            data.insert(key.clone(), value);
        }

        let value = data.into();
        let context = Context::new_with_no_context(value);
        self.data.clear();
        self.next.process(context)?;
        Ok(())
    }
    fn process(&mut self, context: Context) -> ProcessResult<ProcessDecision> {
        if let Some(key) = self.name(&context) {
            let value = context.build();
            self.data.entry(key).or_default().push(value);
        }
        Ok(ProcessDecision::Continue)
    }
    fn start(&mut self, _: Titles) -> ProcessResult<()> {
        self.next.start(Titles::default())
    }
}

impl GrouperProcess {
    fn name(&self, context: &Context) -> Option<String> {
        if let Some(JsonValue::String(str)) = self.group_by.get(context) {
            Some(str)
        } else {
            None
        }
    }
}

#[cfg(test)]
mod tests {
    use std::cell::RefCell;
    use std::ops::Deref;
    use std::rc::Rc;

    use super::*;
    use crate::json_value::JsonValue;
    use crate::processor::{Context, Titles};

    #[test]
    fn parse_parse_correctly() {
        let str = "(.len)";
        let grouper = Grouper::from_str(str).unwrap();

        let input = Context::new_with_no_context("test".into());

        assert_eq!(grouper.group_by.get(&input), Some((4).into()));
    }

    #[test]
    fn parse_fail_if_too_long() {
        let str = "(.len)3";
        let err = Grouper::from_str(str).err().unwrap();

        assert!(matches!(err, SelectionParseError::ExpectingEof(_, _)));
    }

    #[test]
    fn start_will_remove_the_title() -> ProcessResult<()> {
        struct Next(Rc<RefCell<bool>>);
        let data = Rc::new(RefCell::new(false));
        let one = Rc::new("one".into());
        let two = Rc::new("two".into());
        let titles = Titles::default().with_title(&one).with_title(&two);
        impl Process for Next {
            fn complete(&mut self) -> ProcessResult<()> {
                Ok(())
            }
            fn process(&mut self, _: Context) -> ProcessResult<ProcessDecision> {
                Ok(ProcessDecision::Continue)
            }
            fn start(&mut self, titles: Titles) -> ProcessResult<()> {
                assert_eq!(titles.len(), 0);
                *self.0.borrow_mut() = true;
                Ok(())
            }
        }
        {
            let str = "(.len)";
            let grouper = Grouper::from_str(str).unwrap();
            let next = Box::new(Next(data.clone()));
            let mut grouper = grouper.create_process(next);

            grouper.start(titles)?;
        }

        let binding = data.borrow();
        let data = &*binding;
        assert_eq!(data, &true);

        Ok(())
    }

    #[test]
    fn complete_will_complete_with_the_correct_values() -> ProcessResult<()> {
        struct Next {
            data: Rc<RefCell<Option<JsonValue>>>,
        }
        let data = Rc::new(RefCell::new(Option::None));
        impl Process for Next {
            fn complete(&mut self) -> ProcessResult<()> {
                Ok(())
            }
            fn process(&mut self, context: Context) -> ProcessResult<ProcessDecision> {
                let input = context.input().deref().clone();
                assert!(self.data.borrow().is_none());
                *self.data.borrow_mut() = Some(input);
                Ok(ProcessDecision::Continue)
            }
            fn start(&mut self, _: Titles) -> ProcessResult<()> {
                Ok(())
            }
        }
        {
            let str = ".";
            let grouper = Grouper::from_str(str).unwrap();
            let next = Box::new(Next { data: data.clone() });
            let one = Rc::new("one".into());
            let two = Rc::new("two".into());
            let titles = Titles::default().with_title(&one).with_title(&two);
            let mut grouper = grouper.create_process(next);

            grouper.start(titles)?;

            let context = Context::new_with_no_context("one".into())
                .with_result(&one, Some((1).into()))
                .with_result(&two, Some((2).into()));
            grouper.process(context)?;
            let context = Context::new_with_no_context("one".into())
                .with_result(&one, Some((4).into()))
                .with_result(&two, Some((6).into()));
            grouper.process(context)?;
            let context = Context::new_with_no_context("three".into())
                .with_result(&one, Some((3).into()))
                .with_result(&two, Some((4).into()));
            grouper.process(context)?;
            let context = Context::new_with_no_context((1).into())
                .with_result(&one, Some((10).into()))
                .with_result(&two, Some((20).into()));
            grouper.process(context)?;
            let context = Context::new_with_no_context("one".into())
                .with_result(&one, Some((10).into()))
                .with_result(&two, Some((20).into()));
            grouper.process(context)?;

            grouper.complete()?;
        }

        let binding = data.borrow();
        let data = binding.deref().clone().unwrap();
        let data = format!("{data}");
        assert_eq!(
            data,
            r#"{"one": [{"one": 1, "two": 2}, {"one": 4, "two": 6}, {"one": 10, "two": 20}], "three": [{"one": 3, "two": 4}]}"#
        );

        Ok(())
    }
}