use std::io::{self, Write};
use std::path::PathBuf;

use crate::node::Node;

use crate::output::dump::*;
use crate::traits::*;

/// Finds the types of nodes specified in the input slice.
pub(crate) fn find<'a, T: ParserTrait>(parser: &'a T, filters: &[String]) -> Vec<Node<'a>> {
    let filters = parser.get_filters(filters);
    let node = parser.get_root();
    let mut cursor = node.cursor();
    let mut stack = Vec::new();
    let mut good = Vec::new();

    stack.push(node);

    while let Some(node) = stack.pop() {
        if filters.any(&node) {
            good.push(node);
        }
        cursor.reset(&node);
        if cursor.goto_first_child() {
            let start = stack.len();
            loop {
                stack.push(cursor.node());
                if !cursor.goto_next_sibling() {
                    break;
                }
            }
            stack[start..].reverse();
        }
    }
    good
}

/// Configuration options for finding different
/// types of nodes in a code.
#[derive(Debug)]
pub(crate) struct FindCfg {
    /// Path to the file containing the code
    pub(crate) path: PathBuf,
    /// Types of nodes to find
    pub(crate) filters: Vec<String>,
    /// The first line of code considered in the search
    ///
    /// If `None`, the search starts from the
    /// first line of code in a file
    pub(crate) line_start: Option<usize>,
    /// The end line of code considered in the search
    ///
    /// If `None`, the search ends at the
    /// last line of code in a file
    pub(crate) line_end: Option<usize>,
}

#[derive(Debug)]
pub(crate) struct Find {
    _guard: (),
}

impl Callback for Find {
    type Res = std::io::Result<()>;
    type Cfg = FindCfg;

    fn call<T: ParserTrait>(cfg: Self::Cfg, parser: &T) -> Self::Res {
        let good = find(parser, &cfg.filters);
        if !good.is_empty() {
            let mut stdout = io::stdout().lock();
            writeln!(stdout, "In file {}", cfg.path.to_str().unwrap())?;
            for node in good {
                dump_node(parser.get_code(), &node, 1, cfg.line_start, cfg.line_end)?;
            }
            writeln!(stdout)?;
        }
        Ok(())
    }
}
