extern crate core;
use cel_parser::parse;
use std::convert::TryFrom;
use std::sync::Arc;
use thiserror::Error;
mod macros;
pub mod context;
pub use cel_parser::Expression;
pub use context::Context;
pub use functions::FunctionContext;
pub use objects::{ResolveResult, Value};
mod duration;
mod functions;
mod magic;
pub mod objects;
mod resolvers;
mod ser;
pub use ser::to_value;
mod testing;
use magic::FromContext;
pub mod extractors {
    pub use crate::magic::{Arguments, Identifier, This};
}
#[derive(Error, Debug)]
#[error("Error parsing {msg}")]
pub struct ParseError {
    msg: String,
}
#[derive(Error, Debug, PartialEq, Clone)]
pub enum ExecutionError {
    #[error("Invalid argument count: expected {expected}, got {actual}")]
    InvalidArgumentCount { expected: usize, actual: usize },
    #[error("Invalid argument type: {:?}", .target)]
    UnsupportedTargetType { target: Value },
    #[error("Method '{method}' not supported on type '{target:?}'")]
    NotSupportedAsMethod { method: String, target: Value },
    #[error("Unable to use value '{0:?}' as a key")]
    UnsupportedKeyType(Value),
    #[error("Unexpected type: got '{got}', want '{want}'")]
    UnexpectedType { got: String, want: String },
    #[error("No such key: {0}")]
    NoSuchKey(Arc<String>),
    #[error("Undeclared reference to '{0}'")]
    UndeclaredReference(Arc<String>),
    #[error("Missing argument or target")]
    MissingArgumentOrTarget,
    #[error("{0:?} can not be compared to {1:?}")]
    ValuesNotComparable(Value, Value),
    #[error("Error executing function '{function}': {message}")]
    FunctionError { function: String, message: String },
}
impl ExecutionError {
    pub fn no_such_key(name: &str) -> Self {
        ExecutionError::NoSuchKey(Arc::new(name.to_string()))
    }
    pub fn undeclared_reference(name: &str) -> Self {
        ExecutionError::UndeclaredReference(Arc::new(name.to_string()))
    }
    pub fn invalid_argument_count(expected: usize, actual: usize) -> Self {
        ExecutionError::InvalidArgumentCount { expected, actual }
    }
    pub fn function_error<E: ToString>(function: &str, error: E) -> Self {
        ExecutionError::FunctionError {
            function: function.to_string(),
            message: error.to_string(),
        }
    }
    pub fn unsupported_target_type(target: Value) -> Self {
        ExecutionError::UnsupportedTargetType { target }
    }
    pub fn not_supported_as_method(method: &str, target: Value) -> Self {
        ExecutionError::NotSupportedAsMethod {
            method: method.to_string(),
            target,
        }
    }
    pub fn unsupported_key_type(value: Value) -> Self {
        ExecutionError::UnsupportedKeyType(value)
    }
    pub fn missing_argument_or_target() -> Self {
        ExecutionError::MissingArgumentOrTarget
    }
}
#[derive(Debug)]
pub struct Program {
    expression: Expression,
}
impl Program {
    pub fn compile(source: &str) -> Result<Program, ParseError> {
        match parse(source) {
            Ok(expression) => Ok(Program { expression }),
            Err(e) => Err(ParseError {
                msg: format!("{}", e),
            }),
        }
    }
    pub fn execute(&self, context: &Context) -> ResolveResult {
        Value::resolve(&self.expression, context)
    }
}
impl TryFrom<&str> for Program {
    type Error = ParseError;
    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Program::compile(value)
    }
}
#[cfg(test)]
mod tests {
    use crate::context::Context;
    use crate::objects::{ResolveResult, Value};
    use crate::testing::test_script;
    use crate::{ExecutionError, Program};
    use std::collections::HashMap;
    use std::convert::TryInto;
    #[test]
    fn parse() {
        Program::compile("1 + 1").unwrap();
    }
    #[test]
    fn from_str() {
        let input = "1.1";
        let _p: Program = input.try_into().unwrap();
    }
    #[test]
    fn variables() {
        fn assert_output(script: &str, expected: ResolveResult) {
            let mut ctx = Context::default();
            ctx.add_variable_from_value("foo", HashMap::from([("bar", 1i64)]));
            ctx.add_variable_from_value("arr", vec![1i64, 2, 3]);
            ctx.add_variable_from_value("str", "foobar".to_string());
            assert_eq!(test_script(script, Some(ctx)), expected);
        }
        assert_output("size([1, 2, 3]) == 3", Ok(true.into()));
        assert_output("size([]) == 3", Ok(false.into()));
        assert_output("foo.bar == 1", Ok(true.into()));
        assert_output("arr[0] == 1", Ok(true.into()));
        assert_output("str[0] == 'f'", Ok(true.into()));
        assert_output(
            "{'a': 1} + {'a': 2, 'b': 3}",
            Ok(HashMap::from([("a", 2), ("b", 3)]).into()),
        );
    }
    #[test]
    fn test_contains() {
        let tests = vec![
            ("list", "[1, 2, 3].contains(3) == true"),
            ("map", "{1: true, 2: true, 3: true}.contains(3) == true"),
            ("string", "'foobar'.contains('bar') == true"),
            ("bytes", "b'foobar'.contains(b'o') == true"),
        ];
        for (name, script) in tests {
            assert_eq!(test_script(script, None), Ok(true.into()), "{}", name);
        }
    }
    #[test]
    fn test_execution_errors() {
        let tests = vec![
            (
                "no such key",
                "foo.baz.bar == 1",
                ExecutionError::no_such_key("baz"),
            ),
            (
                "undeclared reference",
                "missing == 1",
                ExecutionError::undeclared_reference("missing"),
            ),
            (
                "unsupported key type",
                "{null: true}",
                ExecutionError::unsupported_key_type(Value::Null),
            ),
        ];
        for (name, script, error) in tests {
            let mut ctx = Context::default();
            ctx.add_variable_from_value("foo", HashMap::from([("bar", 1)]));
            let res = test_script(script, Some(ctx));
            assert_eq!(res, error.into(), "{}", name);
        }
    }
}