Documentation
use crate::{
    json_parser::JsonParserError, json_value::JsonValue, processor::Context, reader::Reader,
    selection::Get, selection::Result,
};
use std::{io::Read, rc::Rc};

enum Type {
    Variable,
    Macro,
}

struct VariableExtructor {
    name: String,
    variable_type: Type,
}

impl Get for VariableExtructor {
    fn get(&self, value: &Context) -> Option<JsonValue> {
        match self.variable_type {
            Type::Macro => value.get_definition(&self.name).and_then(|f| f.get(value)),
            Type::Variable => value.get_variable_value(&self.name).cloned(),
        }
    }
}

pub fn parse_get_variable<R: Read>(reader: &mut Reader<R>) -> Result<Rc<dyn Get>> {
    let variable_type = match reader.peek()? {
        None => {
            return Err(JsonParserError::UnexpectedEof(reader.where_am_i()).into());
        }
        Some(b':') => Type::Variable,
        Some(b'@') => Type::Macro,
        Some(ch) => {
            return Err(JsonParserError::UnexpectedCharacter(
                reader.where_am_i(),
                ch as char,
                ":@".into(),
            )
            .into());
        }
    };
    let mut name = Vec::new();
    loop {
        match reader.next()? {
            None | Some(b' ' | b'\n' | b'\t' | b'\r' | b')') => {
                break;
            }
            Some(ch) => name.push(ch),
        };
    }
    if name.is_empty() {
        return Err(JsonParserError::UnexpectedEof(reader.where_am_i()).into());
    }
    let name = String::from_utf8(name)?;
    Ok(Rc::new(VariableExtructor {
        name,
        variable_type,
    }))
}

#[cfg(test)]
mod tests {
    use std::{cell::RefCell, str::FromStr};

    use super::*;
    use crate::{json_value::JsonValue, reader::from_string, selection::SelectionParseError};

    #[test]
    fn parse_return_error_for_nothing() {
        let text = String::new();
        let mut reader = from_string(&text);
        let error = parse_get_variable(&mut reader).err().unwrap();

        assert!(matches!(
            error,
            SelectionParseError::JsonError(JsonParserError::UnexpectedEof(_))
        ));
    }

    #[test]
    fn parse_return_error_for_unexpected_char() {
        let text = "hi".to_string();
        let mut reader = from_string(&text);
        let error = parse_get_variable(&mut reader).err().unwrap();

        assert!(matches!(
            error,
            SelectionParseError::JsonError(JsonParserError::UnexpectedCharacter(_, _, _))
        ));
    }

    #[test]
    fn parse_return_error_for_empty_name() {
        let text = ":".to_string();
        let mut reader = from_string(&text);
        let error = parse_get_variable(&mut reader).err().unwrap();

        assert!(matches!(
            error,
            SelectionParseError::JsonError(JsonParserError::UnexpectedEof(_))
        ));
    }

    #[test]
    fn macro_will_apply_getter() -> Result<()> {
        let text = "@name".to_string();
        let mut reader = from_string(&text);
        let getter = parse_get_variable(&mut reader)?;

        struct Caller(Rc<RefCell<usize>>);
        let data = Rc::new(RefCell::new(0));
        impl Get for Caller {
            fn get(&self, _: &Context) -> Option<JsonValue> {
                *self.0.borrow_mut() += 1;
                JsonValue::from_str("12").ok()
            }
        }
        let caller: Rc<dyn Get> = Rc::new(Caller(data.clone()));
        let context = Context::new_empty().with_definition("name".to_string(), &caller);

        let value = getter.get(&context);

        {
            assert_eq!(value, JsonValue::from_str("12").ok());
            let binding = data.borrow();
            let data = &*binding;
            assert_eq!(data, &1);
        }

        {
            let value = getter.get(&context);
            assert_eq!(value, JsonValue::from_str("12").ok());
            let binding = data.borrow();
            let data = &*binding;
            assert_eq!(data, &2);
        }

        Ok(())
    }

    #[test]
    fn macro_will_get_variable() -> Result<()> {
        let text = ":name".to_string();
        let mut reader = from_string(&text);
        let getter = parse_get_variable(&mut reader)?;

        let context = Context::new_empty().with_variable("name".to_string(), (22).into());

        let value = getter.get(&context);

        assert_eq!(value, JsonValue::from_str("22").ok());

        Ok(())
    }
}