mod error;
mod live;
mod posts;
mod scrobbles;
mod skweets;

use std::borrow::Cow;
use std::collections::HashSet;

use atom_syndication::Feed;
use rocket::fs::{relative, FileServer};
use rocket::http::{ContentType, Status};
use rocket::{Request, State};
use rocket_dyn_templates::{context, Template};

use error::BlossomError;

type Result<T> = std::result::Result<T, BlossomError>;

struct Clients {
    listenbrainz: listenbrainz::raw::Client,
    skinnyverse: mastodon_async::Mastodon,
    reqwest: reqwest::Client,
}

#[macro_use]
extern crate rocket;

#[get("/")]
async fn home(clients: &State<Clients>) -> Template {
    let (live, listenbrainz, skweets, blogposts) = tokio::join!(
        live::get_live_status(&clients.reqwest),
        scrobbles::get_now_playing(&clients.listenbrainz),
        skweets::get_recents(&clients.skinnyverse),
        posts::get_blogposts()
    );
    let is_live = live.unwrap_or_default().online;
    let listenbrainz = listenbrainz.unwrap_or_default();
    let skweets = skweets.unwrap_or_default();
    let blogposts = blogposts.unwrap_or_default();
    Template::render(
        "home",
        context! {
            is_live,
            listenbrainz,
            skweets,
            blogposts,
        },
    )
}

#[get("/blog/<blogpost>")]
async fn blogpost(blogpost: &str) -> Result<Template> {
    let mut blogpost = posts::get_blogpost(blogpost).await?;
    blogpost.render().await?;
    Ok(Template::render(
        "blogpost",
        context! {
            blogpost,
        },
    ))
}

#[get("/blog?<filter>")]
async fn blog(filter: Vec<String>) -> Result<Template> {
    let mut blogposts = posts::get_blogposts().await?;
    let tags: Vec<String> = posts::get_tags(&blogposts)
        .into_iter()
        .map(|tag| tag.to_owned())
        .collect();
    let mut filter_hashset: HashSet<String> = HashSet::new();
    if !filter.is_empty() {
        filter_hashset.extend(filter.into_iter());
        blogposts = posts::filter_by_tags(blogposts, &filter_hashset);
    }
    for blogpost in &mut blogposts {
        blogpost.render().await?;
    }
    let reverse = true;
    Ok(Template::render(
        "blog",
        context! {
            reverse,
            tags,
            filter_hashset,
            blogposts,
        },
    ))
}

#[get("/feed")]
async fn feed() -> Result<(Status, (ContentType, String))> {
    let posts = posts::get_blogposts().await?;
    let feed = posts::syndication::atom(posts).await;
    let feed: String = String::from_utf8(feed.write_to(Vec::new())?)?;
    Ok((
        Status::new(200),
        (ContentType::new("application", "atom+xml"), feed),
    ))
}

#[get("/contact")]
async fn contact() -> Template {
    Template::render("contact", context! {})
}

#[get("/plants")]
async fn plants() -> Result<Template> {
    Err(BlossomError::Unimplemented(Status::NotImplemented))
}

#[catch(default)]
fn catcher(status: Status, req: &Request) -> Template {
    let message;
    if status.code == 404 {
        message = "i either haven't built this page yet or it looks like you're a little lost";
    } else if status.code == 500 {
        message = "omg the server went kaputt!!";
    } else if status.code == 501 {
        message = "it looks like this is not yet here!!!";
    } else {
        message = "idk i got bored";
    }
    let status = format!("{}", status);
    Template::render(
        "error",
        context! { status: status, req: req.uri(), message: message },
    )
}

#[tokio::main]
async fn main() -> std::result::Result<(), rocket::Error> {
    let mut skinny_data = mastodon_async::Data::default();
    skinny_data.base = Cow::from("https://skinnyver.se");

    let _rocket = rocket::build()
        .manage(Clients {
            listenbrainz: listenbrainz::raw::Client::new(),
            skinnyverse: mastodon_async::Mastodon::from(skinny_data),
            reqwest: reqwest::Client::new(),
        })
        .attach(Template::custom(|engines| {
            engines.tera.autoescape_on(vec![]);
        }))
        .mount("/", routes![home, contact, blog, blogpost, feed, plants])
        .register("/", catchers![catcher])
        .mount("/", FileServer::from("./static"))
        .launch()
        .await?;

    Ok(())
}
