use flow::Flow;
use music_player::{
    core::{ControlMsg, Core},
    database::{MemoryIndex, MusicIndex},
    media::{TrackId, TrackInfo},
    scanner::FileScanner,
    Error, FatalError,
};
use serde::{Deserialize, Serialize};
use std::{
    net::{IpAddr, Ipv4Addr, SocketAddr},
    path::PathBuf,
    sync::{Arc, RwLock},
    thread,
};
use warp::Filter;

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct TrackRequest {
    id: String,
}

#[derive(Serialize)]
#[serde(tag = "type", content = "content")]
enum Response<A: Serialize> {
    Success(A),
    Failure(String),
    Fatal(String),
}

impl<A: Serialize> From<Result<A, Error>> for Response<A> {
    fn from(res: Result<A, Error>) -> Self {
        match res {
            Ok(val) => Self::Success(val),
            Err(err) => Self::Failure(format!("{}", err)),
        }
    }
}

impl<A: Serialize> From<Flow<A, FatalError, Error>> for Response<A> {
    fn from(res: Flow<A, FatalError, Error>) -> Self {
        match res {
            Flow::Ok(val) => Self::Success(val),
            Flow::Fatal(fatal) => Self::Fatal(format!("{}", fatal)),
            Flow::Err(err) => Self::Failure(format!("{}", err)),
        }
    }
}

struct Static(PathBuf);

impl Static {
    fn read(self, root: PathBuf) -> String {
        let mut path = root;
        path.push(self.0);
        std::fs::read_to_string(path.clone()).expect(&format!("to find {:?}", path))
    }
}

#[tokio::main]
pub async fn main() {
    let dev = std::env::var("DEV")
        .ok()
        .and_then(|v| v.parse::<bool>().ok())
        .unwrap_or(false);
    let bundle_root = std::env::var("BUNDLE_ROOT")
        .map(|b| PathBuf::from(b))
        .unwrap();
    let music_root = std::env::var("MUSIC_ROOT")
        .map(|b| PathBuf::from(b))
        .unwrap();

    match gstreamer::init() {
        Ok(()) => (),
        Err(err) => panic!("failed to initialize gstreamer: {}", err),
    }

    let index = Arc::new(MemoryIndex::new());
    let scanner = Arc::new(FileScanner::new(vec![music_root.clone()]));
    let core = match Core::new(index.clone(), scanner) {
        Ok(core) => Arc::new(RwLock::new(core)),
        Err(error) => panic!("core failed to initialize: {}", error),
    };

    println!("config: {:?} {:?} {:?}", dev, bundle_root, music_root);

    let root = warp::path!().and(warp::get()).map({
        let bundle_root = bundle_root.clone();
        move || {
            warp::http::Response::builder()
                .header("content-type", "text/html")
                .body(Static(PathBuf::from("index.html")).read(bundle_root.clone()))
        }
    });
    let assets = warp::path!(String).and(warp::get()).map({
        let bundle_root = bundle_root.clone();
        move |filename: String| {
            let mime_type = mime_guess::from_path(filename.clone())
                .first()
                .map(|m| m.essence_str().to_owned())
                .unwrap_or("text/plain".to_owned());
            println!("mime_type: {:?}", mime_type);
            warp::http::Response::builder()
                .header("content-type", mime_type)
                .body(Static(PathBuf::from(filename)).read(bundle_root.clone()))
        }
    });

    /*
    let devices = warp::path!("api" / "v1" / "devices")
        .and(warp::get())
        .map(|| {
            let conn = Connection::new_session().expect("to connect to dbus");
            warp::reply::json(&list_devices(conn))
        });
        */

    let track_list = warp::path!("api" / "v1" / "tracks").and(warp::get()).map({
        let index = index.clone();
        move || {
            warp::reply::json(&Response::from(
                index
                    .list_tracks()
                    .map_err(|db_err| Error::DatabaseError(db_err)),
            ))
        }
    });

    let play_track = warp::path!("api" / "v1" / "play")
        .and(warp::post())
        .and(warp::body::json())
        .map({
            let core = core.clone();
            move |body: TrackRequest| {
                warp::reply::json(&Response::from(
                    core.write().unwrap().play_track(TrackId::from(body.id)),
                ))
            }
        });

    let stop_playback = warp::path!("api" / "v1" / "stop").and(warp::post()).map({
        let core = core.clone();
        move || warp::reply::json(&Response::from(core.write().unwrap().stop_playback()))
    });

    /*
    let tracks_for_artist = warp::path!("api" / "v1" / "artist" / String)
        .and(warp::get())
        .map(|_artist: String| warp::reply::json(&tracks()));
    let tracks_for_album = warp::path!("api" / "v1" / "album" / String)
        .and(warp::get())
        .map(|_album: String| warp::reply::json(&tracks()));

    let queue = warp::path!("api" / "v1" / "queue")
        .and(warp::get())
        .map(|| {
            let conn = Connection::new_session().expect("to connect to dbus");
            warp::reply::json(&list_tracks(conn))
        });

    let playing_status = warp::path!("api" / "v1" / "play-pause")
        .and(warp::get())
        .map(|| warp::reply::json(&PlayPause::Paused));

    let routes = devices
        .or(track_list)
        .or(tracks_for_album)
        .or(tracks_for_artist)
        .or(queue)
        .or(playing_status);
        */
    let routes = root
        .or(assets)
        .or(track_list)
        .or(play_track)
        .or(stop_playback);
    let server = warp::serve(routes);
    server
        .run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002))
        .await;
}
