/*
    Hurry Curry! - a game about cooking
    Copyright 2024 metamuffin
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, version 3 of the License only.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.
    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see .
*/
#![feature(never_type)]
use anyhow::Result;
use http_body_util::Full;
use hurrycurry_protocol::registry::Entry;
use hyper::{
    body::Bytes, header::HeaderValue, server::conn::http1, service::service_fn, Response,
    StatusCode,
};
use hyper_util::rt::TokioIo;
use log::warn;
use mdns_sd::{ServiceDaemon, ServiceEvent};
use std::{cmp::Reverse, collections::HashMap, sync::Arc};
use tokio::{net::TcpListener, spawn, sync::RwLock};
fn main() -> Result<()> {
    env_logger::init_from_env("LOG");
    tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()?
        .block_on(async_main())?;
}
async fn async_main() -> Result {
    let mdns = ServiceDaemon::new()?;
    let mdns_events = mdns.browse("_hurrycurry._tcp.local.")?;
    let entries = Arc::new(RwLock::new(HashMap::::new()));
    let entries2 = entries.clone();
    spawn(async move {
        while let Ok(event) = mdns_events.recv_async().await {
            match event {
                ServiceEvent::ServiceResolved(service_info) => {
                    entries2.write().await.insert(
                        service_info.get_fullname().to_owned(),
                        Entry {
                            name: service_info
                                .get_fullname()
                                .strip_suffix("._hurrycurry._tcp.local.")
                                .unwrap_or(service_info.get_fullname())
                                .to_owned(),
                            address: service_info
                                .get_addresses()
                                .into_iter()
                                .map(|a| format!("ws://{a}:{}", service_info.get_port()))
                                .collect(),
                            players_online: service_info
                                .get_property_val_str("players")
                                .and_then(|p| p.parse().ok())
                                .unwrap_or(0),
                            last_game: 0,
                            version: service_info
                                .get_property_val_str("protocol")
                                .and_then(|v| {
                                    let (maj, min) = v.split_once(".")?;
                                    Some((maj.parse().ok()?, min.parse().ok()?))
                                })
                                .unwrap_or((0, 0)),
                        },
                    );
                }
                ServiceEvent::ServiceRemoved(_, fullname) => {
                    entries2.write().await.remove(&fullname);
                }
                _ => (),
            }
        }
    });
    let listener = TcpListener::bind("127.0.0.1:27035").await?;
    loop {
        let (stream, _) = listener.accept().await?;
        let entries = entries.clone();
        spawn(async move {
            if let Err(e) = http1::Builder::new()
                .serve_connection(
                    TokioIo::new(stream),
                    service_fn(move |_req| {
                        let entries = entries.clone();
                        async move {
                            Ok::<_, !>(match _req.uri().path() {
                                "/" => Response::new(Full::new(Bytes::from(
                                    "Hurry Curry! local discovery service",
                                ))),
                                "/v1/list" => {
                                    let mut ents = entries
                                        .read()
                                        .await
                                        .clone()
                                        .into_values()
                                        .collect::>();
                                    ents.sort_by_key(|e| Reverse(e.players_online));
                                    let mut res = Response::new(Full::new(Bytes::from(
                                        serde_json::to_string(&ents).unwrap(),
                                    )));
                                    *res.status_mut() = StatusCode::OK;
                                    res.headers_mut().insert(
                                        "content-type",
                                        HeaderValue::from_static("application/json"),
                                    );
                                    res
                                }
                                _ => {
                                    let mut res =
                                        Response::new(Full::new(Bytes::from("not found")));
                                    *res.status_mut() = StatusCode::NOT_FOUND;
                                    res
                                }
                            })
                        }
                    }),
                )
                .await
            {
                warn!("conn failed: {e}");
            }
        });
    }
}