use comrak;
use glob::glob;
use inflector::Inflector;
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 list
pub struct List {
    /// The ID of the list (unique string)
    pub id: String,
    /// The index of the list (unique number)
    pub index: i8,
    /// The raw list data
    pub data: String,
    /// The time the list was last modified
    pub mtime: SystemTime,
    /// The name of the list, i.e. the person it is for
    pub name: String,
    /// The path to the list file
    path: PathBuf
}

impl List {
    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 list file {}",
                self.path.to_str().unwrap()));
        file.write_all(data.as_bytes())
            .expect(&format!("Cannot write list file {}",
                self.path.to_str().unwrap()));

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

    pub fn load_all(list_path: Option<&str>) -> Vec<Self> {
        let mut lists : Vec<List> = vec![];
        let mut index = 0;
        let path_glob = match list_path {
            Some(dir) => format!("{}/lists/*.list", dir),
            None      => format!("lists/*.list")
        };
        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 list file {}", file_name));
            file.read_to_string(&mut data)
                .expect(&format!("Cannot read list file {}", file_name));
            let metadata = file.metadata()
                .expect(&format!("Cannot get metadata of list file {}", file_name));

            let mut list = List {
                id: String::from(name),
                index : index,
                data: data,
                mtime: metadata.modified().unwrap(),
                name: String::from(name).to_title_case(),
                path: entry.clone()
            };
            lists.push(list);
            index += 1;
        }
        lists
    }
}

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

    #[test]
    fn loads_all_lists() {
        let lists = List::load_all(Some("test"));
        let list_ids: Vec<&str> = lists.iter()
            .map(|list| list.id.as_ref()).collect();
        assert_eq!(list_ids, vec!["test", "updatable"]);
    }

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

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

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

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

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