/*
    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/>.

*/
use super::{Entity, EntityContext};
use anyhow::Result;
use hurrycurry_game_core::Item;
use hurrycurry_locale::TrError;
use hurrycurry_protocol::{
    Hand, ItemIndex, ItemLocation, Message, PacketC, PlayerID, TileIndex, glam::IVec2,
};
use rand::RngExt;
use std::{collections::HashMap, fmt::Write};

#[derive(Debug, Clone)]
pub struct TagMinigame {
    scores: HashMap<PlayerID, f32>,
    player_place_timeouts: HashMap<PlayerID, f32>,
    wall_place_delays: HashMap<IVec2, f32>,
    wall_remove_timeouts: HashMap<IVec2, f32>,
    report_cooldown: f32,
    init_done: bool,
    tag_item: ItemIndex,
    blocker_tile: TileIndex,
}
impl TagMinigame {
    pub fn new(tag_item: ItemIndex, blocker_tile: TileIndex) -> Self {
        Self {
            init_done: false,
            report_cooldown: 0.,
            scores: HashMap::new(),
            player_place_timeouts: HashMap::new(),
            wall_remove_timeouts: HashMap::new(),
            wall_place_delays: HashMap::new(),
            tag_item,
            blocker_tile,
        }
    }
}
impl Entity for TagMinigame {
    fn tick(&mut self, c: EntityContext) -> Result<(), TrError> {
        if !self.init_done {
            self.init_done = true;
            // Hand out the item to some player(s)
            let player_ids = c.game.players.keys().copied().collect::<Vec<_>>();
            let num_tagged = 1 + (player_ids.len() / 5);
            for _ in 0..num_tagged {
                if player_ids.is_empty() {
                    break;
                }
                let id = player_ids[usize::from_ne_bytes(rand::rng().random())];
                let player = c.game.players.get_mut(&id).expect("just got the ids");
                if let Some(slot) = player.items.get_mut(0) {
                    *slot = Some(Item {
                        active: None,
                        kind: self.tag_item,
                    });
                    c.packet_out.push_back(PacketC::SetItem {
                        location: ItemLocation::Player(id, Hand(0)),
                        item: Some(self.tag_item),
                    });
                }
            }
        }

        // Award points to players with the item
        for (&id, player) in &c.game.players {
            if let Some(slot) = player.items.first()
                && slot.is_none()
            {
                *self.scores.entry(id).or_default() += c.dt
            }
            if player.movement.boosting && !self.player_place_timeouts.contains_key(&id) {
                let pos = player.movement.position.floor().as_ivec2();
                self.player_place_timeouts.insert(id, 3.);
                self.wall_place_delays.insert(pos, 0.5);
            }
        }

        self.player_place_timeouts
            .values_mut()
            .for_each(|v| *v -= c.dt);
        self.player_place_timeouts.retain(|_, v| *v > 0.);

        self.wall_place_delays.values_mut().for_each(|v| *v -= c.dt);
        self.wall_place_delays
            .iter()
            .filter(|(_, v)| **v <= 0.)
            .for_each(|(&pos, _)| {
                self.wall_remove_timeouts.insert(pos, 3.);
                c.game.add_tile_part(pos, self.blocker_tile);
            });
        self.wall_place_delays.retain(|_, v| *v > 0.);

        self.wall_remove_timeouts
            .values_mut()
            .for_each(|v| *v -= c.dt);
        self.wall_remove_timeouts
            .iter()
            .filter(|(_, v)| **v <= 0.)
            .for_each(|(&pos, _)| {
                c.game.remove_tile_part(pos, self.blocker_tile);
            });
        self.wall_remove_timeouts.retain(|_, v| *v > 0.);

        // Send out a scoreboard message every 0.1s
        self.report_cooldown -= c.dt;
        if self.report_cooldown < 0. {
            self.report_cooldown = 0.1;
            let mut scores = self
                .scores
                .iter()
                .map(|(x, y)| ((y * 10.) as i64, *x))
                .collect::<Vec<_>>();
            scores.sort_by_key(|(s, _)| -s);

            let mut o = String::new();
            writeln!(o, "Tag Minigame — Try to not have the lettuce!").unwrap();
            for ((score, player), rank) in scores.into_iter().zip(1..) {
                let name = c.game.players.get(&player).map_or("unknown", |p| &p.name);
                writeln!(o, "{rank:>2}. {score:>5} {name}").unwrap();
            }
            o.pop(); // newline
            c.packet_out.push_back(PacketC::ServerMessage {
                error: false,
                message: Message::Text(o),
            });
        }

        Ok(())
    }
}
