use liquid_core::Expression;
use liquid_core::Result;
use liquid_core::Runtime;
use liquid_core::{
    Display_filter, Filter, FilterParameters, FilterReflection, FromFilterParameters, ParseFilter,
};
use liquid_core::{Value, ValueView};

mod array;
mod date;
mod html;
mod math;
mod slice;
mod string;
mod url;

pub use self::array::{
    Compact, Concat, First, Join, Last, Map, Reverse, Sort, SortNatural, Uniq, Where,
};
pub use self::date::Date;
pub use self::html::{Escape, EscapeOnce, NewlineToBr, StripHtml};
pub use self::math::{
    Abs, AtLeast, AtMost, Ceil, DividedBy, Floor, Minus, Modulo, Plus, Round, Times,
};
pub use self::slice::Slice;
pub use self::string::case::{Capitalize, Downcase, Upcase};
pub use self::string::operate::{Append, Prepend, Remove, RemoveFirst, Replace, ReplaceFirst};
pub use self::string::strip::{Lstrip, Rstrip, Strip, StripNewlines};
pub use self::string::truncate::{Truncate, TruncateWords};
pub use self::string::Split;
pub use self::url::{UrlDecode, UrlEncode};

#[derive(Clone, ParseFilter, FilterReflection)]
#[filter(
    name = "size",
    description = "Returns the size of the input. For an array or object this is the number of elemets. For other values it's the lenght of its string representation.",
    parsed(SizeFilter)
)]
pub struct Size;

#[derive(Debug, Default, Display_filter)]
#[name = "size"]
struct SizeFilter;

impl Filter for SizeFilter {
    fn evaluate(&self, input: &dyn ValueView, _runtime: &Runtime<'_>) -> Result<Value> {
        if let Some(x) = input.as_scalar() {
            Ok(Value::scalar(x.to_kstr().len() as i32))
        } else if let Some(x) = input.as_array() {
            Ok(Value::scalar(x.size()))
        } else if let Some(x) = input.as_object() {
            Ok(Value::scalar(x.size()))
        } else {
            Ok(Value::scalar(0i32))
        }
    }
}

#[derive(Debug, FilterParameters)]
struct DefaultArgs {
    #[parameter(description = "The default value.")]
    default: Expression,
}

#[derive(Clone, ParseFilter, FilterReflection)]
#[filter(
    name = "default",
    description = "Sets a default value for the given input.",
    parameters(DefaultArgs),
    parsed(DefaultFilter)
)]
pub struct Default;

#[derive(Debug, FromFilterParameters, Display_filter)]
#[name = "default"]
struct DefaultFilter {
    #[parameters]
    args: DefaultArgs,
}

impl Filter for DefaultFilter {
    fn evaluate(&self, input: &dyn ValueView, runtime: &Runtime<'_>) -> Result<Value> {
        let args = self.args.evaluate(runtime)?;

        if input.query_state(liquid_core::value::State::DefaultValue) {
            Ok(args.default.to_value())
        } else {
            Ok(input.to_value())
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use liquid_core::Object;

    #[test]
    fn unit_size() {
        assert_eq!(
            liquid_core::call_filter!(Size, "abc").unwrap(),
            Value::scalar(3f64)
        );
        assert_eq!(
            liquid_core::call_filter!(Size, "this has 22 characters").unwrap(),
            Value::scalar(22f64)
        );
        assert_eq!(
            liquid_core::call_filter!(
                Size,
                Value::Array(vec![
                    Value::scalar(0f64),
                    Value::scalar(1f64),
                    Value::scalar(2f64),
                    Value::scalar(3f64),
                    Value::scalar(4f64),
                ])
            )
            .unwrap(),
            Value::scalar(5f64)
        );
    }

    #[test]
    fn unit_default() {
        assert_eq!(
            liquid_core::call_filter!(Default, "", "bar").unwrap(),
            liquid_core::value!("bar")
        );
        assert_eq!(
            liquid_core::call_filter!(Default, "foo", "bar").unwrap(),
            liquid_core::value!("foo")
        );
        assert_eq!(
            liquid_core::call_filter!(Default, 0_f64, "bar").unwrap(),
            Value::scalar(0_f64)
        );
        assert_eq!(
            liquid_core::call_filter!(Default, liquid_core::value!([]), 1_f64).unwrap(),
            Value::scalar(1_f64)
        );
        assert_eq!(
            liquid_core::call_filter!(Default, liquid_core::value!([""]), 1_f64).unwrap(),
            liquid_core::value!([""])
        );
        assert_eq!(
            liquid_core::call_filter!(Default, Object::new(), 1_f64).unwrap(),
            Value::scalar(1_f64)
        );
        assert_eq!(
            liquid_core::call_filter!(Default, false, Value::scalar(1_f64)).unwrap(),
            Value::scalar(1_f64)
        );
        assert_eq!(
            liquid_core::call_filter!(Default, true, Value::scalar(1_f64)).unwrap(),
            Value::scalar(true)
        );
    }
}
