/*
    Hurry Curry! - a game about cooking
    Copyright (C) 2025 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 crate::{
    ConnectionID,
    server::{AnnounceState, ConnectionData, GameServerExt, Server},
};
use anyhow::Result;
use hurrycurry_locale::{TrError, tre, trm};
use hurrycurry_protocol::{Menu, Message, PacketC, PacketS, PlayerID, VERSION};
use log::{debug, info, trace};

impl Server {
    pub fn tick_outer(&mut self, dt: f32) -> anyhow::Result<()> {
        if !self.paused {
            let r = self.tick(dt);
            if let Some((name, timer)) = r {
                self.scoreboard.save()?;
                self.load(self.index.generate_with_book(&name)?, timer);
            }
        }

        match &mut self.announce_state {
            AnnounceState::Queued if !self.paused => {
                self.announce_state = AnnounceState::Running(3.5);
                self.packet_out
                    .push_back(PacketC::Menu(Menu::AnnounceStart));
                self.update_paused();
            }
            AnnounceState::Running(timer) => {
                *timer -= dt;
                if *timer <= 0. {
                    self.announce_state = AnnounceState::Done;
                    self.update_paused();
                }
            }
            _ => (),
        }

        while let Some(p) = self.packet_out.pop_front() {
            if matches!(p, PacketC::UpdateMap { .. } | PacketC::Movement { .. }) {
                trace!("-> {p:?}");
            } else {
                debug!("-> {p:?}");
            }
            self.tx.send(p).unwrap();
        }
        Ok(())
    }

    pub async fn connect(&mut self, id: ConnectionID) -> Vec<PacketC> {
        let mut init = self.game.prime_client();
        init.insert(
            0,
            PacketC::Version {
                major: VERSION.0,
                minor: VERSION.1,
                supports_bincode: true,
            },
        );
        self.connections.insert(id, ConnectionData::default());
        self.update_paused();
        init
    }

    pub async fn disconnect(&mut self, conn: ConnectionID) {
        if let Some(cd) = self.connections.get(&conn) {
            for player in cd.players.clone() {
                let _ = self.packet_in_outer(conn, PacketS::Leave { player }).await;
            }
        }
        self.connections.remove(&conn);
        self.update_paused();
    }

    pub async fn packet_in_outer(
        &mut self,
        conn: ConnectionID,
        packet: PacketS,
    ) -> Result<Vec<PacketC>, TrError> {
        if let Some(p) = get_packet_player(&packet)
            && !self
                .connections
                .entry(conn)
                .or_default()
                .players
                .contains(&p)
        {
            return Err(tre!("s.error.packet_sender_invalid"));
        }
        let mut replies = Vec::new();
        match &packet {
            PacketS::Communicate {
                message: Some(Message::Text(text)),
                timeout: None,
                player,
                ..
            } if let Some(command) = text.strip_prefix("/") => {
                match self.handle_command_parse(*player, command) {
                    Ok(packets) => return Ok(packets),
                    Err(e) => {
                        return Ok(vec![PacketC::ServerMessage {
                            message: e.into(),
                            error: true,
                        }]);
                    }
                }
            }
            PacketS::Ready => {
                self.connections.entry(conn).or_default().ready = true;
                self.update_paused();
            }
            PacketS::Idle { paused } => {
                self.connections.entry(conn).or_default().idle = *paused;
                self.update_paused();
            }
            PacketS::Leave { player } => {
                self.connections
                    .entry(conn)
                    .or_default()
                    .players
                    .remove(player);
            }
            PacketS::Join { .. } => {
                if self.connections.entry(conn).or_default().players.len() > 8 {
                    return Err(tre!("s.error.conn_too_many_players"));
                }
            }
            _ => (),
        }
        self.packet_in(Some(conn), packet, &mut replies)?;

        for p in &replies {
            if let PacketC::Joined { id } = p {
                self.connections
                    .entry(conn)
                    .or_default()
                    .players
                    .insert(*id);
            }
        }

        if self.count_chefs() == 0 && !self.game.lobby {
            self.tx
                .send(PacketC::ServerMessage {
                    message: trm!("s.state.abort_no_players"),
                    error: false,
                })
                .ok();
            self.load(
                self.index
                    .generate_with_book("lobby")
                    .map_err(|m| tre!("s.error.map_load", s = format!("{m}")))?,
                None,
            );
        }
        Ok(replies)
    }

    pub fn update_paused(&mut self) {
        let all_idle = self
            .connections
            .values()
            .all(|c| c.idle && !c.players.is_empty());
        let mut not_ready = self
            .connections
            .values()
            .filter(|c| !c.ready && !c.players.is_empty())
            .count();
        let announcing = matches!(self.announce_state, AnnounceState::Running(_));

        if self.game.lobby {
            not_ready = 0; // not waiting for players in lobby
        }

        let should_pause = all_idle || not_ready > 0 || announcing;

        let reason = if not_ready > 0 {
            info!("Game paused: {not_ready} player are not ready");
            Some(trm!(
                "s.state.paused.any_not_ready",
                s = not_ready.to_string()
            ))
        } else if all_idle {
            if self.connections.is_empty() {
                info!("Game paused: Server empty");
            } else {
                info!("Game paused: All players idle");
            }
            Some(trm!("s.state.paused.all_idle"))
        } else if announcing {
            info!("Game paused: Waiting for announcement");
            None
        } else {
            if self.paused {
                info!("Game unpaused");
            }
            None
        };
        self.paused = should_pause;

        for p in self.game.players.keys() {
            self.packet_out
                .push_back(PacketC::Pause { state: self.paused });
            self.packet_out.push_back(PacketC::ServerHint {
                position: None,
                message: reason.clone(),
                player: *p,
            });
        }
    }
}

fn get_packet_player(packet: &PacketS) -> Option<PlayerID> {
    match packet {
        PacketS::Join { .. } => None,
        PacketS::Idle { .. } => None,
        PacketS::Leave { player } => Some(*player),
        PacketS::Movement { player, .. } => Some(*player),
        PacketS::Interact { player, .. } => Some(*player),
        PacketS::Communicate { player, .. } => Some(*player),
        PacketS::ReplaceHand { player, .. } => Some(*player),
        PacketS::Effect { player, .. } => Some(*player),
        PacketS::Ready => None,
        PacketS::ApplyScore(_) => None,
        PacketS::ReplayTick { .. } => None,
    }
}
