use super::*;

macro_rules! parse_token {
    (expression : $rule:expr => $tree:expr) => {
        $tree.push($tree.templar.parse_match($rule.into_inner())?)?;
    };
    (content : $rule:expr => $tree:expr) => {
        $tree.push(Node::Data($rule.as_str().into()))?
    };
    (template : $rule:expr => $tree:expr) => {
        $tree.push($tree.templar.parse_match($rule.into_inner())?.set_operation(Operations::Concat))?
    };
    (for : $rule:expr => $tree:expr) => {{
        $tree.set_op(Operations::ForLoop)?;
        $tree.finish_op()?;
        return Ok($tree.into_node()?.into_scope())
    }};
    (scope : $rule:expr => $tree:expr) => {
        $tree.push($tree.templar.parse_match($rule.into_inner())?.set_operation(Operations::Concat).into_scope())?
    };
    (true => $tree:expr) => {
        $tree.push(Node::Data(true.into()))?;
    };
    (false => $tree:expr) => {
        $tree.push(Node::Data(false.into()))?;
    };
    (str ' ' : $rule:expr => $tree:expr) => {{
        $tree.push({
            let mut result = String::new();
            for pair in $rule.clone().into_inner() {
                match pair.as_rule() {
                    Rule::str_single => result.push_str(&$rule.as_str().replace("\\'", "'")),
                    Rule::str_double => result.push_str(&$rule.as_str().replace("\\\"", "\"")),
                    Rule::str_backtick => result.push_str(&$rule.as_str().replace("\\`", "`")),
                    _ => parse_token!(!pair),
                }
            }
            result.truncate(result.len() - 1);
            result.remove(0);
            result.shrink_to_fit();
            Node::Data(result.into())
        })?;
    }};
    (nil => $tree:expr) => {
        $tree.push(Node::Data(InnerData::Null.into()))?;
    };
    (args : $rule:expr => $tree:expr) => {
        $tree.push($tree.templar.parse_match($rule.into_inner())?)?;
    };
    (op : $name:ident => $tree:expr) => {
        $tree.set_op(Operations::$name)?
    };
    (ident : $rule:expr) => {
        $rule.as_str().into()
    };
    (number : $rule:expr => $tree:expr) => {
        $tree.push(Node::Data(
            $rule
                .as_str()
                .parse::<i64>()
                .map_err(|e| TemplarError::ParseFailure(format!("{}", e)))?
                .into(),
        ))?;
    };
    (array : $rule:expr => $tree:expr) => {
        $tree.push({
            let mut tree = ParseTree::new($tree.templar);
            for pair in $rule.into_inner() {
                match pair.as_rule() {
                    Rule::expression_cap => tree.push(tree.templar.parse_match(pair.into_inner())?)?,
                    _ => parse_token!(!pair),
                }
            }
            Node::Array(tree.into_nodes()?)
        })?;
    };
    (map : $rule:expr => $tree:expr) => {
        $tree.push({
            let mut res = BTreeMap::new();
            let mut last_lit = Data::empty();
            for pair in $rule.into_inner() {
                match pair.as_rule() {
                    Rule::literal_cap => last_lit = $tree.templar.parse_match(pair.into_inner())?.into_document()?,
                    Rule::expression_cap => { res.insert(last_lit.take(), $tree.templar.parse_match(pair.into_inner())?); },
                    _ => parse_token!(!pair),
                }
            }
            Node::Map(res)
        })?;
    };
    (fn : $rule:expr => $tree:expr) => {
        $tree.push({
            let mut tree = ParseTree::new($tree.templar);
            let mut name = String::new();
            for pair in $rule.into_inner() {
                match pair.as_rule() {
                    Rule::ident => name = parse_token!(ident: pair),
                    Rule::args => parse_token!(args: pair => tree),
                    _ => parse_token!(!pair),
                }
            }
            name.shrink_to_fit();
            let executor = FunctionExecutor::new(tree.templar.functions
                    .get(&name)
                    .ok_or_else(|| TemplarError::FunctionNotFound(name.to_string()))?
                    .clone());
            Node::Operation(Arc::new(Operation::from_function(name, executor, tree.into_node()?)))
        })?;
    };
    (filter : $rule:expr => $tree:expr) => {{
        let mut tree = ParseTree::new($tree.templar);
        let mut name = String::new();
        for pair in $rule.into_inner() {
            match pair.as_rule() {
                Rule::ident => name = parse_token!(ident: pair),
                Rule::args => parse_token!(args: pair => tree),
                _ => parse_token!(!pair),
            }
        }
        $tree.filter(&name, tree.into_node()?)?;
    }};
    (value : $rule:expr => $tree:expr) => {
        $tree.push({
            let mut result: Vec<InnerData> = vec![];
            for pair in $rule.into_inner() {
                match pair.as_rule() {
                    Rule::ident => result.push(parse_token!(ident: pair)),
                    Rule::value_key => result.push(parse_token!(value_key: pair).into()),
                    _ => parse_token!(!pair),
                }
            }
            Node::Value(result)
        })?
    };
    (value_key : $rule:expr) => {
        $rule
            .into_inner()
            .next()
            .unwrap()
            .into_inner()
            .as_str()
            .replace("\\'", "'")
    };
    (! $rule:expr) => {{
        return Err(TemplarError::ParseFailure(format!(
            "Unexpected rule while parsing expression: {}",
            $rule
        )));
    }};
}

impl Templar {
    pub(crate) fn parse_match(&self, pairs: pest::iterators::Pairs<'_, Rule>) -> Result<Node> {
        let mut tree = ParseTree::new(self);
        for pair in pairs {
            match pair.as_rule() {
                Rule::template_inner
                | Rule::template_block
                | Rule::ctrl_block_if
                | Rule::ctrl_block_else
                | Rule::ctrl_block_loop => parse_token!(template: pair => tree),
                Rule::ctrl_block_end_loop => parse_token!(for: pair => tree),
                Rule::ctrl_block_scope => parse_token!(scope: pair => tree),
                Rule::expression_cap => parse_token!(expression: pair => tree),
                Rule::content => parse_token!(content: pair => tree),
                Rule::filter => parse_token!(filter: pair => tree),
                Rule::function => parse_token!(fn: pair => tree),
                Rule::value => parse_token!(value: pair => tree),
                Rule::number_lit => parse_token!(number: pair => tree),
                Rule::true_lit => parse_token!(true => tree),
                Rule::false_lit => parse_token!(false => tree),
                Rule::string_lit => parse_token!(str ' ': pair => tree),
                Rule::null_lit => parse_token!(nil => tree),
                Rule::array_lit => parse_token!(array: pair => tree),
                Rule::map_lit => parse_token!(map: pair => tree),
                Rule::kw_if => parse_token!(op: IfThen => tree),
                Rule::op_add => parse_token!(op: Add => tree),
                Rule::op_sub => parse_token!(op: Subtract => tree),
                Rule::op_div => parse_token!(op: Divide => tree),
                Rule::op_mlt => parse_token!(op: Multiply => tree),
                Rule::op_mod => parse_token!(op: Modulus => tree),
                Rule::op_and => parse_token!(op: And => tree),
                Rule::op_or => parse_token!(op: Or => tree),
                Rule::op_eq => parse_token!(op: Equals => tree),
                Rule::op_ne => parse_token!(op: NotEquals => tree),
                Rule::op_gt => parse_token!(op: GreaterThan => tree),
                Rule::op_gte => parse_token!(op: GreaterThanEquals => tree),
                Rule::op_lt => parse_token!(op: LessThan => tree),
                Rule::op_lte => parse_token!(op: LessThanEquals => tree),
                Rule::op_cat => parse_token!(op: Concat => tree),
                Rule::op_set => parse_token!(op: Set => tree),
                Rule::EOI | Rule::ctrl_block_end_if => tree.finish_op()?,
                _ => parse_token!(!pair),
            }
        }
        Ok(tree.into_node()?)
    }
}
