/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2026 Hurry Curry! Contributors

    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 <https://www.gnu.org/licenses/>.

*/

pub mod benchmark;
pub mod commands;
pub mod entity;
pub mod network;
pub mod scoreboard;
pub mod server;
pub mod state;
pub mod vote;

use hurrycurry_protocol::glam::Vec2;
use rand::random;
use std::fmt::Display;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ConnectionID(pub i64);

impl Display for ConnectionID {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "conn#{}", self.0)
    }
}

pub trait InterpolateExt {
    fn exp_to(&mut self, target: Self, dt: f32);
}
impl InterpolateExt for Vec2 {
    fn exp_to(&mut self, target: Self, dt: f32) {
        *self = target + (*self - target) * (-dt).exp();
    }
}
impl InterpolateExt for f32 {
    fn exp_to(&mut self, target: Self, dt: f32) {
        *self = target + (*self - target) * (-dt).exp();
    }
}

fn random_gauss() -> f32 {
    [(); 12].map(|()| random::<f32>()).iter().sum::<f32>() - 6.
}

#[cfg(test)]
mod test {
    use super::{
        ConnectionID,
        server::{Server, ServerConfig},
    };
    use hurrycurry_protocol::{Character, GameConfig, Hand, PacketS, PlayerClass, PlayerID};
    use tokio::sync::broadcast;

    fn server() -> Server {
        Server::new(
            ServerConfig {
                data_path: "../data".into(),
                ..Default::default()
            },
            broadcast::channel(1024).0,
        )
        .unwrap()
    }

    #[test]
    fn init_server() {
        server();
    }

    #[test]
    fn full_game() {
        let mut s = server();
        s.load(GameConfig {
            map: "junior".to_string(),
            ..Default::default()
        })
        .unwrap();
        while s.tick(0.1).is_none() {}
    }

    #[test]
    fn map_load() {
        let mut s = server();
        s.load(GameConfig {
            map: "junior".to_string(),
            ..Default::default()
        })
        .unwrap();
    }
    #[test]
    fn map_load_book() {
        let mut s = server();
        s.load(GameConfig {
            map: "junior".to_string(),
            ..Default::default()
        })
        .unwrap();
    }
    #[test]
    fn tick() {
        let mut s = server();
        for _ in 0..100 {
            s.tick(0.1);
        }
    }
    #[test]
    fn packet_sender_verif() {
        let mut s = server();

        for (conn, p) in [
            PacketS::Leave {
                player: PlayerID(0),
            },
            PacketS::Interact {
                hand: Hand(0),
                player: PlayerID(0),
                target: None,
            },
            PacketS::ReplayTick { dt: 1. },
        ]
        .into_iter()
        .enumerate()
        {
            s.packet_in_outer(
                ConnectionID(conn.try_into().unwrap()),
                PacketS::Join {
                    name: format!("test {conn}"),
                    character: Character::default(),
                    class: PlayerClass::Chef,
                    id: None,
                    position: None,
                },
            )
            .unwrap();

            let x = s.packet_in_outer(ConnectionID(conn.try_into().unwrap()), p);
            assert!(x.is_ok(), "test {} {:?}", conn, x)
        }
    }
}
