/*
    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 bot;
mod button;
mod campaign;
mod conveyor;
mod ctf_minigame;
mod customers;
mod demand_sink;
mod environment_effect;
mod item_portal;
mod pedestrians;
mod player_portal;
pub mod player_portal_pair;
mod tag_minigame;
mod tram;
pub mod tutorial;

use crate::{
    entity::{
        bot::BotDriver, ctf_minigame::CtfMinigame, demand_sink::DemandSink,
        pedestrians::Pedestrians, player_portal_pair::PlayerPortalPair, tag_minigame::TagMinigame,
    },
    scoreboard::ScoreboardStore,
};
use anyhow::Result;
use button::Button;
use campaign::{Gate, Map};
use conveyor::Conveyor;
use customers::Customers;
use environment_effect::{EnvironmentController, EnvironmentEffectController};
use hurrycurry_bot::algos::ALGO_CONSTRUCTORS;
use hurrycurry_data::{PrivateGamedata, entities::EntityDecl};
use hurrycurry_game_core::Game;
use hurrycurry_locale::TrError;
use hurrycurry_protocol::{
    Character, GameConfig, ItemLocation, PacketC, PacketS, PlayerClass, PlayerID,
};
use item_portal::ItemPortal;
use player_portal::PlayerPortal;
use std::{
    any::Any,
    collections::{HashMap, VecDeque},
};
use tram::Tram;

pub type DynEntity = Box<dyn Entity + Send + Sync + 'static>;
pub type Entities = Vec<DynEntity>;

pub struct EntityContext<'a> {
    pub game: &'a mut Game,
    pub serverdata: &'a PrivateGamedata,
    pub packet_out: &'a mut VecDeque<PacketC>,
    pub packet_in: &'a mut VecDeque<PacketS>,
    pub score_changed: &'a mut bool,
    pub load_map: &'a mut Option<GameConfig>,
    pub scoreboard: &'a ScoreboardStore,
    pub replies: Option<&'a mut Vec<PacketC>>,
    pub dt: f32,
}
impl EntityContext<'_> {
    pub fn dup<'a>(&'a mut self) -> EntityContext<'a> {
        EntityContext {
            game: self.game,
            serverdata: self.serverdata,
            packet_out: self.packet_out,
            packet_in: self.packet_in,
            score_changed: self.score_changed,
            dt: self.dt,
            scoreboard: self.scoreboard,
            load_map: self.load_map,
            replies: None,
        }
    }
}

pub trait Entity: Any {
    fn tick(&mut self, _c: EntityContext<'_>) -> Result<(), TrError> {
        Ok(())
    }
    fn finished(&self) -> bool {
        false
    }
    fn constructor(&mut self, _c: EntityContext<'_>) {}
    fn destructor(&mut self, _c: EntityContext<'_>) {}
    fn interact(
        &mut self,
        _c: EntityContext<'_>,
        _target: Option<ItemLocation>,
        _player: PlayerID,
    ) -> Result<bool, TrError> {
        Ok(false)
    }
}

pub fn construct_entity(decl: &EntityDecl) -> DynEntity {
    match decl.to_owned() {
        EntityDecl::TagMinigame {
            tag_item,
            blocker_tile,
        } => Box::new(TagMinigame::new(tag_item, blocker_tile)),
        EntityDecl::Button { pos, action } => Box::new(Button { pos, action }),
        EntityDecl::ItemPortal { from, to } => Box::new(ItemPortal { from, to }),
        EntityDecl::PlayerPortal { from, to } => Box::new(PlayerPortal { from, to }),
        EntityDecl::Conveyor { from, to, speed } => Box::new(Conveyor {
            from,
            to,
            max_cooldown: 1. / speed.unwrap_or(2.),
            cooldown: 0.,
        }),
        EntityDecl::Map { name, pos } => Box::new(Map { pos, name }),
        EntityDecl::Gate {
            condition,
            pos,
            blocker_tile,
        } => Box::new(Gate {
            condition,
            unlocked: false,
            pos,
            blocker_tile,
            active: true,
        }),
        EntityDecl::Customers {
            scaling_factor,
            spawn_cooldown,
            ..
        } => Box::new(Customers::new(
            scaling_factor.unwrap_or(0.5),
            spawn_cooldown.unwrap_or(5.),
        )),
        EntityDecl::EnvironmentEffect(config) => Box::new(EnvironmentEffectController::new(config)),
        EntityDecl::Environment(names) => Box::new(EnvironmentController(names)),
        EntityDecl::Tram {
            length,
            color,
            points,
            smoothing,
            spacing,
        } => Box::new(Tram {
            length,
            character: Character {
                color: color.unwrap_or_default(),
                hairstyle: 0,
                headwear: 0,
            },
            ids: Vec::new(),
            points,
            progress: 0.,
            spacing,
            smoothing,
        }),
        EntityDecl::Pedestrians {
            spawn_delay,
            spawn_delay_stdev,
            points,
            speed,
        } => Box::new(Pedestrians {
            players: HashMap::new(),
            points,
            spawn_delay,
            spawn_delay_stdev: spawn_delay_stdev.unwrap_or(0.),
            cooldown: 0.,
            speed: speed.unwrap_or(0.6),
        }),
        EntityDecl::DemandSink { pos } => Box::new(DemandSink { pos }),
        EntityDecl::CtfMinigame {
            spawnpoints,
            item_indices,
            ..
        } => Box::new(CtfMinigame::new(&spawnpoints, &item_indices)),
        EntityDecl::PlayerPortalPair {
            a,
            b,
            in_tile,
            neutral_tile,
            out_tile,
        } => Box::new(PlayerPortalPair::new(a, b, in_tile, neutral_tile, out_tile)),

        EntityDecl::Bot { name: search_name } => {
            let (name, cons) = ALGO_CONSTRUCTORS
                .iter()
                .find(|(name, _)| *name == search_name.as_str())
                .unwrap_or(&ALGO_CONSTRUCTORS[0]);

            Box::new(BotDriver::new(
                format!("{name}-bot"),
                Character {
                    color: 0,
                    hairstyle: 0,
                    headwear: 0,
                },
                PlayerClass::Bot,
                cons,
            ))
        }
    }
}
