use comrak;
use glob::glob;
use inflector::Inflector;
use serde_derive::{Deserialize, Serialize};
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use std::time::SystemTime;

#[derive(Clone, Debug, Serialize, Deserialize)]
/// Structure for representing a wish note
pub struct Note {
    /// The ID of the note (unique string)
    pub id: String,
    /// The index of the note (unique number)
    pub index: i8,
    /// The raw note data
    pub data: String,
    /// The time the note was last modified
    pub mtime: SystemTime,
    /// The name of the note, i.e. the person it is for
    pub name: String,
    /// The path to the note file
    path: PathBuf,
}

impl Note {
    pub fn to_html(&self) -> String {
        let mut options = comrak::ComrakOptions::default();
        options.ext_strikethrough = true;
        options.ext_tagfilter = true;
        options.ext_autolink = true;
        options.ext_tasklist = true;

        comrak::markdown_to_html(&self.data, &options)
    }

    pub fn update_data(&mut self, data: &String) {
        let mut file = File::create(&self.path).expect(&format!(
            "Cannot open note file: {}",
            self.path.to_str().unwrap()
        ));
        file.write_all(data.as_bytes()).expect(&format!(
            "Cannot write note file: {}",
            self.path.to_str().unwrap()
        ));

        self.data = data.clone();
        let metadata = file.metadata().unwrap();
        self.mtime = metadata.modified().unwrap();
    }

    pub fn load_all(note_path: Option<&str>) -> Vec<Self> {
        let mut notes: Vec<Note> = vec![];
        let mut index = 0;
        let path_glob = match note_path {
            Some(dir) => format!("{}/notes/*.note", dir),
            None => format!("notes/*.note"),
        };
        for entry in glob(path_glob.as_str()).unwrap().filter_map(Result::ok) {
            let file_name = entry.file_name().unwrap().to_str().unwrap();
            let name = match file_name.find('.') {
                Some(index) => &file_name[0..index],
                None => "unknown",
            };
            let mut data = String::new();
            let mut file =
                File::open(&entry).expect(&format!("Cannot open note file: {}", file_name));
            file.read_to_string(&mut data)
                .expect(&format!("Cannot read note file: {}", file_name));
            let metadata = file
                .metadata()
                .expect(&format!("Cannot get metadata of note file: {}", file_name));

            let note = Note {
                id: String::from(name),
                index: index,
                data: data,
                mtime: metadata.modified().unwrap(),
                name: String::from(name).to_title_case(),
                path: entry.clone(),
            };
            notes.push(note);
            index += 1;
        }
        notes
    }
}

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

    #[test]
    fn loads_all_notes() {
        let notes = Note::load_all(Some("test"));
        let note_ids: Vec<&str> = notes.iter().map(|note| note.id.as_ref()).collect();
        assert_eq!(note_ids, vec!["test", "updatable"]);
    }

    #[test]
    fn converts_to_html() {
        let notes = Note::load_all(Some("test"));
        let note = notes.iter().find(|note| note.id == "test").unwrap();
        assert_eq!(
            note.to_html(),
            r#"<p>This is a test note</p>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
"#
        );
    }

    #[test]
    fn updates_data() {
        let mut notes = Note::load_all(Some("test"));
        let note = notes
            .iter_mut()
            .find(|note| note.id == "updatable")
            .unwrap();
        assert_eq!(note.data, "Some content");

        // Update the data van verify it has changed
        let new_data = "New content";
        note.update_data(&String::from(new_data));
        assert_eq!(note.data, new_data);

        // Verify that the data is written to the file of the note by
        // loading them again
        let mut notes = Note::load_all(Some("test"));
        let note = notes
            .iter_mut()
            .find(|note| note.id == "updatable")
            .unwrap();
        assert_eq!(note.data, new_data);

        // ... and change it back again
        note.update_data(&String::from("Some content"));
    }
}
