/*
    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_bot::{BotAlgo, DynBotAlgo, PacketSink};
use hurrycurry_locale::TrError;
use hurrycurry_protocol::{Character, PacketS, PlayerClass, PlayerID};
use log::debug;
use std::any::Any;

pub type DynBotDriver = BotDriver<DynBotAlgo>;

pub enum BotDriver<T> {
    Joining {
        name: String,
        character: Character,
        class: PlayerClass,
        constructor: Option<Box<dyn FnOnce(PlayerID) -> T + Send + Sync>>,
    },
    Running {
        state: T,
        id: PlayerID,
    },
    Left,
}

impl<T: BotAlgo> BotDriver<T> {
    pub fn new(
        name: String,
        character: Character,
        class: PlayerClass,
        constructor: impl FnOnce(PlayerID) -> T + 'static + Send + Sync,
    ) -> Self {
        Self::Joining {
            character,
            class,
            constructor: Some(Box::new(constructor)),
            name,
        }
    }
}
impl<T: BotAlgo + Any> Entity for BotDriver<T> {
    fn finished(&self) -> bool {
        matches!(self, Self::Left)
    }
    fn tick(&mut self, c: EntityContext<'_>) -> Result<(), TrError> {
        match self {
            BotDriver::Joining {
                name,
                character,
                class,
                constructor,
            } => {
                let id = c.game.get_unused_player_id(); // TODO clashes when multiple bots join in the same tick
                debug!("enter {id}");
                c.packet_in.push_back(PacketS::Join {
                    name: name.to_string(),
                    character: *character,
                    id: Some(id),
                    class: *class,
                    position: None,
                });
                *self = BotDriver::Running {
                    state: constructor.take().unwrap()(id),
                    id,
                };
                Ok(())
            }
            BotDriver::Running { state, id } => {
                state.tick(PacketSink::new(c.packet_in), c.game, c.dt);
                if state.is_finished() {
                    debug!("leave {id}");
                    c.packet_in.push_back(PacketS::Leave { player: *id });
                    *self = BotDriver::Left
                }
                Ok(())
            }
            BotDriver::Left => Ok(()),
        }
    }
    fn destructor(&mut self, c: EntityContext<'_>) {
        if let Self::Running { id, .. } = self {
            c.packet_in.push_back(PacketS::Leave { player: *id })
        }
    }
}
