// bibiman - a TUI for managing BibLaTeX databases
// Copyright (C) 2024  lukeflo
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
/////

use super::BibiData;
use nucleo_matcher::{
    Config, Matcher,
    pattern::{CaseMatching, Normalization, Pattern},
};
use std::{collections::HashMap, ffi::OsStr, fs, path::PathBuf};

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum SearchField {
    Author,
    Title,
    Abstract,
    CustomColumn,
    #[default]
    All,
}

impl AsRef<str> for SearchField {
    fn as_ref(&self) -> &str {
        match self {
            Self::Author => "Author",
            Self::Title => "Title",
            Self::Abstract => "Abstract",
            Self::CustomColumn => "Custom column",
            _ => "All fields",
        }
    }
}

#[derive(Debug, Default, PartialEq, Eq)]
pub struct BibiSearch {
    pub search_string: String, // Search string show in footer, used for search
    pub inner_search: bool,    // True, if we trigger a search for already filtered list
    pub filtered_tag_list: Vec<String>,
    search_field: SearchField,
}

impl BibiSearch {
    // Stringify EntryTableItem by joining/concat
    fn convert_to_string(inner_vec: &BibiData) -> String {
        format!(
            "{} {} {} {} {} {}",
            &inner_vec.authors,
            &inner_vec.title,
            &inner_vec.year,
            &inner_vec.custom_field_value(),
            &inner_vec.keywords.join(" "),
            &inner_vec.citekey
        )
    }

    // Return a filtered entry list
    pub fn search_entry_list(
        &self,
        search_pattern: &str,
        orig_list: Vec<BibiData>,
    ) -> Vec<BibiData> {
        // Create a hashmap to connect stingified entry with entry vec
        let mut entry_string_hm: HashMap<String, Vec<BibiData>> = HashMap::new();

        // Convert all entries to string and insert them into the hashmap
        // next to the original inner Vec<String> of the entry list
        for entry in orig_list {
            let key = match self.search_field {
                SearchField::All => Self::convert_to_string(&entry),
                SearchField::Author => entry.authors.clone(),
                SearchField::Title => entry.title.clone(),
                SearchField::Abstract => entry.abstract_text.clone(),
                SearchField::CustomColumn => entry.custom_field.1.clone(),
            };
            match entry_string_hm.insert(key.clone(), vec![entry]) {
                None => {}
                Some(mut val) => {
                    entry_string_hm.get_mut(&key).unwrap().append(&mut val);
                }
            };
        }

        // Set up matcher (TODO: One time needed only, move to higher level)
        let mut matcher = Matcher::new(Config::DEFAULT);

        // Filter the stringified entries and collect them into a vec
        let filtered_matches: Vec<String> = {
            let matches =
                Pattern::parse(search_pattern, CaseMatching::Ignore, Normalization::Smart)
                    .match_list(entry_string_hm.keys(), &mut matcher);
            matches.into_iter().map(|f| f.0.to_string()).collect()
        };

        // Create filtered entry list and push the inner entry vec's to it
        // Use the filtered stringified hm-key as index
        let mut filtered_list: Vec<BibiData> = Vec::new();
        for m in filtered_matches {
            for entrie in entry_string_hm[&m].clone() {
                filtered_list.push(entrie);
            }
        }
        filtered_list.sort();
        filtered_list
    }

    pub fn search_tag_list(search_pattern: &str, orig_list: Vec<String>) -> Vec<String> {
        // Set up matcher (TODO: One time needed only)
        let mut matcher = Matcher::new(Config::DEFAULT);

        // Filter the list items by search pattern
        let filtered_matches: Vec<String> = {
            let matches =
                Pattern::parse(search_pattern, CaseMatching::Ignore, Normalization::Smart)
                    .match_list(orig_list, &mut matcher);
            matches.into_iter().map(|f| f.0.to_string()).collect()
        };
        filtered_matches
    }

    pub fn filter_entries_by_tag(keyword: &str, orig_list: &[BibiData]) -> Vec<BibiData> {
        let mut filtered_list: Vec<BibiData> = Vec::new();

        // Loop over the whole given entry table
        // Check if the selected keyword is present in the current entry
        // If present, push the entry to the filtered list
        for e in orig_list {
            if e.keywords.contains(&keyword.to_string()) {
                filtered_list.push(e.to_owned());
            }
        }

        filtered_list
    }

    pub(crate) fn set_search_field(&mut self, field: SearchField) {
        self.search_field = field;
    }

    pub(crate) fn search_field(&self) -> &SearchField {
        &self.search_field
    }
}

pub fn search_pattern_in_file<'a>(pattern: &str, file: &'a PathBuf) -> Option<&'a OsStr> {
    let content = fs::read_to_string(file).unwrap();

    if content.contains(pattern) {
        Some(file.as_os_str())
    } else {
        None
    }
}

#[cfg(test)]
mod tests {
    use std::ffi::OsString;

    use crate::bibiman::bibisetup::CustomField;

    use super::*;

    #[test]
    fn test_vector_join() {
        let bibvec: BibiData = BibiData {
            id: 3,
            authors: "Author".to_string(),
            short_author: "".to_string(),
            title: "Title".to_string(),
            year: "1999".to_string(),
            custom_field: (CustomField::Pubtype, "article".to_string()),
            keywords: vec!["hello".to_string(), "bye".to_string()],
            citekey: "author_1999".to_string(),
            abstract_text: "An abstract with multiple sentences. Here is the second".to_string(),
            doi_url: Some("https://www.bibiman.org".to_string()),
            filepath: Some(vec![OsString::from("/home/file/path.pdf")]),
            file_field: true,
            subtitle: None,
            notes: None,
            symbols: [None, None, None],
            sanitized_bibi_data: None,
        }
        .gen_sanitized();

        let joined_vec = BibiSearch::convert_to_string(&bibvec);

        assert_eq!(
            joined_vec,
            "Author Title 1999 article hello bye author_1999"
        )
    }
}
