#[macro_use]
extern crate serde_json;

use async_std::task;
use async_std::net::{TcpListener, TcpStream};
use futures::prelude::*;
use glob::glob;
use handlebars::Handlebars;
use log::*;
use rodio::Source;
use serde::Deserialize;
use tide::{Body, Request, StatusCode};

use std::fs::File;
use std::io::BufReader;

#[derive(Debug, Deserialize)]
struct Query {
    admin: String,
}

async fn index(req: Request<()>) -> Result<Body, tide::Error> {
    let mut hb = Handlebars::new();

    if let Err(failure) = hb.register_templates_directory(".hbs", "views") {
        error!("Failed to render: {:?}", failure);
        return Err(tide::Error::from_str(StatusCode::InternalServerError,  "Could not render templates"))
    }

    let mut admin = false;
    if let Ok(query) = req.query::<Query>() {
        info!("admin: {:?}", query);
        admin = query.admin == "rtyler";
    }

    let mut sounds = vec![];

    for sound in glob("sounds/*.wav").expect("Failed to glob sounds") {
        if let Ok(sound) = sound {
            if let Some(name) = sound.as_path().file_stem() {
                // factory whistle is a special admin only sound ^_^
                if name != "factorywhistle" {
                    sounds.push(name.to_os_string().into_string().unwrap());
                }
            }
        }
    }
    info!("sounds: {:?}", sounds);

    let view = hb.render("index",
        &json!({"sounds": sounds, "admin" : admin}))
        .expect("Failed to render");
    let mut body = Body::from_string(view);
    body.set_mime("text/html");
    Ok(body)
}

async fn play(req: Request<()>) -> tide::Result {
    if let Ok(sound) = req.param::<String>("sound") {
        let device = rodio::default_output_device().unwrap();
        let file = File::open(format!("sounds/{}.wav", sound)).unwrap();
        let source = rodio::Decoder::new(BufReader::new(file)).unwrap();
        rodio::play_raw(&device, source.convert_samples());
        Ok(tide::Redirect::new("/").into())
    }
    else {
        Err(tide::Error::from_str(StatusCode::NotFound,  "Could not find sound"))
    }
}

async fn handle_websocket(stream: TcpStream) {
    let addr = stream
        .peer_addr()
        .expect("connected streams should have a peer address");
    info!("Peer address: {}", addr);

    let ws_stream = async_tungstenite::accept_async(stream)
        .await
        .expect("Error during the websocket handshake occurred");

    info!("New WebSocket connection: {}", addr);

    let (write, read) = ws_stream.split();
    read.forward(write)
        .await
        .expect("Failed to forward message")
}

#[async_std::main]
async fn main() -> Result<(), tide::Error> {
    pretty_env_logger::init();

    let mut app = tide::new();
    app.at("/play/:sound").post(play);
    app.at("/").get(index);
    app.at("/assets").serve_dir("assets/");

    task::spawn(async move {
        let addr = "0.0.0.0:9078";
        // Create the event loop and TCP listener we'll accept connections on.
        let try_socket = TcpListener::bind(&addr).await;
        let listener = try_socket.expect("Failed to bind");
        info!("Listening on: {}", addr);

        while let Ok((stream, _)) = listener.accept().await {
            task::spawn(handle_websocket(stream));
        }
    });

    Ok(app.listen("0.0.0.0:9077").await?)
}
