//! # My crate
//! Hello how do you do

use anyhow::{Context, Result};
use std::env;
use std::fs;

#[derive(Debug)]
pub struct Config {
    pub query: String,
    pub filepath: String,
    pub ignore_case: bool,
}

impl Config {
    pub fn default() -> Config {
        return Config{
            query: String::from(""),
            filepath: String::from(""),
            ignore_case: env::var("IGNORE_CASE").is_ok(),
        };
    }
    pub fn new(args: impl Iterator<Item = String>) -> Result<Config> {
        let mut args_iter = args.into_iter();
        let usage_str = "Usage is: cargo run -- {{query}} {{filepath}}";
        // Get rid of the first argument since it's the filename
        let _ = args_iter.next().context(usage_str)?;
        let query = args_iter.next().context(usage_str)?;
        let filepath = args_iter.next().context(usage_str)?;

        return Ok(Config{
            query: query,
            filepath: filepath,
            ignore_case: env::var("IGNORE_CASE").is_ok(),
        });
    }
    pub fn build(args: impl Iterator<Item = String>) -> Result<Config> {
        return Config::new(args);
    }
}

pub fn search<'a>(query: &str, contents: &'a str, ignore_case: bool) -> Vec<&'a str> {
    let qq;
    if ignore_case {
        qq = query.to_lowercase();
    } else {
        qq = String::from(query);
    }
    // let mut vv: Vec<&str> = vec![];
    // for line in contents.split("\n") {
    //     let query_line;
    //     if ignore_case {
    //         query_line = line.to_lowercase();
    //     } else {
    //         query_line = String::from(line) 
    //     }
    //     if query_line.contains(&qq) {
    //         vv.push(line);
    //     }
    // }

    return contents
        .lines()
        .filter(|line| {
            if ignore_case {
                line.to_lowercase().contains(&qq)
            } else {
                line.contains(&qq)
            }
        })
        .collect();

    // for line in contents.lines() {
    //     if line.contains(query) {
    //         vv.push(line);
    //     }
    // }

    // return vv;
}

// impl<T> Option<T> {
//     pub fn unwrap_or_else<F>(self, f: F) -> T
//     where F: FnOnce() -> T {
//         match self {
//             Some(val) => val,
//             None => f(),
//         }
//     }
// }

pub fn search_iter<'a>(query: &str, contents: &'a str, ignore_case: bool) -> impl Iterator<Item = &'a str> {
    let qq;
    if ignore_case {
        qq = query.to_lowercase();
    } else {
        qq = String::from(query);
    }

    return contents
        .lines()
        // Very tricky. With move, qq is transferred into a secret object represnting the
        // closure. As long as the iterator lives, the secret object lives, and thus qq lives.
        // The closure can be called multiple times because the secret object lives past each call.
        .filter(move |line| {
            let qq_capture = &qq;
            if ignore_case {
                line.to_lowercase().contains(qq_capture)
            } else {
                line.contains(qq_capture)
            }
        });
}

pub fn run(config: Config) -> Result<()> {
    let contents = fs::read_to_string(config.filepath)
        .context("Couldn't read file").context("hello how").context("howdy doody")?;
    // println!("{contents}");

    for line in search_iter(&config.query, &contents, config.ignore_case) {
        println!("{line}");
    }

    return Ok(());
}

/// Adds one to the number
/// Examples:
/// ```
/// let x = 1;
/// let y = nader::add_one(x);
/// assert_eq!(2, y);
/// ```
pub fn add_one(x: i32) -> i32 {
    return x + 1;
}

// use std::io;
// use std::fmt;

// struct ErrStr {
//     ss: String,
// }

// impl From<io::Error> for ErrStr {
//     fn from(error: io::Error) -> Self {
//         ErrStr { 
//             ss: error.to_string()
//         }
//     }
// }

// impl fmt::Display for ErrStr {
//     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
//         write!(f, "{}", self.ss)
//     }
// }

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

    #[test]
    fn case_sensitive() {
        let contents = "\
hi good sir
hello how do you do
well howdy doody";
        let empty: Vec<&str> = vec![];
        assert_eq!(vec!["hello how do you do"], search("hello", &contents, false));
        assert_eq!(empty, search("hEllo", &contents, false));
        assert_eq!(vec!["hello how do you do", "well howdy doody"], search("do", &contents, false));

        {
            let res: Vec<_> = search_iter("hello", &contents, false).collect();
            assert_eq!(vec!["hello how do you do"], res);
        }
        {
            let res: Vec<_> = search_iter("hEllo", &contents, false).collect();
            assert_eq!(empty, res);
        }
        {
            let res: Vec<_> = search_iter("do", &contents, false).collect();
            assert_eq!(vec!["hello how do you do", "well howdy doody"], res);
        }
    }

    #[test]
    fn case_insensitive() {
        let contents = "\
hi good sir
hEllo how do you do
well howdy doody";

        assert_eq!(vec!["hEllo how do you do"], search("hello", &contents, true));
        assert_eq!(vec!["hEllo how do you do"], search("HELLO", &contents, true));
        assert_eq!(vec!["hEllo how do you do", "well howdy doody"], search("do", &contents, true));
        assert_eq!(vec!["hi good sir"], search("GOOD", &contents, true));


        {
            let res: Vec<_> = search_iter("hello", &contents, true).collect();
            assert_eq!(vec!["hEllo how do you do"], res);
        }
        {
            let res: Vec<_> = search_iter("HELLO", &contents, true).collect();
            assert_eq!(vec!["hEllo how do you do"], res);
        }
        {
            let res: Vec<_> = search_iter("do", &contents, true).collect();
            assert_eq!(vec!["hEllo how do you do", "well howdy doody"], res);
        }
        {
            let res: Vec<_> = search_iter("GOOD", &contents, true).collect();
            assert_eq!(vec!["hi good sir"], res);
        }
    }

    #[test]
    fn confg_errors() {
        let args0: Vec<String> = vec![];
        let args1: Vec<String> = vec![String::from("hi")];
        let args2: Vec<String> = vec![String::from("hi"), String::from("hello")];
        let args_missing_file: Vec<String> = vec!["hi".to_string(), "hello".to_string(), "nonexisted_file".to_string()];
        {
            // This panics if it's Ok. Returns the error if it's Err.
            let err = Config::build(args0.into_iter()).expect_err("Should fail");
            // This shows the actual error string if the match fails
            assert!(format!("{err:?}").contains("Usage"), "Wrong error: {:?}", err);
        }
        {
            let err = Config::build(args1.into_iter()).expect_err("Should fail");
            assert!(format!("{err:?}").contains("Usage"), "Wrong error: {:?}", err);
        }
        {
            let err = Config::build(args2.into_iter()).expect_err("Should fail");
            assert!(format!("{err:?}").contains("Usage"), "Wrong error: {:?}", err);
        }
        {
            let config = Config::build(args_missing_file.into_iter()).expect("Should produce config");
            let err = run(config).expect_err("Should error");
            assert!(format!("{err:?}").contains("No such file"), "Wrong error: {:?}", err);
        }
        
    }
}