use rocket::State;
use rocket_contrib::Json;
use super::super::NoteStore;
use super::super::models::note::Note;

#[get("/", format = "application/json")]
fn index(notes: State<NoteStore>) -> Option<Json<Vec<Note>>> {
    let notes = notes.read().unwrap();
    Some(Json(notes.clone()))
}

#[get("/<note_id>", format = "text/html")]
fn show_html(note_id: String, notes: State<NoteStore>) -> Option<String> {
    let notes = notes.read().unwrap();
    let note = notes.iter().find(|note| note.id == note_id)?;
    Some(note.to_html())
}

#[get("/<note_id>", format = "application/json")]
fn show_json(note_id: String, notes: State<NoteStore>) -> Option<Json<Note>> {
    let notes = notes.read().unwrap();
    let note = notes.iter().find(|note| note.id == note_id)?;
    Some(Json(note.clone()))
}

#[put("/<note_id>", format = "application/json", data = "<new_note>")]
fn update(note_id: String, new_note: Json<Note>, notes: State<NoteStore>) -> Option<Json<Note>> {
    let mut notes = notes.write().unwrap();
    let note = notes.iter_mut().find(|note| note.id == note_id)?;
    note.update_data(&new_note.data);
    Some(Json(note.clone()))
}

#[cfg(test)]
mod tests {
    use rocket;
    use rocket::http::{Accept, ContentType, Status};
    use rocket::local::Client;
    use serde_json;
    use super::*;

    #[test]
    fn index() {
        let client = Client::new(rocket(Some("test"))).unwrap();

        // Try to get all the notes
        let mut res = client.get("/notes").header(Accept::JSON).dispatch();
        assert_eq!(res.status(), Status::Ok);

        let body = res.body_string().unwrap();
        let notes = serde_json::from_str::<Vec<Note>>(body.as_str()).unwrap();
        assert_eq!(notes[0].id, "test");
        assert_eq!(notes[0].index, 0);
        assert_eq!(notes[0].name, "Test");
        assert_eq!(
            notes[0].data,
            "This is a test note\n\n* One\n* Two\n* Three\n"
        );
        // The mtime field can vary, don't test for it
        // The path field is private, also don't test for it

        // Cannot get the notes in HTML format
        let res = client.get("/notes").header(Accept::HTML).dispatch();
        assert_eq!(res.status(), Status::NotFound);
    }

    #[test]
    fn show_html() {
        let client = Client::new(rocket(Some("test"))).unwrap();

        // Try to get the note and verify the body
        let mut res = client.get("/notes/test").header(Accept::HTML).dispatch();
        assert_eq!(res.status(), Status::Ok);

        let body = res.body_string().unwrap();
        assert_eq!(
            body,
            r#"<p>This is a test note</p>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
"#
        );
    }

    #[test]
    fn show_json() {
        let client = Client::new(rocket(Some("test"))).unwrap();

        // Try to get the note and verify the body
        let mut res = client.get("/notes/test").header(Accept::JSON).dispatch();
        assert_eq!(res.status(), Status::Ok);

        let body = res.body_string().unwrap();
        let note = serde_json::from_str::<Note>(body.as_str()).unwrap();
        assert_eq!(note.id, "test");
        assert_eq!(note.index, 0);
        assert_eq!(note.name, "Test");
        assert_eq!(note.data, "This is a test note\n\n* One\n* Two\n* Three\n");
        // The mtime field can vary, don't test for it
        // The path field is private, also don't test for it

        // Try to get a note that doesn't exist
        let res = client
            .get("/notes/doesntexit")
            .header(Accept::JSON)
            .dispatch();
        assert_eq!(res.status(), Status::NotFound);

        // FIXME: Test that there is some kind of error in the JSON
    }

    #[test]
    fn update() {
        let client = Client::new(rocket(Some("test"))).unwrap();

        // Try to get the note and determine what to change it to
        let mut res = client
            .get("/notes/updatable")
            .header(Accept::JSON)
            .dispatch();
        let body = res.body_string().unwrap();
        let note = serde_json::from_str::<Note>(body.as_str()).unwrap();
        assert_eq!(note.data, "Some content");

        // Try to change the note data, then verify it was changed
        let new_data = "New content";
        let mut new_json: serde_json::Value = json!({
            "id": "updatable",
            "index": 1,
            "data": new_data,
            "mtime": {
                "secs_since_epoch": 0,
                "nanos_since_epoch": 0
            },
            "name": "Updatable",
            "path": "test/notes/updatablenote"
        });
        let res = client
            .put("/notes/updatable")
            .header(ContentType::JSON)
            .body(new_json.to_string())
            .dispatch();
        assert_eq!(res.status(), Status::Ok);

        let mut res = client
            .get("/notes/updatable")
            .header(Accept::JSON)
            .dispatch();
        let body = res.body_string().unwrap();
        let note = serde_json::from_str::<Note>(body.as_str()).unwrap();
        assert_eq!(note.data, new_data);

        // ... and change it back
        *new_json.get_mut("data").unwrap() = json!("Some content");
        let res = client
            .put("/notes/updatable")
            .header(ContentType::JSON)
            .body(new_json.to_string())
            .dispatch();
        assert_eq!(res.status(), Status::Ok);

        // Try to change a note that doesn't exist
        let res = client
            .put("/notes/doesntexit")
            .header(ContentType::JSON)
            .body(new_json.to_string())
            .dispatch();
        assert_eq!(res.status(), Status::NotFound);

        // Try to change a note without a proper body
        let res = client
            .put("/notes/updatable")
            .header(ContentType::JSON)
            .body(r#"{}"#)
            .dispatch();
        assert_eq!(res.status(), Status::BadRequest);

        // Try to change a note without a proper type (i.e. not JSON)
        let res = client
            .put("/notes/updatable")
            .header(ContentType::Plain)
            .body("foo bar baz")
            .dispatch();
        assert_eq!(res.status(), Status::NotFound);
    }
}
